@sideboard-ai/core 0.1.130 → 0.1.132

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.cjs CHANGED
@@ -6780,11 +6780,40 @@ function humanizeAgentFailDetail(detail) {
6780
6780
  }
6781
6781
  function turnFailChatText(opts) {
6782
6782
  const chat = opts.assistantText.trim();
6783
- if (chat) return chat;
6784
- if (opts.exitCode === 0) return "";
6783
+ if (opts.exitCode === 0) return chat;
6785
6784
  const detail = opts.detail.trim();
6786
- if (detail) return humanizeAgentFailDetail(detail);
6787
- return formatTurnExitError(opts.exitCode ?? 1, "");
6785
+ const fail4 = detail ? humanizeAgentFailDetail(detail) : formatTurnExitError(opts.exitCode ?? 1, "");
6786
+ if (!chat) return fail4;
6787
+ const failCore = fail4.replace(/^exit\s*\d+:\s*/i, "").trim();
6788
+ if (looksLikeAgentFailureMessage(chat) || failCore && chat.includes(failCore) || chat.includes(fail4)) {
6789
+ return chat;
6790
+ }
6791
+ return `${chat}
6792
+
6793
+ ${fail4}`;
6794
+ }
6795
+ function shouldFeedErrorBackToAgent(opts) {
6796
+ const detail = opts.detail.trim();
6797
+ const chat = (opts.assistantText ?? "").trim();
6798
+ if (looksLikeAgentFailureMessage(detail) || looksLikeAgentFailureMessage(chat)) {
6799
+ return false;
6800
+ }
6801
+ if (looksLikeInvalidAgentSession(detail) || looksLikeV8Oom(detail) || looksLikeRetryableRunnerCrash(detail)) {
6802
+ return true;
6803
+ }
6804
+ if (/without details|segfault|sig(?:segv|abrt|ill)|fatal error/i.test(detail)) {
6805
+ return true;
6806
+ }
6807
+ return !chat && (opts.partsCount ?? 0) === 0;
6808
+ }
6809
+ function formatAgentErrorContinuePrompt(detail) {
6810
+ const fail4 = humanizeAgentFailDetail(detail.trim()) || "the agent process exited without details";
6811
+ return [
6812
+ "The previous agent process ended before it finished. Error:",
6813
+ fail4,
6814
+ "",
6815
+ "Continue from where you left off. Use the error to recover \u2014 do not restart the whole task unless the error requires it. If you cannot continue, say why."
6816
+ ].join("\n");
6788
6817
  }
6789
6818
  function formatTurnExitError(exitCode, stderrSummary) {
6790
6819
  const code = exitCode ?? 1;
@@ -6814,6 +6843,8 @@ var init_error_detail = __esm({
6814
6843
 
6815
6844
  // src/brightsy/config.ts
6816
6845
  function brightsyConfigPath() {
6846
+ const override = process.env.BRIGHTSY_CONFIG?.trim();
6847
+ if (override) return override;
6817
6848
  return (0, import_node_path22.join)((0, import_node_os6.homedir)(), ".brightsy", "config.json");
6818
6849
  }
6819
6850
  function loadBrightsyConfig() {
@@ -6843,6 +6874,117 @@ var init_config = __esm({
6843
6874
  }
6844
6875
  });
6845
6876
 
6877
+ // src/http/fetch.ts
6878
+ function setHttpFetchImpl(fetchImpl) {
6879
+ injected = fetchImpl;
6880
+ }
6881
+ function formatFetchError(err, url) {
6882
+ if (!(err instanceof Error)) return `${String(err)} (${url})`;
6883
+ const cause = err.cause;
6884
+ let detail = "";
6885
+ if (cause instanceof Error) {
6886
+ const code = typeof cause.code === "string" ? cause.code : void 0;
6887
+ detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
6888
+ } else if (cause != null) {
6889
+ detail = ` [${String(cause)}]`;
6890
+ }
6891
+ return `${err.message}${detail} (${url})`;
6892
+ }
6893
+ async function httpFetch(input, init) {
6894
+ const url = typeof input === "string" ? input : input.href;
6895
+ const fn = injected ?? globalThis.fetch.bind(globalThis);
6896
+ try {
6897
+ return await fn(url, init);
6898
+ } catch (err) {
6899
+ throw new Error(formatFetchError(err, url));
6900
+ }
6901
+ }
6902
+ var injected;
6903
+ var init_fetch = __esm({
6904
+ "src/http/fetch.ts"() {
6905
+ "use strict";
6906
+ injected = null;
6907
+ }
6908
+ });
6909
+
6910
+ // src/brightsy/oauth.ts
6911
+ var oauth_exports = {};
6912
+ __export(oauth_exports, {
6913
+ brightsyAccessTokenNeedsRefresh: () => brightsyAccessTokenNeedsRefresh,
6914
+ ensureBrightsyLocalConfigFresh: () => ensureBrightsyLocalConfigFresh,
6915
+ refreshBrightsyAccessToken: () => refreshBrightsyAccessToken
6916
+ });
6917
+ function brightsyAccessTokenNeedsRefresh(expiresAt, now = Date.now()) {
6918
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= 0) {
6919
+ return true;
6920
+ }
6921
+ return now >= expiresAt - REFRESH_SKEW_MS;
6922
+ }
6923
+ function applyGrant(current, grant) {
6924
+ return {
6925
+ access_token: grant.access_token,
6926
+ refresh_token: grant.refresh_token || current.refresh_token,
6927
+ expires_at: grant.expires_at ?? current.expires_at
6928
+ };
6929
+ }
6930
+ async function refreshBrightsyAccessToken(opts) {
6931
+ const endpoint = (opts.endpoint || "https://brightsy.ai").replace(/\/$/, "");
6932
+ const url = `${endpoint}/oauth/token`;
6933
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
6934
+ let res;
6935
+ try {
6936
+ res = await fetchImpl(url, {
6937
+ method: "POST",
6938
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6939
+ body: new URLSearchParams({
6940
+ grant_type: "refresh_token",
6941
+ refresh_token: opts.refreshToken,
6942
+ client_id: opts.clientId || "brightsy-cli"
6943
+ })
6944
+ });
6945
+ } catch (err) {
6946
+ throw new Error(formatFetchError(err, url));
6947
+ }
6948
+ if (!res.ok) return null;
6949
+ const data = await res.json();
6950
+ if (!data.access_token) return null;
6951
+ return {
6952
+ access_token: data.access_token,
6953
+ refresh_token: data.refresh_token,
6954
+ expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
6955
+ };
6956
+ }
6957
+ async function ensureBrightsyLocalConfigFresh(opts) {
6958
+ let cfg;
6959
+ try {
6960
+ cfg = loadBrightsyConfig();
6961
+ } catch {
6962
+ return null;
6963
+ }
6964
+ if (!cfg.refresh_token || !brightsyAccessTokenNeedsRefresh(cfg.expires_at)) {
6965
+ return cfg;
6966
+ }
6967
+ const grant = await refreshBrightsyAccessToken({
6968
+ endpoint: cfg.endpoint || "https://brightsy.ai",
6969
+ refreshToken: cfg.refresh_token,
6970
+ clientId: cfg.oauth_client_id,
6971
+ fetchImpl: opts?.fetchImpl
6972
+ });
6973
+ if (!grant) return cfg;
6974
+ const next = { ...cfg, ...applyGrant(cfg, grant) };
6975
+ saveBrightsyConfig(next);
6976
+ return next;
6977
+ }
6978
+ var REFRESH_SKEW_MS;
6979
+ var init_oauth = __esm({
6980
+ "src/brightsy/oauth.ts"() {
6981
+ "use strict";
6982
+ init_fetch();
6983
+ init_config();
6984
+ REFRESH_SKEW_MS = 6e4;
6985
+ }
6986
+ });
6987
+
6846
6988
  // src/brightsy/accounts.ts
6847
6989
  async function loadConnectedTeams() {
6848
6990
  const { listConnectedBrightsyTeams: listConnectedBrightsyTeams2 } = await Promise.resolve().then(() => (init_connected_teams(), connected_teams_exports));
@@ -6873,6 +7015,10 @@ async function listBrightsyAccounts() {
6873
7015
  }
6874
7016
  async function getBrightsySession() {
6875
7017
  try {
7018
+ const { ensureBrightsyLocalConfigFresh: ensureBrightsyLocalConfigFresh2 } = await Promise.resolve().then(() => (init_oauth(), oauth_exports));
7019
+ const { ensureConnectedBrightsyTeamTokens: ensureConnectedBrightsyTeamTokens2 } = await Promise.resolve().then(() => (init_connected_teams(), connected_teams_exports));
7020
+ await ensureBrightsyLocalConfigFresh2();
7021
+ await ensureConnectedBrightsyTeamTokens2();
6876
7022
  loadBrightsyConfig();
6877
7023
  const data = await runBrightsyTeamsJson([]);
6878
7024
  const accounts = Array.isArray(data.teams) ? data.teams : [];
@@ -6993,7 +7139,6 @@ function applyConnectedTeamToCli(team) {
6993
7139
  }
6994
7140
  async function refreshTeamToken(team) {
6995
7141
  if (!team.refresh_token) return team;
6996
- const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
6997
7142
  const cfg = (() => {
6998
7143
  try {
6999
7144
  return loadBrightsyConfig();
@@ -7001,39 +7146,34 @@ async function refreshTeamToken(team) {
7001
7146
  return null;
7002
7147
  }
7003
7148
  })();
7004
- const clientId = cfg?.oauth_client_id || "brightsy-cli";
7005
- const res = await fetch(`${endpoint}/oauth/token`, {
7006
- method: "POST",
7007
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
7008
- body: new URLSearchParams({
7009
- grant_type: "refresh_token",
7010
- refresh_token: team.refresh_token,
7011
- client_id: clientId
7012
- })
7149
+ const grant = await refreshBrightsyAccessToken({
7150
+ endpoint: team.endpoint || "https://brightsy.ai",
7151
+ refreshToken: team.refresh_token,
7152
+ clientId: cfg?.oauth_client_id
7013
7153
  });
7014
- if (!res.ok) return team;
7015
- const data = await res.json();
7016
- if (!data.access_token) return team;
7154
+ if (!grant) return team;
7017
7155
  return {
7018
7156
  ...team,
7019
- access_token: data.access_token,
7020
- refresh_token: data.refresh_token || team.refresh_token,
7021
- expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : team.expires_at
7157
+ access_token: grant.access_token,
7158
+ refresh_token: grant.refresh_token || team.refresh_token,
7159
+ expires_at: grant.expires_at ?? team.expires_at
7022
7160
  };
7023
7161
  }
7024
7162
  async function ensureConnectedBrightsyTeamTokens() {
7163
+ await ensureBrightsyLocalConfigFresh().catch(() => null);
7025
7164
  const teams = readStore4();
7026
7165
  if (teams.length === 0) return [];
7027
7166
  const next = [];
7028
7167
  let changed = false;
7029
7168
  for (const team of teams) {
7030
- const expired = typeof team.expires_at === "number" && Date.now() >= team.expires_at - 6e4;
7031
- if (!expired) {
7169
+ if (!team.refresh_token || !brightsyAccessTokenNeedsRefresh(team.expires_at)) {
7032
7170
  next.push(team);
7033
7171
  continue;
7034
7172
  }
7035
7173
  const refreshed = await refreshTeamToken(team);
7036
- if (refreshed.access_token !== team.access_token) changed = true;
7174
+ if (refreshed.access_token !== team.access_token || refreshed.refresh_token !== team.refresh_token || refreshed.expires_at !== team.expires_at) {
7175
+ changed = true;
7176
+ }
7037
7177
  next.push(refreshed);
7038
7178
  }
7039
7179
  if (changed) writeStore3(next);
@@ -7128,6 +7268,7 @@ var init_connected_teams = __esm({
7128
7268
  init_paths();
7129
7269
  init_accounts();
7130
7270
  init_config();
7271
+ init_oauth();
7131
7272
  }
7132
7273
  });
7133
7274
 
@@ -8532,6 +8673,7 @@ function teamEnv(team) {
8532
8673
  const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
8533
8674
  return {
8534
8675
  BRIGHTSY_API_TOKEN: team.access_token,
8676
+ ...team.refresh_token ? { BRIGHTSY_REFRESH_TOKEN: team.refresh_token } : {},
8535
8677
  BRIGHTSY_ACCOUNT_ID: team.id,
8536
8678
  BRIGHTSY_API_URL: endpoint
8537
8679
  };
@@ -8818,10 +8960,15 @@ var init_types = __esm({
8818
8960
 
8819
8961
  // src/agents/claude.ts
8820
8962
  async function loadMcpServers() {
8963
+ if (mcpListCache && Date.now() - mcpListCache.at < 3e4) {
8964
+ return mcpListCache.servers;
8965
+ }
8821
8966
  const claude = resolveClaudeExecutable();
8822
8967
  const mcpText = await run(claude, ["mcp", "list"], { reject: false });
8823
- return parseMcpList(`${mcpText.stdout}
8968
+ const servers = parseMcpList(`${mcpText.stdout}
8824
8969
  ${mcpText.stderr}`);
8970
+ mcpListCache = { at: Date.now(), servers };
8971
+ return servers;
8825
8972
  }
8826
8973
  function usageFromClaude(usage) {
8827
8974
  if (!usage) return null;
@@ -9035,7 +9182,7 @@ function parseIssuesJson(raw) {
9035
9182
  }
9036
9183
  return [];
9037
9184
  }
9038
- var import_node_fs25, BASE_ALLOWED_TOOLS, CLAUDE_PRINT_BG_WAIT_CEILING_MS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
9185
+ var import_node_fs25, BASE_ALLOWED_TOOLS, CLAUDE_PRINT_BG_WAIT_CEILING_MS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, mcpListCache, claudeAdapter;
9039
9186
  var init_claude = __esm({
9040
9187
  "src/agents/claude.ts"() {
9041
9188
  "use strict";
@@ -9073,6 +9220,7 @@ var init_claude = __esm({
9073
9220
  "Skill(claude-in-chrome)"
9074
9221
  ];
9075
9222
  CLAUDE_PROMPT_ARG_MAX = 2e5;
9223
+ mcpListCache = null;
9076
9224
  claudeAdapter = {
9077
9225
  kind: "claude",
9078
9226
  async detect() {
@@ -10451,10 +10599,13 @@ var init_opencode = __esm({
10451
10599
  const part = obj.part;
10452
10600
  const id = part?.tool_use_id ?? part?.id ?? obj.tool_use_id ?? obj.id;
10453
10601
  if (!id) return null;
10602
+ const flagged = obj.isError === true || obj.is_error === true;
10603
+ const errText2 = formatUnknownDetail(obj.error);
10454
10604
  return {
10455
10605
  type: "tool_result",
10456
10606
  id,
10457
- content: part?.output ?? part?.content ?? obj.output ?? obj.content
10607
+ content: part?.output ?? part?.content ?? obj.output ?? obj.content ?? (errText2 || void 0),
10608
+ isError: flagged || Boolean(errText2)
10458
10609
  };
10459
10610
  }
10460
10611
  if (obj.type === "step_finish" || obj.type === "step-finish") {
@@ -12929,7 +13080,48 @@ function isMergedPrState(prUrl, prState) {
12929
13080
  if (!prUrl?.trim()) return false;
12930
13081
  return (prState ?? "").trim().toUpperCase() === "MERGED";
12931
13082
  }
12932
- function groupHomeBoardWorktrees(threads) {
13083
+ function groupCreatedAt(group) {
13084
+ let min = group[0]?.createdAt ?? "";
13085
+ for (const t of group) {
13086
+ if (t.createdAt && t.createdAt < min) min = t.createdAt;
13087
+ }
13088
+ return min;
13089
+ }
13090
+ function groupUpdatedAt(group) {
13091
+ let max = group[0]?.updatedAt ?? "";
13092
+ for (const t of group) {
13093
+ if (t.updatedAt && t.updatedAt > max) max = t.updatedAt;
13094
+ }
13095
+ return max;
13096
+ }
13097
+ function sortThreadsInWorktree(list) {
13098
+ return [...list].sort((a, b) => {
13099
+ const created = a.createdAt.localeCompare(b.createdAt);
13100
+ if (created !== 0) return created;
13101
+ return a.id.localeCompare(b.id);
13102
+ });
13103
+ }
13104
+ function compareWorktreeGroups(a, b, mode, labelOf) {
13105
+ if (mode === "name") {
13106
+ const named = (labelOf?.(a) ?? a[0]?.id ?? "").localeCompare(
13107
+ labelOf?.(b) ?? b[0]?.id ?? "",
13108
+ void 0,
13109
+ { sensitivity: "base" }
13110
+ );
13111
+ if (named !== 0) return named;
13112
+ } else if (mode === "activity") {
13113
+ const activity = groupUpdatedAt(b).localeCompare(groupUpdatedAt(a));
13114
+ if (activity !== 0) return activity;
13115
+ } else {
13116
+ const created = groupCreatedAt(b).localeCompare(groupCreatedAt(a));
13117
+ if (created !== 0) return created;
13118
+ }
13119
+ return (a[0]?.id ?? "").localeCompare(b[0]?.id ?? "");
13120
+ }
13121
+ function defaultWorktreeGroupLabel(group) {
13122
+ return worktreeDisplayLabelForGroup(group);
13123
+ }
13124
+ function groupHomeBoardWorktrees(threads, sort = DEFAULT_WORKTREE_SORT, labelOf = defaultWorktreeGroupLabel) {
12933
13125
  const map = /* @__PURE__ */ new Map();
12934
13126
  for (const t of threads) {
12935
13127
  const key = normalizeWorktreePath(t.worktreePath);
@@ -12937,7 +13129,7 @@ function groupHomeBoardWorktrees(threads) {
12937
13129
  list.push(t);
12938
13130
  map.set(key, list);
12939
13131
  }
12940
- return [...map.values()].map((list) => list.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))).sort((a, b) => (b[0]?.updatedAt ?? "").localeCompare(a[0]?.updatedAt ?? ""));
13132
+ return [...map.values()].map((list) => sortThreadsInWorktree(list)).sort((a, b) => compareWorktreeGroups(a, b, sort, labelOf));
12941
13133
  }
12942
13134
  function classifyWorktreeColumn(group) {
12943
13135
  if (group.some((t) => isMergedPrState(t.prUrl, t.prState))) return "done";
@@ -13243,8 +13435,7 @@ function assembleHomeBoard(input) {
13243
13435
  const kind = input.kind ?? "all";
13244
13436
  const limit = Math.max(1, input.limit ?? BOARD_PAGE_SIZE);
13245
13437
  const wsName = input.workspaceName ?? (() => "");
13246
- const byUpdated = (a, b) => b.updatedAt.localeCompare(a.updatedAt);
13247
- const live = input.threads.filter((t) => t.status !== "archived" && isHomeBoardThread(t)).sort(byUpdated);
13438
+ const live = input.threads.filter((t) => t.status !== "archived" && isHomeBoardThread(t));
13248
13439
  const columns = emptyColumns();
13249
13440
  const hidden = emptyHidden();
13250
13441
  const totals = {
@@ -13299,7 +13490,7 @@ function assembleHomeBoard(input) {
13299
13490
  }
13300
13491
  return { columns, hidden, totals };
13301
13492
  }
13302
- var GLOBAL_WORKSPACE_ID2, BOARD_COLUMN_DEFS, HOME_BOARD_CACHE_TTL_MS, DEFAULTISH_BRANCH, BOARD_PAGE_SIZE, HOME_BOARD_AGENT_HINT;
13493
+ var GLOBAL_WORKSPACE_ID2, BOARD_COLUMN_DEFS, HOME_BOARD_CACHE_TTL_MS, DEFAULT_WORKTREE_SORT, DEFAULTISH_BRANCH, BOARD_PAGE_SIZE, HOME_BOARD_AGENT_HINT;
13303
13494
  var init_home_board = __esm({
13304
13495
  "src/board/home-board.ts"() {
13305
13496
  "use strict";
@@ -13312,6 +13503,7 @@ var init_home_board = __esm({
13312
13503
  { id: "done", title: "Merged" }
13313
13504
  ];
13314
13505
  HOME_BOARD_CACHE_TTL_MS = 15 * 60 * 1e3;
13506
+ DEFAULT_WORKTREE_SORT = "created";
13315
13507
  DEFAULTISH_BRANCH = /^(main|master|develop|development|trunk|default|head)$/;
13316
13508
  BOARD_PAGE_SIZE = 40;
13317
13509
  HOME_BOARD_AGENT_HINT = "Home is a Kanban of worktrees (one card per checkout; sibling chat tabs nest as inner cards). create_thread reuses a live worktree for the same ticket, PR, or named branch \u2014 do not recreate it. Columns are the path to merge: New (no PR) \u2192 Draft (draft PR) \u2192 Review (open PR) \u2192 Merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats stay in the sidebar. Do not invent status.";
@@ -16400,6 +16592,11 @@ var init_orchestrator = __esm({
16400
16592
  turnBaselines = /* @__PURE__ */ new Map();
16401
16593
  /** Timers for orchestration session-quota auto-resume. */
16402
16594
  quotaResumeTimers = /* @__PURE__ */ new Map();
16595
+ /**
16596
+ * Threads that already got a crash-continue turn. Cleared on a successful
16597
+ * finish or a user send so a later crash can recover again.
16598
+ */
16599
+ crashContinued = /* @__PURE__ */ new Set();
16403
16600
  maxConcurrent;
16404
16601
  runningCount = 0;
16405
16602
  constructor(opts) {
@@ -16613,6 +16810,29 @@ var init_orchestrator = __esm({
16613
16810
  );
16614
16811
  }
16615
16812
  }
16813
+ /**
16814
+ * Cursor-style recovery: after a runner crash, feed the error back as the
16815
+ * next prompt so the same agent can continue instead of sitting on lastError.
16816
+ */
16817
+ maybeEnqueueCrashContinue(threadId, opts) {
16818
+ if (this.crashContinued.has(threadId)) return;
16819
+ if (this.haltDrain.has(threadId)) return;
16820
+ if (!shouldFeedErrorBackToAgent({
16821
+ detail: opts.detail,
16822
+ assistantText: opts.assistantText,
16823
+ partsCount: opts.partsCount
16824
+ })) {
16825
+ return;
16826
+ }
16827
+ const thread = readThread(threadId);
16828
+ if (!thread || thread.status === "archived") return;
16829
+ this.crashContinued.add(threadId);
16830
+ const prompt = formatAgentErrorContinuePrompt(opts.detail);
16831
+ const queue = [prompt, ...thread.queue];
16832
+ updateThread(threadId, { queue });
16833
+ this.emit({ type: "queue_changed", threadId, queue });
16834
+ this.haltDrain.delete(threadId);
16835
+ }
16616
16836
  getThreads(includeArchived = false) {
16617
16837
  return listThreads({ includeArchived });
16618
16838
  }
@@ -16712,15 +16932,20 @@ var init_orchestrator = __esm({
16712
16932
  throw new Error(`Thread is archived: ${thread.id}`);
16713
16933
  }
16714
16934
  const queue = [...current.queue, prompt];
16935
+ this.crashContinued.delete(thread.id);
16715
16936
  this.haltDrain.delete(thread.id);
16716
- const patch = { queue, status: "queued" };
16937
+ const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
16938
+ const patch = { queue };
16939
+ if (!inFlight) patch.status = "queued";
16717
16940
  const pid = current.agentPid;
16718
16941
  if (typeof pid === "number" && pid > 0 && !isPidAlive(pid)) {
16719
16942
  patch.agentPid = null;
16720
16943
  }
16721
16944
  updateThread(thread.id, patch);
16722
16945
  this.emit({ type: "queue_changed", threadId: thread.id, queue });
16723
- this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
16946
+ if (!inFlight) {
16947
+ this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
16948
+ }
16724
16949
  if (thisProcessShouldDrainAgentQueues()) {
16725
16950
  void this.drainQueue(thread.id);
16726
16951
  }
@@ -17041,7 +17266,7 @@ var init_orchestrator = __esm({
17041
17266
  let parts = result.parts;
17042
17267
  let usage = result.usage ?? void 0;
17043
17268
  let exitCode = result.exitCode;
17044
- if (this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
17269
+ if (!this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
17045
17270
  const sessionId = result.sessionId || this.requireThread(threadId).sessionId || "";
17046
17271
  if (sessionId) {
17047
17272
  const { recoverFinishedCursorRun: recoverFinishedCursorRun2 } = await Promise.resolve().then(() => (init_cursor_recover(), cursor_recover_exports));
@@ -17062,7 +17287,7 @@ var init_orchestrator = __esm({
17062
17287
  }
17063
17288
  let lastStderr = summarizeTurnStderr(stderrTail);
17064
17289
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
17065
- if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent !== "brightsy" && shouldRetryFailedAgentTurn(detail, {
17290
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && shouldRetryFailedAgentTurn(detail, {
17066
17291
  hasSession: Boolean(this.requireThread(threadId).sessionId)
17067
17292
  })) {
17068
17293
  updateThread(threadId, { sessionId: null });
@@ -17158,7 +17383,11 @@ var init_orchestrator = __esm({
17158
17383
  )) {
17159
17384
  updateThread(threadId, { sessionId: null });
17160
17385
  }
17161
- await syncThreadBranchFromGit(threadId);
17386
+ if (this.stoppedTurns.has(threadId)) {
17387
+ void syncThreadBranchFromGit(threadId).catch(() => void 0);
17388
+ } else {
17389
+ await syncThreadBranchFromGit(threadId);
17390
+ }
17162
17391
  if (this.stoppedTurns.has(threadId)) {
17163
17392
  const stopped = writeLiveStatus(threadId, "stopped");
17164
17393
  if (stopped?.status === "stopped") {
@@ -17182,9 +17411,16 @@ var init_orchestrator = __esm({
17182
17411
  });
17183
17412
  }
17184
17413
  this.emit({ type: "turn_finished", threadId, exitCode });
17185
- if (exitCode !== 0) {
17414
+ if (exitCode === 0) {
17415
+ this.crashContinued.delete(threadId);
17416
+ } else {
17186
17417
  const blob = [chatText, detail].filter(Boolean).join("\n");
17187
17418
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
17419
+ this.maybeEnqueueCrashContinue(threadId, {
17420
+ detail: detail || failDetail,
17421
+ assistantText: chatText,
17422
+ partsCount: parts.length
17423
+ });
17188
17424
  }
17189
17425
  }
17190
17426
  } catch (err) {
@@ -17204,6 +17440,11 @@ var init_orchestrator = __esm({
17204
17440
  }
17205
17441
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
17206
17442
  void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
17443
+ this.maybeEnqueueCrashContinue(threadId, {
17444
+ detail: message,
17445
+ assistantText: "",
17446
+ partsCount: 0
17447
+ });
17207
17448
  }
17208
17449
  } finally {
17209
17450
  this.startingTurns.delete(threadId);
@@ -18305,6 +18546,7 @@ __export(index_exports, {
18305
18546
  CONTEXT_MIN_MESSAGES: () => CONTEXT_MIN_MESSAGES,
18306
18547
  CONVENTION_SETUP_RELPATHS: () => CONVENTION_SETUP_RELPATHS,
18307
18548
  COORDINATOR_TOOL_PLAYBOOK: () => COORDINATOR_TOOL_PLAYBOOK,
18549
+ DEFAULT_WORKTREE_SORT: () => DEFAULT_WORKTREE_SORT,
18308
18550
  FAMOUS_SOCCER_TEAMS: () => FAMOUS_SOCCER_TEAMS,
18309
18551
  GITHUB_GIT_AUTH_MODES: () => GITHUB_GIT_AUTH_MODES,
18310
18552
  GLOBAL_WORKSPACE_ID: () => GLOBAL_WORKSPACE_ID,
@@ -18386,6 +18628,7 @@ __export(index_exports, {
18386
18628
  autoRenameBranchEnabled: () => autoRenameBranchEnabled,
18387
18629
  autoRunAfterSetupEnabled: () => autoRunAfterSetupEnabled,
18388
18630
  branchDisplayLabel: () => branchDisplayLabel,
18631
+ brightsyAccessTokenNeedsRefresh: () => brightsyAccessTokenNeedsRefresh,
18389
18632
  brightsyAdapter: () => brightsyAdapter,
18390
18633
  brightsyCloudConnectAgent: () => brightsyCloudConnectAgent,
18391
18634
  brightsyCloudConnectEnabled: () => brightsyCloudConnectEnabled,
@@ -18472,7 +18715,9 @@ __export(index_exports, {
18472
18715
  enrichPathWithNpmGlobalBin: () => enrichPathWithNpmGlobalBin,
18473
18716
  enrichWorkspacesWithGithub: () => enrichWorkspacesWithGithub,
18474
18717
  ensureAgentPath: () => ensureAgentPath,
18718
+ ensureBrightsyLocalConfigFresh: () => ensureBrightsyLocalConfigFresh,
18475
18719
  ensureCloudCoordinator: () => ensureCloudCoordinator,
18720
+ ensureConnectedBrightsyTeamTokens: () => ensureConnectedBrightsyTeamTokens,
18476
18721
  ensureGhPreferOrigin: () => ensureGhPreferOrigin,
18477
18722
  ensureGlobalCoordinatorCwd: () => ensureGlobalCoordinatorCwd,
18478
18723
  ensureReviewRequestFile: () => ensureReviewRequestFile,
@@ -18735,6 +18980,7 @@ __export(index_exports, {
18735
18980
  readWorktreeInclude: () => readWorktreeInclude,
18736
18981
  recordScheduleRun: () => recordScheduleRun,
18737
18982
  recordSlackOutboundWatch: () => recordSlackOutboundWatch,
18983
+ refreshBrightsyAccessToken: () => refreshBrightsyAccessToken,
18738
18984
  refreshGitHubAuth: () => refreshGitHubAuth,
18739
18985
  registerPackagedUserMcpClients: () => registerPackagedUserMcpClients,
18740
18986
  releaseCaffeinateHoldForThread: () => releaseCaffeinateHoldForThread,
@@ -18965,37 +19211,14 @@ async function refreshGitHubAuth() {
18965
19211
  return getGitHubStatus();
18966
19212
  }
18967
19213
 
18968
- // src/http/fetch.ts
18969
- var injected = null;
18970
- function setHttpFetchImpl(fetchImpl) {
18971
- injected = fetchImpl;
18972
- }
18973
- function formatFetchError(err, url) {
18974
- if (!(err instanceof Error)) return `${String(err)} (${url})`;
18975
- const cause = err.cause;
18976
- let detail = "";
18977
- if (cause instanceof Error) {
18978
- const code = typeof cause.code === "string" ? cause.code : void 0;
18979
- detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
18980
- } else if (cause != null) {
18981
- detail = ` [${String(cause)}]`;
18982
- }
18983
- return `${err.message}${detail} (${url})`;
18984
- }
18985
- async function httpFetch(input, init) {
18986
- const url = typeof input === "string" ? input : input.href;
18987
- const fn = injected ?? globalThis.fetch.bind(globalThis);
18988
- try {
18989
- return await fn(url, init);
18990
- } catch (err) {
18991
- throw new Error(formatFetchError(err, url));
18992
- }
18993
- }
19214
+ // src/integrations/linear.ts
19215
+ init_fetch();
18994
19216
 
18995
19217
  // src/integrations/linear-oauth.ts
18996
19218
  var import_node_crypto9 = require("crypto");
18997
19219
  var import_node_http = require("http");
18998
19220
  init_app_settings();
19221
+ init_fetch();
18999
19222
 
19000
19223
  // src/integrations/linear-app.ts
19001
19224
  var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
@@ -19011,7 +19234,7 @@ var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
19011
19234
  var LINEAR_REVOKE = "https://api.linear.app/oauth/revoke";
19012
19235
  var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
19013
19236
  var LINEAR_OAUTH_SCOPES = "read,write";
19014
- var REFRESH_SKEW_MS = 5 * 6e4;
19237
+ var REFRESH_SKEW_MS2 = 5 * 6e4;
19015
19238
  function linearOAuthCredentials() {
19016
19239
  const settings = loadAppSettings();
19017
19240
  const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
@@ -19122,7 +19345,7 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
19122
19345
  const refresh = settings.integrations.linearRefreshToken?.trim() || "";
19123
19346
  const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
19124
19347
  const apiKey = settings.integrations.linearApiKey?.trim() || "";
19125
- if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS)) {
19348
+ if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
19126
19349
  return access;
19127
19350
  }
19128
19351
  if (refresh) {
@@ -19705,6 +19928,7 @@ async function listIssues(repoPath) {
19705
19928
  }
19706
19929
 
19707
19930
  // src/index.ts
19931
+ init_fetch();
19708
19932
  init_agents();
19709
19933
  init_spawn();
19710
19934
  init_message_parts();
@@ -22278,7 +22502,9 @@ init_home_board();
22278
22502
  init_config();
22279
22503
 
22280
22504
  // src/brightsy/api.ts
22505
+ init_fetch();
22281
22506
  init_config();
22507
+ init_oauth();
22282
22508
  var formatBrightsyFetchError = formatFetchError;
22283
22509
  var BrightsySideboardApi = class {
22284
22510
  cfg;
@@ -22301,7 +22527,7 @@ var BrightsySideboardApi = class {
22301
22527
  return (this.cfg.endpoint || "https://brightsy.ai").replace(/\/$/, "");
22302
22528
  }
22303
22529
  async request(path2, init = {}) {
22304
- await this.refreshIfNeeded();
22530
+ await this.ensureFreshAccessToken();
22305
22531
  const url = `${this.endpoint}${path2}`;
22306
22532
  const headers = {
22307
22533
  Authorization: `Bearer ${this.cfg.access_token}`,
@@ -22324,33 +22550,21 @@ var BrightsySideboardApi = class {
22324
22550
  }
22325
22551
  return await res.json();
22326
22552
  }
22327
- async refreshIfNeeded() {
22328
- const expiresAt = this.cfg.expires_at ?? 0;
22329
- if (!this.cfg.refresh_token || Date.now() < expiresAt - 6e4) return;
22330
- const clientId = this.cfg.oauth_client_id || "brightsy-cli";
22331
- const url = `${this.endpoint}/oauth/token`;
22332
- let res;
22333
- try {
22334
- res = await this.fetchImpl(url, {
22335
- method: "POST",
22336
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
22337
- body: new URLSearchParams({
22338
- grant_type: "refresh_token",
22339
- refresh_token: this.cfg.refresh_token,
22340
- client_id: clientId
22341
- })
22342
- });
22343
- } catch (err) {
22344
- throw new Error(formatBrightsyFetchError(err, url));
22345
- }
22346
- if (!res.ok) return;
22347
- const data = await res.json();
22348
- if (!data.access_token) return;
22349
- this.cfg.access_token = data.access_token;
22350
- if (data.refresh_token) this.cfg.refresh_token = data.refresh_token;
22351
- if (data.expires_in) {
22352
- this.cfg.expires_at = Date.now() + data.expires_in * 1e3;
22553
+ /** Refresh ~/.brightsy when the access token is stale or has no expiry. */
22554
+ async ensureFreshAccessToken() {
22555
+ if (!this.cfg.refresh_token || !brightsyAccessTokenNeedsRefresh(this.cfg.expires_at)) {
22556
+ return;
22353
22557
  }
22558
+ const grant = await refreshBrightsyAccessToken({
22559
+ endpoint: this.endpoint,
22560
+ refreshToken: this.cfg.refresh_token,
22561
+ clientId: this.cfg.oauth_client_id,
22562
+ fetchImpl: this.fetchImpl
22563
+ });
22564
+ if (!grant) return;
22565
+ this.cfg.access_token = grant.access_token;
22566
+ if (grant.refresh_token) this.cfg.refresh_token = grant.refresh_token;
22567
+ if (grant.expires_at) this.cfg.expires_at = grant.expires_at;
22354
22568
  saveBrightsyConfig(this.cfg);
22355
22569
  }
22356
22570
  async getAccess() {
@@ -22604,6 +22818,7 @@ async function runCloudConnect(opts) {
22604
22818
  // src/index.ts
22605
22819
  init_accounts();
22606
22820
  init_connected_teams();
22821
+ init_oauth();
22607
22822
  init_injected_mcp();
22608
22823
 
22609
22824
  // src/agents/user-mcp-config.ts
@@ -24620,6 +24835,7 @@ init_outbound_watch();
24620
24835
  CONTEXT_MIN_MESSAGES,
24621
24836
  CONVENTION_SETUP_RELPATHS,
24622
24837
  COORDINATOR_TOOL_PLAYBOOK,
24838
+ DEFAULT_WORKTREE_SORT,
24623
24839
  FAMOUS_SOCCER_TEAMS,
24624
24840
  GITHUB_GIT_AUTH_MODES,
24625
24841
  GLOBAL_WORKSPACE_ID,
@@ -24701,6 +24917,7 @@ init_outbound_watch();
24701
24917
  autoRenameBranchEnabled,
24702
24918
  autoRunAfterSetupEnabled,
24703
24919
  branchDisplayLabel,
24920
+ brightsyAccessTokenNeedsRefresh,
24704
24921
  brightsyAdapter,
24705
24922
  brightsyCloudConnectAgent,
24706
24923
  brightsyCloudConnectEnabled,
@@ -24787,7 +25004,9 @@ init_outbound_watch();
24787
25004
  enrichPathWithNpmGlobalBin,
24788
25005
  enrichWorkspacesWithGithub,
24789
25006
  ensureAgentPath,
25007
+ ensureBrightsyLocalConfigFresh,
24790
25008
  ensureCloudCoordinator,
25009
+ ensureConnectedBrightsyTeamTokens,
24791
25010
  ensureGhPreferOrigin,
24792
25011
  ensureGlobalCoordinatorCwd,
24793
25012
  ensureReviewRequestFile,
@@ -25050,6 +25269,7 @@ init_outbound_watch();
25050
25269
  readWorktreeInclude,
25051
25270
  recordScheduleRun,
25052
25271
  recordSlackOutboundWatch,
25272
+ refreshBrightsyAccessToken,
25053
25273
  refreshGitHubAuth,
25054
25274
  registerPackagedUserMcpClients,
25055
25275
  releaseCaffeinateHoldForThread,