@sideboard-ai/core 0.1.127 → 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.";
@@ -15868,7 +16060,8 @@ function formatArtifactDirective() {
15868
16060
  "```html",
15869
16061
  "<!DOCTYPE html><html><head><title>Demo</title></head><body><h1>Hi</h1></body></html>",
15870
16062
  "```",
15871
- "2) Or call Sideboard MCP `present_artifact` with title, type (html|svg|markdown), and content \u2014 not both a fence and this tool for the same body.",
16063
+ "2) Or call Sideboard MCP `present_artifact` with title, type (html|svg|markdown|react|log), and content \u2014 not both a fence and this tool for the same body.",
16064
+ " type=log is append-only: same `artifact_id`, `content` = new lines only (plus optional status/phase). Do not resend the full log or wrap it in HTML.",
15872
16065
  "CMS / JSON Schema forms & tables (Brightsy or any schema+schemaUi source):",
15873
16066
  "3) Call Sideboard MCP `present_schema` when the user needs to filter, edit, publish, or persist rows \u2014 including after you already showed a markdown table, if they then ask for an editable table. If they only need to read the data, a markdown table is enough. When you do call it, pass title, mode (table|form), and either:",
15874
16067
  " - datasource=brightsy + resource_id (record type UUID) after fetching types via Brightsy MCP, or",
@@ -15883,7 +16076,7 @@ function formatArtifactDirective() {
15883
16076
  ].join("\n");
15884
16077
  }
15885
16078
  function formatUiReminder() {
15886
- return "Sideboard UI: markdown table is enough to read data; present_schema if they ask to edit/filter (even after markdown); present_files for the file manager. html fence or present_artifact, not both for the same document. ask_user only for a real multiple-choice (not hellos or \u201Cwhat next?\u201D) \u2014 reply in chat. Do not say artifacts/CMS UI are unavailable.";
16079
+ return "Sideboard UI: markdown table is enough to read data; present_schema if they ask to edit/filter (even after markdown); present_files for the file manager. html fence or present_artifact, not both for the same document. type=log appends (same artifact_id, new lines only). ask_user only for a real multiple-choice (not hellos or \u201Cwhat next?\u201D) \u2014 reply in chat. Do not say artifacts/CMS UI are unavailable.";
15887
16080
  }
15888
16081
  function loadAgentInstructions(worktreePath, agent) {
15889
16082
  const candidates = FILES_BY_AGENT[agent] ?? FILES_BY_AGENT.claude;
@@ -16399,6 +16592,11 @@ var init_orchestrator = __esm({
16399
16592
  turnBaselines = /* @__PURE__ */ new Map();
16400
16593
  /** Timers for orchestration session-quota auto-resume. */
16401
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();
16402
16600
  maxConcurrent;
16403
16601
  runningCount = 0;
16404
16602
  constructor(opts) {
@@ -16612,6 +16810,29 @@ var init_orchestrator = __esm({
16612
16810
  );
16613
16811
  }
16614
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
+ }
16615
16836
  getThreads(includeArchived = false) {
16616
16837
  return listThreads({ includeArchived });
16617
16838
  }
@@ -16711,15 +16932,20 @@ var init_orchestrator = __esm({
16711
16932
  throw new Error(`Thread is archived: ${thread.id}`);
16712
16933
  }
16713
16934
  const queue = [...current.queue, prompt];
16935
+ this.crashContinued.delete(thread.id);
16714
16936
  this.haltDrain.delete(thread.id);
16715
- 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";
16716
16940
  const pid = current.agentPid;
16717
16941
  if (typeof pid === "number" && pid > 0 && !isPidAlive(pid)) {
16718
16942
  patch.agentPid = null;
16719
16943
  }
16720
16944
  updateThread(thread.id, patch);
16721
16945
  this.emit({ type: "queue_changed", threadId: thread.id, queue });
16722
- 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
+ }
16723
16949
  if (thisProcessShouldDrainAgentQueues()) {
16724
16950
  void this.drainQueue(thread.id);
16725
16951
  }
@@ -17040,7 +17266,7 @@ var init_orchestrator = __esm({
17040
17266
  let parts = result.parts;
17041
17267
  let usage = result.usage ?? void 0;
17042
17268
  let exitCode = result.exitCode;
17043
- 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) {
17044
17270
  const sessionId = result.sessionId || this.requireThread(threadId).sessionId || "";
17045
17271
  if (sessionId) {
17046
17272
  const { recoverFinishedCursorRun: recoverFinishedCursorRun2 } = await Promise.resolve().then(() => (init_cursor_recover(), cursor_recover_exports));
@@ -17061,7 +17287,7 @@ var init_orchestrator = __esm({
17061
17287
  }
17062
17288
  let lastStderr = summarizeTurnStderr(stderrTail);
17063
17289
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
17064
- 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, {
17065
17291
  hasSession: Boolean(this.requireThread(threadId).sessionId)
17066
17292
  })) {
17067
17293
  updateThread(threadId, { sessionId: null });
@@ -17157,7 +17383,11 @@ var init_orchestrator = __esm({
17157
17383
  )) {
17158
17384
  updateThread(threadId, { sessionId: null });
17159
17385
  }
17160
- await syncThreadBranchFromGit(threadId);
17386
+ if (this.stoppedTurns.has(threadId)) {
17387
+ void syncThreadBranchFromGit(threadId).catch(() => void 0);
17388
+ } else {
17389
+ await syncThreadBranchFromGit(threadId);
17390
+ }
17161
17391
  if (this.stoppedTurns.has(threadId)) {
17162
17392
  const stopped = writeLiveStatus(threadId, "stopped");
17163
17393
  if (stopped?.status === "stopped") {
@@ -17181,9 +17411,16 @@ var init_orchestrator = __esm({
17181
17411
  });
17182
17412
  }
17183
17413
  this.emit({ type: "turn_finished", threadId, exitCode });
17184
- if (exitCode !== 0) {
17414
+ if (exitCode === 0) {
17415
+ this.crashContinued.delete(threadId);
17416
+ } else {
17185
17417
  const blob = [chatText, detail].filter(Boolean).join("\n");
17186
17418
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
17419
+ this.maybeEnqueueCrashContinue(threadId, {
17420
+ detail: detail || failDetail,
17421
+ assistantText: chatText,
17422
+ partsCount: parts.length
17423
+ });
17187
17424
  }
17188
17425
  }
17189
17426
  } catch (err) {
@@ -17203,6 +17440,11 @@ var init_orchestrator = __esm({
17203
17440
  }
17204
17441
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
17205
17442
  void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
17443
+ this.maybeEnqueueCrashContinue(threadId, {
17444
+ detail: message,
17445
+ assistantText: "",
17446
+ partsCount: 0
17447
+ });
17206
17448
  }
17207
17449
  } finally {
17208
17450
  this.startingTurns.delete(threadId);
@@ -18304,6 +18546,7 @@ __export(index_exports, {
18304
18546
  CONTEXT_MIN_MESSAGES: () => CONTEXT_MIN_MESSAGES,
18305
18547
  CONVENTION_SETUP_RELPATHS: () => CONVENTION_SETUP_RELPATHS,
18306
18548
  COORDINATOR_TOOL_PLAYBOOK: () => COORDINATOR_TOOL_PLAYBOOK,
18549
+ DEFAULT_WORKTREE_SORT: () => DEFAULT_WORKTREE_SORT,
18307
18550
  FAMOUS_SOCCER_TEAMS: () => FAMOUS_SOCCER_TEAMS,
18308
18551
  GITHUB_GIT_AUTH_MODES: () => GITHUB_GIT_AUTH_MODES,
18309
18552
  GLOBAL_WORKSPACE_ID: () => GLOBAL_WORKSPACE_ID,
@@ -18385,6 +18628,7 @@ __export(index_exports, {
18385
18628
  autoRenameBranchEnabled: () => autoRenameBranchEnabled,
18386
18629
  autoRunAfterSetupEnabled: () => autoRunAfterSetupEnabled,
18387
18630
  branchDisplayLabel: () => branchDisplayLabel,
18631
+ brightsyAccessTokenNeedsRefresh: () => brightsyAccessTokenNeedsRefresh,
18388
18632
  brightsyAdapter: () => brightsyAdapter,
18389
18633
  brightsyCloudConnectAgent: () => brightsyCloudConnectAgent,
18390
18634
  brightsyCloudConnectEnabled: () => brightsyCloudConnectEnabled,
@@ -18471,7 +18715,9 @@ __export(index_exports, {
18471
18715
  enrichPathWithNpmGlobalBin: () => enrichPathWithNpmGlobalBin,
18472
18716
  enrichWorkspacesWithGithub: () => enrichWorkspacesWithGithub,
18473
18717
  ensureAgentPath: () => ensureAgentPath,
18718
+ ensureBrightsyLocalConfigFresh: () => ensureBrightsyLocalConfigFresh,
18474
18719
  ensureCloudCoordinator: () => ensureCloudCoordinator,
18720
+ ensureConnectedBrightsyTeamTokens: () => ensureConnectedBrightsyTeamTokens,
18475
18721
  ensureGhPreferOrigin: () => ensureGhPreferOrigin,
18476
18722
  ensureGlobalCoordinatorCwd: () => ensureGlobalCoordinatorCwd,
18477
18723
  ensureReviewRequestFile: () => ensureReviewRequestFile,
@@ -18734,6 +18980,7 @@ __export(index_exports, {
18734
18980
  readWorktreeInclude: () => readWorktreeInclude,
18735
18981
  recordScheduleRun: () => recordScheduleRun,
18736
18982
  recordSlackOutboundWatch: () => recordSlackOutboundWatch,
18983
+ refreshBrightsyAccessToken: () => refreshBrightsyAccessToken,
18737
18984
  refreshGitHubAuth: () => refreshGitHubAuth,
18738
18985
  registerPackagedUserMcpClients: () => registerPackagedUserMcpClients,
18739
18986
  releaseCaffeinateHoldForThread: () => releaseCaffeinateHoldForThread,
@@ -18964,37 +19211,14 @@ async function refreshGitHubAuth() {
18964
19211
  return getGitHubStatus();
18965
19212
  }
18966
19213
 
18967
- // src/http/fetch.ts
18968
- var injected = null;
18969
- function setHttpFetchImpl(fetchImpl) {
18970
- injected = fetchImpl;
18971
- }
18972
- function formatFetchError(err, url) {
18973
- if (!(err instanceof Error)) return `${String(err)} (${url})`;
18974
- const cause = err.cause;
18975
- let detail = "";
18976
- if (cause instanceof Error) {
18977
- const code = typeof cause.code === "string" ? cause.code : void 0;
18978
- detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
18979
- } else if (cause != null) {
18980
- detail = ` [${String(cause)}]`;
18981
- }
18982
- return `${err.message}${detail} (${url})`;
18983
- }
18984
- async function httpFetch(input, init) {
18985
- const url = typeof input === "string" ? input : input.href;
18986
- const fn = injected ?? globalThis.fetch.bind(globalThis);
18987
- try {
18988
- return await fn(url, init);
18989
- } catch (err) {
18990
- throw new Error(formatFetchError(err, url));
18991
- }
18992
- }
19214
+ // src/integrations/linear.ts
19215
+ init_fetch();
18993
19216
 
18994
19217
  // src/integrations/linear-oauth.ts
18995
19218
  var import_node_crypto9 = require("crypto");
18996
19219
  var import_node_http = require("http");
18997
19220
  init_app_settings();
19221
+ init_fetch();
18998
19222
 
18999
19223
  // src/integrations/linear-app.ts
19000
19224
  var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
@@ -19010,7 +19234,7 @@ var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
19010
19234
  var LINEAR_REVOKE = "https://api.linear.app/oauth/revoke";
19011
19235
  var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
19012
19236
  var LINEAR_OAUTH_SCOPES = "read,write";
19013
- var REFRESH_SKEW_MS = 5 * 6e4;
19237
+ var REFRESH_SKEW_MS2 = 5 * 6e4;
19014
19238
  function linearOAuthCredentials() {
19015
19239
  const settings = loadAppSettings();
19016
19240
  const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
@@ -19121,7 +19345,7 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
19121
19345
  const refresh = settings.integrations.linearRefreshToken?.trim() || "";
19122
19346
  const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
19123
19347
  const apiKey = settings.integrations.linearApiKey?.trim() || "";
19124
- if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS)) {
19348
+ if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
19125
19349
  return access;
19126
19350
  }
19127
19351
  if (refresh) {
@@ -19264,6 +19488,16 @@ async function disconnectLinear() {
19264
19488
 
19265
19489
  // src/integrations/linear.ts
19266
19490
  var LINEAR_GRAPHQL2 = "https://api.linear.app/graphql";
19491
+ var LIST_ISSUE_FIELDS = `
19492
+ id
19493
+ identifier
19494
+ title
19495
+ url
19496
+ assignee { id name }
19497
+ team { id key }
19498
+ labels(first: 10) { nodes { name } }
19499
+ cycle { name number startsAt endsAt completedAt }
19500
+ `;
19267
19501
  var ISSUE_FIELDS = `
19268
19502
  id
19269
19503
  identifier
@@ -19273,9 +19507,9 @@ var ISSUE_FIELDS = `
19273
19507
  priority
19274
19508
  state { id name type }
19275
19509
  assignee { id name }
19276
- team { id key name states { nodes { id name type } } }
19277
- labels { nodes { name } }
19278
- cycle { id name number startsAt endsAt completedAt }
19510
+ team { id key name states(first: 50) { nodes { id name type } } }
19511
+ labels(first: 50) { nodes { name } }
19512
+ cycle { name number startsAt endsAt completedAt }
19279
19513
  `;
19280
19514
  var ASSIGNED_ISSUES_QUERY = `
19281
19515
  query SideboardAssignedIssues($first: Int!) {
@@ -19287,7 +19521,7 @@ query SideboardAssignedIssues($first: Int!) {
19287
19521
  orderBy: updatedAt
19288
19522
  filter: { state: { type: { nin: ["completed", "canceled"] } } }
19289
19523
  ) {
19290
- nodes { ${ISSUE_FIELDS} }
19524
+ nodes { ${LIST_ISSUE_FIELDS} }
19291
19525
  }
19292
19526
  }
19293
19527
  }
@@ -19300,7 +19534,7 @@ query SideboardTeams {
19300
19534
  id
19301
19535
  key
19302
19536
  name
19303
- states { nodes { id name type } }
19537
+ states(first: 50) { nodes { id name type } }
19304
19538
  }
19305
19539
  }
19306
19540
  }
@@ -19694,6 +19928,7 @@ async function listIssues(repoPath) {
19694
19928
  }
19695
19929
 
19696
19930
  // src/index.ts
19931
+ init_fetch();
19697
19932
  init_agents();
19698
19933
  init_spawn();
19699
19934
  init_message_parts();
@@ -21280,25 +21515,31 @@ async function startMcpServer() {
21280
21515
  }
21281
21516
  server.tool(
21282
21517
  "present_artifact",
21283
- "Show an HTML, SVG, markdown, or React document in Sideboard\u2019s Claude-style side column (desktop). Use instead of claude.ai\u2019s artifact tool \u2014 pass the full document content. Do not also emit the same document as a chat html/markdown fence. Prefer type=html for interactive pages. For type=react, pass a single component module that `export default`s a component (JSX/TSX ok) \u2014 Sideboard bootstraps React/ReactDOM/Babel and renders it; only `react`/`react-dom` imports are available, no other npm packages.",
21518
+ "Show a document or live log in Sideboard\u2019s side column. For html/svg/markdown/react, pass the FULL document and do not also fence that same body in chat. For type=log, pass only NEW lines (same artifact_id appends). Prefer type=log for long-running job output \u2014 do not resend HTML. type=react is a single default-export component (JSX/TSX); only react/react-dom imports.",
21284
21519
  {
21285
21520
  title: import_zod4.z.string().describe("Short title shown in the artifact pane header"),
21286
- type: import_zod4.z.enum(["html", "svg", "markdown", "react"]).describe(
21287
- "Artifact kind \u2014 html opens an iframe preview; react transpiles and renders a default-exported component in a sandboxed iframe"
21521
+ type: import_zod4.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
21522
+ "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
21288
21523
  ),
21289
21524
  content: import_zod4.z.string().describe(
21290
- "Full document body (complete HTML page, SVG markup, markdown, or a React component module with a default export)"
21525
+ "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
21291
21526
  ),
21292
- artifact_id: import_zod4.z.string().optional().describe("Stable id when updating the same artifact across turns")
21527
+ artifact_id: import_zod4.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
21528
+ status: import_zod4.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
21529
+ phase: import_zod4.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
21530
+ mode: import_zod4.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
21293
21531
  },
21294
- async ({ title, type, artifact_id }) => {
21532
+ async ({ title, type, artifact_id, status, phase, mode }) => {
21295
21533
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
21296
21534
  const payload = {
21297
21535
  ok: true,
21298
21536
  artifact_id: id,
21299
21537
  title,
21300
21538
  type,
21301
- message: "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
21539
+ status,
21540
+ phase,
21541
+ mode: type === "log" ? mode ?? "append" : void 0,
21542
+ message: type === "log" ? "Log accepted. Same artifact_id appends; send only new lines next time." : "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
21302
21543
  };
21303
21544
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
21304
21545
  }
@@ -22261,7 +22502,9 @@ init_home_board();
22261
22502
  init_config();
22262
22503
 
22263
22504
  // src/brightsy/api.ts
22505
+ init_fetch();
22264
22506
  init_config();
22507
+ init_oauth();
22265
22508
  var formatBrightsyFetchError = formatFetchError;
22266
22509
  var BrightsySideboardApi = class {
22267
22510
  cfg;
@@ -22284,7 +22527,7 @@ var BrightsySideboardApi = class {
22284
22527
  return (this.cfg.endpoint || "https://brightsy.ai").replace(/\/$/, "");
22285
22528
  }
22286
22529
  async request(path2, init = {}) {
22287
- await this.refreshIfNeeded();
22530
+ await this.ensureFreshAccessToken();
22288
22531
  const url = `${this.endpoint}${path2}`;
22289
22532
  const headers = {
22290
22533
  Authorization: `Bearer ${this.cfg.access_token}`,
@@ -22307,33 +22550,21 @@ var BrightsySideboardApi = class {
22307
22550
  }
22308
22551
  return await res.json();
22309
22552
  }
22310
- async refreshIfNeeded() {
22311
- const expiresAt = this.cfg.expires_at ?? 0;
22312
- if (!this.cfg.refresh_token || Date.now() < expiresAt - 6e4) return;
22313
- const clientId = this.cfg.oauth_client_id || "brightsy-cli";
22314
- const url = `${this.endpoint}/oauth/token`;
22315
- let res;
22316
- try {
22317
- res = await this.fetchImpl(url, {
22318
- method: "POST",
22319
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
22320
- body: new URLSearchParams({
22321
- grant_type: "refresh_token",
22322
- refresh_token: this.cfg.refresh_token,
22323
- client_id: clientId
22324
- })
22325
- });
22326
- } catch (err) {
22327
- throw new Error(formatBrightsyFetchError(err, url));
22328
- }
22329
- if (!res.ok) return;
22330
- const data = await res.json();
22331
- if (!data.access_token) return;
22332
- this.cfg.access_token = data.access_token;
22333
- if (data.refresh_token) this.cfg.refresh_token = data.refresh_token;
22334
- if (data.expires_in) {
22335
- 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;
22336
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;
22337
22568
  saveBrightsyConfig(this.cfg);
22338
22569
  }
22339
22570
  async getAccess() {
@@ -22587,6 +22818,7 @@ async function runCloudConnect(opts) {
22587
22818
  // src/index.ts
22588
22819
  init_accounts();
22589
22820
  init_connected_teams();
22821
+ init_oauth();
22590
22822
  init_injected_mcp();
22591
22823
 
22592
22824
  // src/agents/user-mcp-config.ts
@@ -24603,6 +24835,7 @@ init_outbound_watch();
24603
24835
  CONTEXT_MIN_MESSAGES,
24604
24836
  CONVENTION_SETUP_RELPATHS,
24605
24837
  COORDINATOR_TOOL_PLAYBOOK,
24838
+ DEFAULT_WORKTREE_SORT,
24606
24839
  FAMOUS_SOCCER_TEAMS,
24607
24840
  GITHUB_GIT_AUTH_MODES,
24608
24841
  GLOBAL_WORKSPACE_ID,
@@ -24684,6 +24917,7 @@ init_outbound_watch();
24684
24917
  autoRenameBranchEnabled,
24685
24918
  autoRunAfterSetupEnabled,
24686
24919
  branchDisplayLabel,
24920
+ brightsyAccessTokenNeedsRefresh,
24687
24921
  brightsyAdapter,
24688
24922
  brightsyCloudConnectAgent,
24689
24923
  brightsyCloudConnectEnabled,
@@ -24770,7 +25004,9 @@ init_outbound_watch();
24770
25004
  enrichPathWithNpmGlobalBin,
24771
25005
  enrichWorkspacesWithGithub,
24772
25006
  ensureAgentPath,
25007
+ ensureBrightsyLocalConfigFresh,
24773
25008
  ensureCloudCoordinator,
25009
+ ensureConnectedBrightsyTeamTokens,
24774
25010
  ensureGhPreferOrigin,
24775
25011
  ensureGlobalCoordinatorCwd,
24776
25012
  ensureReviewRequestFile,
@@ -25033,6 +25269,7 @@ init_outbound_watch();
25033
25269
  readWorktreeInclude,
25034
25270
  recordScheduleRun,
25035
25271
  recordSlackOutboundWatch,
25272
+ refreshBrightsyAccessToken,
25036
25273
  refreshGitHubAuth,
25037
25274
  registerPackagedUserMcpClients,
25038
25275
  releaseCaffeinateHoldForThread,