@owloops/browserbird 1.2.6 → 1.2.7

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.
@@ -209,7 +209,6 @@ const MIGRATIONS = [{
209
209
  target_channel_id TEXT,
210
210
  active_hours_start TEXT,
211
211
  active_hours_end TEXT,
212
- timezone TEXT DEFAULT 'UTC',
213
212
  enabled INTEGER NOT NULL DEFAULT 1,
214
213
  failure_count INTEGER NOT NULL DEFAULT 0,
215
214
  last_run TEXT,
@@ -536,11 +535,11 @@ function listCronJobs(page = 1, perPage = DEFAULT_PER_PAGE, includeSystem = fals
536
535
  function getEnabledCronJobs() {
537
536
  return getDb().prepare("SELECT * FROM cron_jobs WHERE enabled = 1 ORDER BY created_at").all();
538
537
  }
539
- function createCronJob(name, schedule, prompt, targetChannelId, agentId, timezone, activeHoursStart, activeHoursEnd) {
538
+ function createCronJob(name, schedule, prompt, targetChannelId, agentId, activeHoursStart, activeHoursEnd) {
540
539
  const uid = generateUid(UID_PREFIX.bird);
541
- return getDb().prepare(`INSERT INTO cron_jobs (uid, name, schedule, prompt, target_channel_id, agent_id, timezone, active_hours_start, active_hours_end)
542
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
543
- RETURNING *`).get(uid, name, schedule, prompt, targetChannelId ?? null, agentId ?? "default", timezone ?? "UTC", activeHoursStart ?? null, activeHoursEnd ?? null);
540
+ return getDb().prepare(`INSERT INTO cron_jobs (uid, name, schedule, prompt, target_channel_id, agent_id, active_hours_start, active_hours_end)
541
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
542
+ RETURNING *`).get(uid, name, schedule, prompt, targetChannelId ?? null, agentId ?? "default", activeHoursStart ?? null, activeHoursEnd ?? null);
544
543
  }
545
544
  function updateCronJobStatus(jobUid, status, failureCount) {
546
545
  getDb().prepare(`UPDATE cron_jobs SET last_run = datetime('now'), last_status = ?, failure_count = ? WHERE uid = ?`).run(status, failureCount, jobUid);
@@ -575,10 +574,6 @@ function updateCronJob(jobUid, fields) {
575
574
  sets.push("agent_id = ?");
576
575
  params.push(fields.agentId);
577
576
  }
578
- if (fields.timezone !== void 0) {
579
- sets.push("timezone = ?");
580
- params.push(fields.timezone);
581
- }
582
577
  if (fields.activeHoursStart !== void 0) {
583
578
  sets.push("active_hours_start = ?");
584
579
  params.push(fields.activeHoursStart);
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as updateSessionProviderId, A as completeCronRun, B as listFlights, C as failStaleJobs, D as retryAllFailedJobs, E as listJobs, F as ensureSystemCronJob, G as deleteStaleSessions, H as updateCronJob, I as getCronJob, J as getSessionCount, K as findSession, L as getEnabledCronJobs, M as createCronRun, N as deleteCronJob, O as retryJob, P as deleteOldCronRuns, Q as touchSession, R as getFlightStats, S as failJob, T as hasPendingCronJob, U as updateCronJobStatus, V as setCronJobEnabled, W as createSession, X as getSessionTokenStats, Y as getSessionMessages, Z as listSessions, _ as clearJobs, a as getSetting, at as resolveByUid, b as deleteJob, c as getUserCount, d as getRecentLogs, et as shortUid, f as insertLog, g as claimNextJob, h as logMessage, i as createUser, it as optimizeDatabase, j as createCronJob, k as SYSTEM_CRON_PREFIX, l as setSetting, m as getMessageStats, n as resolveDbPath, nt as getDb, o as getUserByEmail, ot as logger, p as deleteOldMessages, q as getSession, r as resolveDbPathFromArgv, rt as openDatabase, s as getUserById, tt as closeDatabase, u as deleteOldLogs, v as completeJob, w as getJobStats, x as deleteOldJobs, y as createJob, z as listCronJobs } from "./db-C9ESgb0d.mjs";
1
+ import { $ as updateSessionProviderId, A as completeCronRun, B as listFlights, C as failStaleJobs, D as retryAllFailedJobs, E as listJobs, F as ensureSystemCronJob, G as deleteStaleSessions, H as updateCronJob, I as getCronJob, J as getSessionCount, K as findSession, L as getEnabledCronJobs, M as createCronRun, N as deleteCronJob, O as retryJob, P as deleteOldCronRuns, Q as touchSession, R as getFlightStats, S as failJob, T as hasPendingCronJob, U as updateCronJobStatus, V as setCronJobEnabled, W as createSession, X as getSessionTokenStats, Y as getSessionMessages, Z as listSessions, _ as clearJobs, a as getSetting, at as resolveByUid, b as deleteJob, c as getUserCount, d as getRecentLogs, et as shortUid, f as insertLog, g as claimNextJob, h as logMessage, i as createUser, it as optimizeDatabase, j as createCronJob, k as SYSTEM_CRON_PREFIX, l as setSetting, m as getMessageStats, n as resolveDbPath, nt as getDb, o as getUserByEmail, ot as logger, p as deleteOldMessages, q as getSession, r as resolveDbPathFromArgv, rt as openDatabase, s as getUserById, tt as closeDatabase, u as deleteOldLogs, v as completeJob, w as getJobStats, x as deleteOldJobs, y as createJob, z as listCronJobs } from "./db-Da2zSpkY.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { parseArgs, styleText } from "node:util";
4
4
  import { copyFileSync, existsSync, mkdirSync, 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: ${"536f400d5d95ecfb7467a216235dcdd8e7d9573f".substring(0, 7)}`);
126
- buildInfo.push(`built: 2026-03-05T15:03:39+04:00`);
125
+ buildInfo.push(`commit: ${"febb9bb9f678ef12c57880b663dcc875288f1950".substring(0, 7)}`);
126
+ buildInfo.push(`built: 2026-03-06T10:34:46+04:00`);
127
127
  const buildString = buildInfo.length > 0 ? ` (${buildInfo.join(", ")})` : "";
128
128
  const VERSION = `browserbird ${pkg.version}${buildString}`;
129
129
  const BIRD = [
@@ -374,6 +374,37 @@ function loadDotEnv(envPath) {
374
374
  }
375
375
  }
376
376
 
377
+ //#endregion
378
+ //#region src/browser/lock.ts
379
+ /** @fileoverview SQLite-based browser lock for persistent browser mode. */
380
+ /**
381
+ * Attempts to acquire the browser lock for a given holder.
382
+ * Uses a single conditional UPSERT: inserts if absent, overwrites if stale.
383
+ * Returns false when an active (non-stale) lock is held by someone else.
384
+ */
385
+ function acquireBrowserLock(holder, timeoutMs) {
386
+ const timeoutSeconds = Math.ceil(timeoutMs / 1e3);
387
+ const result = getDb().prepare(`INSERT INTO browser_lock (id, holder, acquired_at)
388
+ VALUES (1, ?, datetime('now'))
389
+ ON CONFLICT(id) DO UPDATE SET
390
+ holder = excluded.holder,
391
+ acquired_at = excluded.acquired_at
392
+ WHERE acquired_at <= datetime('now', '-' || ? || ' seconds')`).run(holder, timeoutSeconds);
393
+ return Number(result.changes) > 0;
394
+ }
395
+ /** Refreshes the lock timestamp to prevent staleness during long operations. */
396
+ function refreshBrowserLock(holder) {
397
+ getDb().prepare(`UPDATE browser_lock SET acquired_at = datetime('now') WHERE id = 1 AND holder = ?`).run(holder);
398
+ }
399
+ /** Releases the browser lock only if the caller is the current holder. */
400
+ function releaseBrowserLock(holder) {
401
+ getDb().prepare("DELETE FROM browser_lock WHERE id = 1 AND holder = ?").run(holder);
402
+ }
403
+ /** Clears any browser lock unconditionally. Called on startup before any sessions exist. */
404
+ function clearBrowserLock() {
405
+ getDb().prepare("DELETE FROM browser_lock WHERE id = 1").run();
406
+ }
407
+
377
408
  //#endregion
378
409
  //#region src/server/auth.ts
379
410
  /** @fileoverview Password hashing, token signing, and verification using node:crypto. */
@@ -1353,7 +1384,7 @@ function buildRoutes(getConfig, startedAt, getDeps, options) {
1353
1384
  for (const bird of getEnabledCronJobs()) {
1354
1385
  if (bird.name.startsWith(SYSTEM_CRON_PREFIX)) continue;
1355
1386
  try {
1356
- const next = nextCronMatch(parseCron(bird.schedule), now, bird.timezone);
1387
+ const next = nextCronMatch(parseCron(bird.schedule), now, getConfig().timezone);
1357
1388
  if (next) upcoming.push({
1358
1389
  uid: bird.uid,
1359
1390
  name: bird.name,
@@ -1417,7 +1448,7 @@ function buildRoutes(getConfig, startedAt, getDeps, options) {
1417
1448
  jsonError(res, "\"prompt\" is required", 400);
1418
1449
  return;
1419
1450
  }
1420
- const job = createCronJob(deriveBirdName(body.prompt), body.schedule.trim(), body.prompt.trim(), body.channel?.trim() || void 0, body.agent?.trim() || void 0, body.timezone?.trim() || getConfig().timezone, body.activeHoursStart?.trim() || void 0, body.activeHoursEnd?.trim() || void 0);
1451
+ const job = createCronJob(deriveBirdName(body.prompt), body.schedule.trim(), body.prompt.trim(), body.channel?.trim() || void 0, body.agent?.trim() || void 0, body.activeHoursStart?.trim() || void 0, body.activeHoursEnd?.trim() || void 0);
1421
1452
  broadcastSSE("invalidate", { resource: "birds" });
1422
1453
  json(res, job, 201);
1423
1454
  }
@@ -1441,7 +1472,6 @@ function buildRoutes(getConfig, startedAt, getDeps, options) {
1441
1472
  name: body.prompt ? deriveBirdName(body.prompt) : void 0,
1442
1473
  targetChannelId: body.channel !== void 0 ? body.channel?.trim() || null : void 0,
1443
1474
  agentId: body.agent?.trim() || void 0,
1444
- timezone: body.timezone?.trim() || void 0,
1445
1475
  activeHoursStart: body.activeHoursStart !== void 0 ? body.activeHoursStart?.trim() || null : void 0,
1446
1476
  activeHoursEnd: body.activeHoursEnd !== void 0 ? body.activeHoursEnd?.trim() || null : void 0
1447
1477
  });
@@ -2121,7 +2151,10 @@ function buildCommand$1(options) {
2121
2151
  String(agent.maxTurns)
2122
2152
  ];
2123
2153
  if (sessionId) args.push("--resume", sessionId);
2124
- if (agent.systemPrompt) args.push("--append-system-prompt", agent.systemPrompt);
2154
+ const systemParts = [];
2155
+ if (agent.systemPrompt) systemParts.push(agent.systemPrompt);
2156
+ if (options.timezone) systemParts.push(`System timezone: ${options.timezone}. All cron expressions and scheduled times use this timezone.`);
2157
+ if (systemParts.length > 0) args.push("--append-system-prompt", systemParts.join(" "));
2125
2158
  if (mcpConfigPath) args.push("--mcp-config", mcpConfigPath);
2126
2159
  if (agent.fallbackModel) args.push("--fallback-model", agent.fallbackModel);
2127
2160
  args.push("--dangerously-skip-permissions");
@@ -2210,6 +2243,10 @@ function parseAssistantContent(parsed) {
2210
2243
  type: "text_delta",
2211
2244
  delta: b["text"]
2212
2245
  });
2246
+ else if (b["type"] === "tool_use" && typeof b["name"] === "string") events.push({
2247
+ type: "tool_use",
2248
+ toolName: b["name"]
2249
+ });
2213
2250
  }
2214
2251
  return events;
2215
2252
  }
@@ -2315,7 +2352,10 @@ function ensureWorkspace(mcpConfigPath, systemPrompt) {
2315
2352
  */
2316
2353
  function buildCommand(options) {
2317
2354
  const { message, sessionId, agent, mcpConfigPath } = options;
2318
- ensureWorkspace(mcpConfigPath, agent.systemPrompt);
2355
+ const systemParts = [];
2356
+ if (agent.systemPrompt) systemParts.push(agent.systemPrompt);
2357
+ if (options.timezone) systemParts.push(`System timezone: ${options.timezone}. All cron expressions and scheduled times use this timezone.`);
2358
+ ensureWorkspace(mcpConfigPath, systemParts.join(" ") || void 0);
2319
2359
  const args = [
2320
2360
  "run",
2321
2361
  "--format",
@@ -2428,6 +2468,14 @@ function parseStreamLine(line) {
2428
2468
  accumulators.delete(sid);
2429
2469
  return [completion];
2430
2470
  }
2471
+ case "tool_use": {
2472
+ const toolName = typeof part?.["tool"] === "string" && part["tool"] || "";
2473
+ if (toolName) return [{
2474
+ type: "tool_use",
2475
+ toolName
2476
+ }];
2477
+ return [];
2478
+ }
2431
2479
  case "error": {
2432
2480
  const err = parsed["error"];
2433
2481
  const data = err?.["data"];
@@ -2867,31 +2915,10 @@ function truncate(text, maxLength) {
2867
2915
  return text.slice(0, maxLength) + "...";
2868
2916
  }
2869
2917
 
2870
- //#endregion
2871
- //#region src/browser/lock.ts
2872
- /** @fileoverview SQLite-based browser lock for persistent browser mode. */
2873
- /**
2874
- * Attempts to acquire the browser lock for a given holder.
2875
- * Uses a single conditional UPSERT: inserts if absent, overwrites if stale.
2876
- * Returns false when an active (non-stale) lock is held by someone else.
2877
- */
2878
- function acquireBrowserLock(holder, timeoutMs) {
2879
- const timeoutSeconds = Math.ceil(timeoutMs / 1e3);
2880
- const result = getDb().prepare(`INSERT INTO browser_lock (id, holder, acquired_at)
2881
- VALUES (1, ?, datetime('now'))
2882
- ON CONFLICT(id) DO UPDATE SET
2883
- holder = excluded.holder,
2884
- acquired_at = excluded.acquired_at
2885
- WHERE acquired_at <= datetime('now', '-' || ? || ' seconds')`).run(holder, timeoutSeconds);
2886
- return Number(result.changes) > 0;
2887
- }
2888
- /** Releases the browser lock only if the caller is the current holder. */
2889
- function releaseBrowserLock(holder) {
2890
- getDb().prepare("DELETE FROM browser_lock WHERE id = 1 AND holder = ?").run(holder);
2891
- }
2892
-
2893
2918
  //#endregion
2894
2919
  //#region src/cron/scheduler.ts
2920
+ const BROWSER_TOOL_PREFIX$1 = "mcp__playwright__";
2921
+ const LOCK_HEARTBEAT_MS$1 = 3e4;
2895
2922
  const TICK_INTERVAL_MS = 6e4;
2896
2923
  const MAX_SCHEDULE_ERRORS = 3;
2897
2924
  const systemHandlers = /* @__PURE__ */ new Map();
@@ -2928,19 +2955,26 @@ function startScheduler(config, signal, deps) {
2928
2955
  const agent = config.agents.find((a) => a.id === payload.agentId);
2929
2956
  if (!agent) throw new Error(`agent "${payload.agentId}" not found`);
2930
2957
  const needsBrowserLock = config.browser.enabled && getBrowserMode() === "persistent";
2931
- if (needsBrowserLock) {
2932
- if (!acquireBrowserLock(payload.cronJobUid, config.sessions.processTimeoutMs)) throw new Error("browser is locked by another session");
2933
- }
2958
+ let browserLockAcquired = false;
2959
+ let heartbeatTimer;
2934
2960
  try {
2935
2961
  const { events } = spawnProvider(agent.provider, {
2936
2962
  message: payload.prompt,
2937
2963
  agent,
2938
- mcpConfigPath: config.browser.mcpConfigPath
2964
+ mcpConfigPath: config.browser.mcpConfigPath,
2965
+ timezone: config.timezone
2939
2966
  }, signal);
2940
2967
  if (payload.channelId) logMessage(payload.channelId, null, agent.id, "in", payload.prompt);
2941
2968
  let result = "";
2942
2969
  let completion;
2943
- for await (const event of events) if (event.type === "text_delta") result += redact(event.delta);
2970
+ for await (const event of events) if (event.type === "tool_use") {
2971
+ if (needsBrowserLock && !browserLockAcquired && event.toolName.startsWith(BROWSER_TOOL_PREFIX$1)) {
2972
+ if (!acquireBrowserLock(payload.cronJobUid, config.sessions.processTimeoutMs)) throw new Error("browser is locked by another session");
2973
+ browserLockAcquired = true;
2974
+ heartbeatTimer = setInterval(() => refreshBrowserLock(payload.cronJobUid), LOCK_HEARTBEAT_MS$1);
2975
+ logger.info(`browser lock acquired lazily for bird ${shortUid(payload.cronJobUid)}`);
2976
+ }
2977
+ } else if (event.type === "text_delta") result += redact(event.delta);
2944
2978
  else if (event.type === "completion") completion = event;
2945
2979
  else if (event.type === "rate_limit") logger.debug(`bird ${shortUid(payload.cronJobUid)} rate limit window resets ${(/* @__PURE__ */ new Date(event.resetsAt * 1e3)).toISOString()}`);
2946
2980
  else if (event.type === "error") {
@@ -2967,7 +3001,8 @@ function startScheduler(config, signal, deps) {
2967
3001
  } else logger.info(`bird ${shortUid(payload.cronJobUid)} completed (${result.length} chars)`);
2968
3002
  return result;
2969
3003
  } finally {
2970
- if (needsBrowserLock) releaseBrowserLock(payload.cronJobUid);
3004
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
3005
+ if (browserLockAcquired) releaseBrowserLock(payload.cronJobUid);
2971
3006
  }
2972
3007
  });
2973
3008
  registerHandler("system_cron_run", (raw) => {
@@ -3000,8 +3035,8 @@ function startScheduler(config, signal, deps) {
3000
3035
  } else logger.warn(`bird ${shortUid(job.uid)}: invalid expression "${job.schedule}" (attempt ${count}/${MAX_SCHEDULE_ERRORS})`);
3001
3036
  continue;
3002
3037
  }
3003
- if (!matchesCron(schedule, now, job.timezone)) continue;
3004
- if (!isWithinActiveHours(job.active_hours_start, job.active_hours_end, now, job.timezone)) {
3038
+ if (!matchesCron(schedule, now, config.timezone)) continue;
3039
+ if (!isWithinActiveHours(job.active_hours_start, job.active_hours_end, now, config.timezone)) {
3005
3040
  logger.debug(`bird ${shortUid(job.uid)} skipped: outside active hours`);
3006
3041
  continue;
3007
3042
  }
@@ -3101,6 +3136,8 @@ function createCoalescer(config, onDispatch) {
3101
3136
 
3102
3137
  //#endregion
3103
3138
  //#region src/channel/handler.ts
3139
+ const BROWSER_TOOL_PREFIX = "mcp__playwright__";
3140
+ const LOCK_HEARTBEAT_MS = 3e4;
3104
3141
  function createHandler(client, config, signal, getTeamId) {
3105
3142
  const locks = /* @__PURE__ */ new Map();
3106
3143
  let activeSpawns = 0;
@@ -3148,6 +3185,9 @@ function createHandler(client, config, signal, getTeamId) {
3148
3185
  case "tool_images":
3149
3186
  await uploadImages(event.images, channelId, threadTs);
3150
3187
  break;
3188
+ case "tool_use":
3189
+ meta.onToolUse?.(event.toolName);
3190
+ break;
3151
3191
  case "completion":
3152
3192
  completion = event;
3153
3193
  logger.info(`completion [${event.subtype}]: ${event.tokensIn}in/${event.tokensOut}out, $${event.costUsd.toFixed(4)}, ${event.numTurns} turns`);
@@ -3200,11 +3240,8 @@ function createHandler(client, config, signal, getTeamId) {
3200
3240
  return;
3201
3241
  }
3202
3242
  const needsBrowserLock = config.browser.enabled && getBrowserMode() === "persistent";
3203
- if (needsBrowserLock && !acquireBrowserLock(key, config.sessions.processTimeoutMs)) {
3204
- await client.postMessage(channelId, threadTs, "The browser is in use by another session. Your message will be processed when it finishes.");
3205
- lock.queue.push(dispatch);
3206
- return;
3207
- }
3243
+ let browserLockAcquired = false;
3244
+ let heartbeatTimer;
3208
3245
  lock.processing = true;
3209
3246
  activeSpawns++;
3210
3247
  let sessionUid;
@@ -3227,7 +3264,8 @@ function createHandler(client, config, signal, getTeamId) {
3227
3264
  message: prompt,
3228
3265
  sessionId: existingSessionId,
3229
3266
  agent,
3230
- mcpConfigPath: config.browser.mcpConfigPath
3267
+ mcpConfigPath: config.browser.mcpConfigPath,
3268
+ timezone: config.timezone
3231
3269
  }, signal);
3232
3270
  lock.killCurrent = kill;
3233
3271
  client.setStatus?.(channelId, threadTs, "is thinking...").catch(() => {});
@@ -3235,7 +3273,22 @@ function createHandler(client, config, signal, getTeamId) {
3235
3273
  const title = prompt.length > 60 ? prompt.slice(0, 57) + "..." : prompt;
3236
3274
  client.setTitle?.(channelId, threadTs, title).catch(() => {});
3237
3275
  }
3238
- await streamToChannel(events, channelId, threadTs, session.uid, getTeamId(), userId, { birdName: agent.name });
3276
+ const onToolUse = (toolName) => {
3277
+ if (!needsBrowserLock || browserLockAcquired) return;
3278
+ if (!toolName.startsWith(BROWSER_TOOL_PREFIX)) return;
3279
+ if (acquireBrowserLock(key, config.sessions.processTimeoutMs)) {
3280
+ browserLockAcquired = true;
3281
+ heartbeatTimer = setInterval(() => refreshBrowserLock(key), LOCK_HEARTBEAT_MS);
3282
+ logger.info(`browser lock acquired lazily for ${key} (tool: ${toolName})`);
3283
+ } else {
3284
+ logger.warn(`browser lock unavailable for ${key} (tool: ${toolName})`);
3285
+ client.postMessage(channelId, threadTs, "The browser is in use by another session.").catch(() => {});
3286
+ }
3287
+ };
3288
+ await streamToChannel(events, channelId, threadTs, session.uid, getTeamId(), userId, {
3289
+ birdName: agent.name,
3290
+ onToolUse
3291
+ });
3239
3292
  } catch (err) {
3240
3293
  const errMsg = err instanceof Error ? err.message : String(err);
3241
3294
  logger.error(`handler error: ${errMsg}`);
@@ -3245,7 +3298,8 @@ function createHandler(client, config, signal, getTeamId) {
3245
3298
  await client.postMessage(channelId, threadTs, `Something went wrong: ${errMsg}`, { blocks });
3246
3299
  } catch {}
3247
3300
  } finally {
3248
- if (needsBrowserLock) releaseBrowserLock(key);
3301
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
3302
+ if (browserLockAcquired) releaseBrowserLock(key);
3249
3303
  activeSpawns--;
3250
3304
  lock.processing = false;
3251
3305
  lock.killCurrent = null;
@@ -3656,7 +3710,7 @@ function createSlackChannel(config, signal) {
3656
3710
  const interactionType = body["type"];
3657
3711
  if (interactionType === "view_submission") {
3658
3712
  const view = body["view"];
3659
- if (view?.["callback_id"] === "bird_create") await handleBirdCreateSubmission(view, webClient, config.timezone);
3713
+ if (view?.["callback_id"] === "bird_create") await handleBirdCreateSubmission(view, webClient);
3660
3714
  }
3661
3715
  if (interactionType === "block_actions") {
3662
3716
  const actionsArr = body["actions"];
@@ -3769,7 +3823,7 @@ function createSlackChannel(config, signal) {
3769
3823
  postMessage
3770
3824
  };
3771
3825
  }
3772
- async function handleBirdCreateSubmission(view, webClient, defaultTimezone) {
3826
+ async function handleBirdCreateSubmission(view, webClient) {
3773
3827
  try {
3774
3828
  const stateValues = view["state"]?.["values"] ?? {};
3775
3829
  const name = stateValues["bird_name"]?.["name_input"]?.["value"] ?? "";
@@ -3781,8 +3835,8 @@ async function handleBirdCreateSubmission(view, webClient, defaultTimezone) {
3781
3835
  logger.warn("bird_create submission missing required fields");
3782
3836
  return;
3783
3837
  }
3784
- const { createCronJob, setCronJobEnabled } = await import("./db-C9ESgb0d.mjs").then((n) => n.t);
3785
- const bird = createCronJob(name, schedule, prompt, channelId || void 0, "default", defaultTimezone);
3838
+ const { createCronJob, setCronJobEnabled } = await import("./db-Da2zSpkY.mjs").then((n) => n.t);
3839
+ const bird = createCronJob(name, schedule, prompt, channelId || void 0, "default");
3786
3840
  if (enabledValue !== "enabled") setCronJobEnabled(bird.uid, false);
3787
3841
  await webClient.chat.postMessage({
3788
3842
  channel: channelId || "general",
@@ -3795,7 +3849,7 @@ async function handleBirdCreateSubmission(view, webClient, defaultTimezone) {
3795
3849
  }
3796
3850
  async function handleSessionRetry(sessionUid, channelId, userId, config, handler) {
3797
3851
  try {
3798
- const { getSession, getLastInboundMessage } = await import("./db-C9ESgb0d.mjs").then((n) => n.t);
3852
+ const { getSession, getLastInboundMessage } = await import("./db-Da2zSpkY.mjs").then((n) => n.t);
3799
3853
  const session = getSession(sessionUid);
3800
3854
  if (!session) {
3801
3855
  logger.warn(`retry: session ${sessionUid} not found`);
@@ -3872,6 +3926,7 @@ async function startDaemon(options) {
3872
3926
  const configDir = dirname(configPath);
3873
3927
  const envPath = resolve(configDir, ".env");
3874
3928
  openDatabase(resolveDbPath(options.flags.db));
3929
+ clearBrowserLock();
3875
3930
  startWorker(controller.signal);
3876
3931
  loadDotEnv(envPath);
3877
3932
  let currentConfig = loadConfig(configPath);
@@ -4102,7 +4157,6 @@ ${c("dim", "options:")}
4102
4157
  ${c("yellow", "--agent")} <id> target agent id
4103
4158
  ${c("yellow", "--schedule")} <expr> cron schedule expression
4104
4159
  ${c("yellow", "--prompt")} <text> prompt text
4105
- ${c("yellow", "--timezone")} <tz> IANA timezone (default: UTC)
4106
4160
  ${c("yellow", "--active-hours")} <range> restrict runs to a time window (e.g. "09:00-17:00")
4107
4161
  ${c("yellow", "--limit")} <n> number of flights to show (default: 10)
4108
4162
  ${c("yellow", "--json")} output as JSON (with list, flights)
@@ -4150,7 +4204,6 @@ function handleBirds(argv) {
4150
4204
  agent: { type: "string" },
4151
4205
  schedule: { type: "string" },
4152
4206
  prompt: { type: "string" },
4153
- timezone: { type: "string" },
4154
4207
  "active-hours": { type: "string" },
4155
4208
  limit: { type: "string" },
4156
4209
  json: {
@@ -4224,7 +4277,7 @@ function handleBirds(argv) {
4224
4277
  activeStart = parsed.start;
4225
4278
  activeEnd = parsed.end;
4226
4279
  }
4227
- const job = createCronJob(deriveBirdName(prompt), schedule, prompt, values.channel, values.agent, values.timezone, activeStart, activeEnd);
4280
+ const job = createCronJob(deriveBirdName(prompt), schedule, prompt, values.channel, values.agent, activeStart, activeEnd);
4228
4281
  logger.success(`bird ${shortUid(job.uid)} created: "${schedule}"`);
4229
4282
  process.stderr.write(c("dim", ` hint: run 'browserbird birds fly ${shortUid(job.uid)}' to trigger it now`) + "\n");
4230
4283
  break;
@@ -4232,7 +4285,7 @@ function handleBirds(argv) {
4232
4285
  case "edit": {
4233
4286
  const uidPrefix = positionals[0];
4234
4287
  if (!uidPrefix) {
4235
- logger.error("usage: browserbird birds edit <uid> [--schedule <expr>] [--prompt <text>] [--channel <id>] [--agent <id>] [--timezone <tz>] [--active-hours <range>]");
4288
+ logger.error("usage: browserbird birds edit <uid> [--schedule <expr>] [--prompt <text>] [--channel <id>] [--agent <id>] [--active-hours <range>]");
4236
4289
  process.exitCode = 1;
4237
4290
  return;
4238
4291
  }
@@ -4242,7 +4295,6 @@ function handleBirds(argv) {
4242
4295
  const agent = values.agent;
4243
4296
  const schedule = values.schedule;
4244
4297
  const prompt = values.prompt;
4245
- const timezone = values.timezone;
4246
4298
  const editActiveHoursRaw = values["active-hours"];
4247
4299
  let editActiveStart;
4248
4300
  let editActiveEnd;
@@ -4259,8 +4311,8 @@ function handleBirds(argv) {
4259
4311
  editActiveStart = parsed.start;
4260
4312
  editActiveEnd = parsed.end;
4261
4313
  }
4262
- if (!schedule && !prompt && !channel && !agent && !timezone && editActiveStart === void 0) {
4263
- logger.error("provide at least one of: --schedule, --prompt, --channel, --agent, --timezone, --active-hours");
4314
+ if (!schedule && !prompt && !channel && !agent && editActiveStart === void 0) {
4315
+ logger.error("provide at least one of: --schedule, --prompt, --channel, --agent, --active-hours");
4264
4316
  process.exitCode = 1;
4265
4317
  return;
4266
4318
  }
@@ -4270,7 +4322,6 @@ function handleBirds(argv) {
4270
4322
  name: prompt ? deriveBirdName(prompt) : void 0,
4271
4323
  targetChannelId: channel !== void 0 ? channel || null : void 0,
4272
4324
  agentId: agent,
4273
- timezone,
4274
4325
  activeHoursStart: editActiveStart,
4275
4326
  activeHoursEnd: editActiveEnd
4276
4327
  })) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owloops/browserbird",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "description": "Self-hosted AI agent for Slack with a real browser, a scheduler, and a web dashboard",
5
5
  "type": "module",
6
6
  "bin": {