@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.
@@ -1553,11 +1553,40 @@ function humanizeAgentFailDetail(detail) {
1553
1553
  }
1554
1554
  function turnFailChatText(opts) {
1555
1555
  const chat = opts.assistantText.trim();
1556
- if (chat) return chat;
1557
- if (opts.exitCode === 0) return "";
1556
+ if (opts.exitCode === 0) return chat;
1558
1557
  const detail = opts.detail.trim();
1559
- if (detail) return humanizeAgentFailDetail(detail);
1560
- return formatTurnExitError(opts.exitCode ?? 1, "");
1558
+ const fail4 = detail ? humanizeAgentFailDetail(detail) : formatTurnExitError(opts.exitCode ?? 1, "");
1559
+ if (!chat) return fail4;
1560
+ const failCore = fail4.replace(/^exit\s*\d+:\s*/i, "").trim();
1561
+ if (looksLikeAgentFailureMessage(chat) || failCore && chat.includes(failCore) || chat.includes(fail4)) {
1562
+ return chat;
1563
+ }
1564
+ return `${chat}
1565
+
1566
+ ${fail4}`;
1567
+ }
1568
+ function shouldFeedErrorBackToAgent(opts) {
1569
+ const detail = opts.detail.trim();
1570
+ const chat = (opts.assistantText ?? "").trim();
1571
+ if (looksLikeAgentFailureMessage(detail) || looksLikeAgentFailureMessage(chat)) {
1572
+ return false;
1573
+ }
1574
+ if (looksLikeInvalidAgentSession(detail) || looksLikeV8Oom(detail) || looksLikeRetryableRunnerCrash(detail)) {
1575
+ return true;
1576
+ }
1577
+ if (/without details|segfault|sig(?:segv|abrt|ill)|fatal error/i.test(detail)) {
1578
+ return true;
1579
+ }
1580
+ return !chat && (opts.partsCount ?? 0) === 0;
1581
+ }
1582
+ function formatAgentErrorContinuePrompt(detail) {
1583
+ const fail4 = humanizeAgentFailDetail(detail.trim()) || "the agent process exited without details";
1584
+ return [
1585
+ "The previous agent process ended before it finished. Error:",
1586
+ fail4,
1587
+ "",
1588
+ "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."
1589
+ ].join("\n");
1561
1590
  }
1562
1591
  function formatTurnExitError(exitCode, stderrSummary) {
1563
1592
  const code = exitCode ?? 1;
@@ -6163,6 +6192,8 @@ var init_global_workspace = __esm({
6163
6192
 
6164
6193
  // src/brightsy/config.ts
6165
6194
  function brightsyConfigPath() {
6195
+ const override = process.env.BRIGHTSY_CONFIG?.trim();
6196
+ if (override) return override;
6166
6197
  return (0, import_node_path19.join)((0, import_node_os6.homedir)(), ".brightsy", "config.json");
6167
6198
  }
6168
6199
  function loadBrightsyConfig() {
@@ -6201,6 +6232,108 @@ var init_accounts = __esm({
6201
6232
  }
6202
6233
  });
6203
6234
 
6235
+ // src/http/fetch.ts
6236
+ function formatFetchError(err, url) {
6237
+ if (!(err instanceof Error)) return `${String(err)} (${url})`;
6238
+ const cause = err.cause;
6239
+ let detail = "";
6240
+ if (cause instanceof Error) {
6241
+ const code = typeof cause.code === "string" ? cause.code : void 0;
6242
+ detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
6243
+ } else if (cause != null) {
6244
+ detail = ` [${String(cause)}]`;
6245
+ }
6246
+ return `${err.message}${detail} (${url})`;
6247
+ }
6248
+ async function httpFetch(input, init) {
6249
+ const url = typeof input === "string" ? input : input.href;
6250
+ const fn = injected ?? globalThis.fetch.bind(globalThis);
6251
+ try {
6252
+ return await fn(url, init);
6253
+ } catch (err) {
6254
+ throw new Error(formatFetchError(err, url));
6255
+ }
6256
+ }
6257
+ var injected;
6258
+ var init_fetch = __esm({
6259
+ "src/http/fetch.ts"() {
6260
+ "use strict";
6261
+ injected = null;
6262
+ }
6263
+ });
6264
+
6265
+ // src/brightsy/oauth.ts
6266
+ function brightsyAccessTokenNeedsRefresh(expiresAt, now = Date.now()) {
6267
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= 0) {
6268
+ return true;
6269
+ }
6270
+ return now >= expiresAt - REFRESH_SKEW_MS;
6271
+ }
6272
+ function applyGrant(current, grant) {
6273
+ return {
6274
+ access_token: grant.access_token,
6275
+ refresh_token: grant.refresh_token || current.refresh_token,
6276
+ expires_at: grant.expires_at ?? current.expires_at
6277
+ };
6278
+ }
6279
+ async function refreshBrightsyAccessToken(opts) {
6280
+ const endpoint = (opts.endpoint || "https://brightsy.ai").replace(/\/$/, "");
6281
+ const url = `${endpoint}/oauth/token`;
6282
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
6283
+ let res;
6284
+ try {
6285
+ res = await fetchImpl(url, {
6286
+ method: "POST",
6287
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
6288
+ body: new URLSearchParams({
6289
+ grant_type: "refresh_token",
6290
+ refresh_token: opts.refreshToken,
6291
+ client_id: opts.clientId || "brightsy-cli"
6292
+ })
6293
+ });
6294
+ } catch (err) {
6295
+ throw new Error(formatFetchError(err, url));
6296
+ }
6297
+ if (!res.ok) return null;
6298
+ const data = await res.json();
6299
+ if (!data.access_token) return null;
6300
+ return {
6301
+ access_token: data.access_token,
6302
+ refresh_token: data.refresh_token,
6303
+ expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
6304
+ };
6305
+ }
6306
+ async function ensureBrightsyLocalConfigFresh(opts) {
6307
+ let cfg;
6308
+ try {
6309
+ cfg = loadBrightsyConfig();
6310
+ } catch {
6311
+ return null;
6312
+ }
6313
+ if (!cfg.refresh_token || !brightsyAccessTokenNeedsRefresh(cfg.expires_at)) {
6314
+ return cfg;
6315
+ }
6316
+ const grant = await refreshBrightsyAccessToken({
6317
+ endpoint: cfg.endpoint || "https://brightsy.ai",
6318
+ refreshToken: cfg.refresh_token,
6319
+ clientId: cfg.oauth_client_id,
6320
+ fetchImpl: opts?.fetchImpl
6321
+ });
6322
+ if (!grant) return cfg;
6323
+ const next = { ...cfg, ...applyGrant(cfg, grant) };
6324
+ saveBrightsyConfig(next);
6325
+ return next;
6326
+ }
6327
+ var REFRESH_SKEW_MS;
6328
+ var init_oauth = __esm({
6329
+ "src/brightsy/oauth.ts"() {
6330
+ "use strict";
6331
+ init_fetch();
6332
+ init_config();
6333
+ REFRESH_SKEW_MS = 6e4;
6334
+ }
6335
+ });
6336
+
6204
6337
  // src/brightsy/connected-teams.ts
6205
6338
  function storePath4() {
6206
6339
  return (0, import_node_path20.join)(appDataDir(), "brightsy-teams.json");
@@ -6251,7 +6384,6 @@ function applyConnectedTeamToCli(team) {
6251
6384
  }
6252
6385
  async function refreshTeamToken(team) {
6253
6386
  if (!team.refresh_token) return team;
6254
- const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
6255
6387
  const cfg = (() => {
6256
6388
  try {
6257
6389
  return loadBrightsyConfig();
@@ -6259,39 +6391,34 @@ async function refreshTeamToken(team) {
6259
6391
  return null;
6260
6392
  }
6261
6393
  })();
6262
- const clientId = cfg?.oauth_client_id || "brightsy-cli";
6263
- const res = await fetch(`${endpoint}/oauth/token`, {
6264
- method: "POST",
6265
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
6266
- body: new URLSearchParams({
6267
- grant_type: "refresh_token",
6268
- refresh_token: team.refresh_token,
6269
- client_id: clientId
6270
- })
6394
+ const grant = await refreshBrightsyAccessToken({
6395
+ endpoint: team.endpoint || "https://brightsy.ai",
6396
+ refreshToken: team.refresh_token,
6397
+ clientId: cfg?.oauth_client_id
6271
6398
  });
6272
- if (!res.ok) return team;
6273
- const data = await res.json();
6274
- if (!data.access_token) return team;
6399
+ if (!grant) return team;
6275
6400
  return {
6276
6401
  ...team,
6277
- access_token: data.access_token,
6278
- refresh_token: data.refresh_token || team.refresh_token,
6279
- expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : team.expires_at
6402
+ access_token: grant.access_token,
6403
+ refresh_token: grant.refresh_token || team.refresh_token,
6404
+ expires_at: grant.expires_at ?? team.expires_at
6280
6405
  };
6281
6406
  }
6282
6407
  async function ensureConnectedBrightsyTeamTokens() {
6408
+ await ensureBrightsyLocalConfigFresh().catch(() => null);
6283
6409
  const teams = readStore4();
6284
6410
  if (teams.length === 0) return [];
6285
6411
  const next = [];
6286
6412
  let changed = false;
6287
6413
  for (const team of teams) {
6288
- const expired = typeof team.expires_at === "number" && Date.now() >= team.expires_at - 6e4;
6289
- if (!expired) {
6414
+ if (!team.refresh_token || !brightsyAccessTokenNeedsRefresh(team.expires_at)) {
6290
6415
  next.push(team);
6291
6416
  continue;
6292
6417
  }
6293
6418
  const refreshed = await refreshTeamToken(team);
6294
- if (refreshed.access_token !== team.access_token) changed = true;
6419
+ if (refreshed.access_token !== team.access_token || refreshed.refresh_token !== team.refresh_token || refreshed.expires_at !== team.expires_at) {
6420
+ changed = true;
6421
+ }
6295
6422
  next.push(refreshed);
6296
6423
  }
6297
6424
  if (changed) writeStore2(next);
@@ -6339,6 +6466,7 @@ var init_connected_teams = __esm({
6339
6466
  init_paths();
6340
6467
  init_accounts();
6341
6468
  init_config();
6469
+ init_oauth();
6342
6470
  }
6343
6471
  });
6344
6472
 
@@ -7610,6 +7738,7 @@ function teamEnv(team) {
7610
7738
  const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
7611
7739
  return {
7612
7740
  BRIGHTSY_API_TOKEN: team.access_token,
7741
+ ...team.refresh_token ? { BRIGHTSY_REFRESH_TOKEN: team.refresh_token } : {},
7613
7742
  BRIGHTSY_ACCOUNT_ID: team.id,
7614
7743
  BRIGHTSY_API_URL: endpoint
7615
7744
  };
@@ -7889,10 +8018,15 @@ var init_types = __esm({
7889
8018
 
7890
8019
  // src/agents/claude.ts
7891
8020
  async function loadMcpServers() {
8021
+ if (mcpListCache && Date.now() - mcpListCache.at < 3e4) {
8022
+ return mcpListCache.servers;
8023
+ }
7892
8024
  const claude = resolveClaudeExecutable();
7893
8025
  const mcpText = await run(claude, ["mcp", "list"], { reject: false });
7894
- return parseMcpList(`${mcpText.stdout}
8026
+ const servers = parseMcpList(`${mcpText.stdout}
7895
8027
  ${mcpText.stderr}`);
8028
+ mcpListCache = { at: Date.now(), servers };
8029
+ return servers;
7896
8030
  }
7897
8031
  function usageFromClaude(usage) {
7898
8032
  if (!usage) return null;
@@ -8106,7 +8240,7 @@ function parseIssuesJson(raw) {
8106
8240
  }
8107
8241
  return [];
8108
8242
  }
8109
- var import_node_fs22, BASE_ALLOWED_TOOLS, CLAUDE_PRINT_BG_WAIT_CEILING_MS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, claudeAdapter;
8243
+ var import_node_fs22, BASE_ALLOWED_TOOLS, CLAUDE_PRINT_BG_WAIT_CEILING_MS, CLAUDE_CHROME_ALLOWED_TOOLS, CLAUDE_PROMPT_ARG_MAX, mcpListCache, claudeAdapter;
8110
8244
  var init_claude = __esm({
8111
8245
  "src/agents/claude.ts"() {
8112
8246
  "use strict";
@@ -8144,6 +8278,7 @@ var init_claude = __esm({
8144
8278
  "Skill(claude-in-chrome)"
8145
8279
  ];
8146
8280
  CLAUDE_PROMPT_ARG_MAX = 2e5;
8281
+ mcpListCache = null;
8147
8282
  claudeAdapter = {
8148
8283
  kind: "claude",
8149
8284
  async detect() {
@@ -9522,10 +9657,13 @@ var init_opencode = __esm({
9522
9657
  const part = obj.part;
9523
9658
  const id = part?.tool_use_id ?? part?.id ?? obj.tool_use_id ?? obj.id;
9524
9659
  if (!id) return null;
9660
+ const flagged = obj.isError === true || obj.is_error === true;
9661
+ const errText2 = formatUnknownDetail(obj.error);
9525
9662
  return {
9526
9663
  type: "tool_result",
9527
9664
  id,
9528
- content: part?.output ?? part?.content ?? obj.output ?? obj.content
9665
+ content: part?.output ?? part?.content ?? obj.output ?? obj.content ?? (errText2 || void 0),
9666
+ isError: flagged || Boolean(errText2)
9529
9667
  };
9530
9668
  }
9531
9669
  if (obj.type === "step_finish" || obj.type === "step-finish") {
@@ -12021,7 +12159,48 @@ function isMergedPrState(prUrl, prState) {
12021
12159
  if (!prUrl?.trim()) return false;
12022
12160
  return (prState ?? "").trim().toUpperCase() === "MERGED";
12023
12161
  }
12024
- function groupHomeBoardWorktrees(threads) {
12162
+ function groupCreatedAt(group) {
12163
+ let min = group[0]?.createdAt ?? "";
12164
+ for (const t of group) {
12165
+ if (t.createdAt && t.createdAt < min) min = t.createdAt;
12166
+ }
12167
+ return min;
12168
+ }
12169
+ function groupUpdatedAt(group) {
12170
+ let max = group[0]?.updatedAt ?? "";
12171
+ for (const t of group) {
12172
+ if (t.updatedAt && t.updatedAt > max) max = t.updatedAt;
12173
+ }
12174
+ return max;
12175
+ }
12176
+ function sortThreadsInWorktree(list) {
12177
+ return [...list].sort((a, b) => {
12178
+ const created = a.createdAt.localeCompare(b.createdAt);
12179
+ if (created !== 0) return created;
12180
+ return a.id.localeCompare(b.id);
12181
+ });
12182
+ }
12183
+ function compareWorktreeGroups(a, b, mode, labelOf) {
12184
+ if (mode === "name") {
12185
+ const named = (labelOf?.(a) ?? a[0]?.id ?? "").localeCompare(
12186
+ labelOf?.(b) ?? b[0]?.id ?? "",
12187
+ void 0,
12188
+ { sensitivity: "base" }
12189
+ );
12190
+ if (named !== 0) return named;
12191
+ } else if (mode === "activity") {
12192
+ const activity = groupUpdatedAt(b).localeCompare(groupUpdatedAt(a));
12193
+ if (activity !== 0) return activity;
12194
+ } else {
12195
+ const created = groupCreatedAt(b).localeCompare(groupCreatedAt(a));
12196
+ if (created !== 0) return created;
12197
+ }
12198
+ return (a[0]?.id ?? "").localeCompare(b[0]?.id ?? "");
12199
+ }
12200
+ function defaultWorktreeGroupLabel(group) {
12201
+ return worktreeDisplayLabelForGroup(group);
12202
+ }
12203
+ function groupHomeBoardWorktrees(threads, sort = DEFAULT_WORKTREE_SORT, labelOf = defaultWorktreeGroupLabel) {
12025
12204
  const map = /* @__PURE__ */ new Map();
12026
12205
  for (const t of threads) {
12027
12206
  const key = normalizeWorktreePath(t.worktreePath);
@@ -12029,7 +12208,7 @@ function groupHomeBoardWorktrees(threads) {
12029
12208
  list.push(t);
12030
12209
  map.set(key, list);
12031
12210
  }
12032
- return [...map.values()].map((list) => list.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))).sort((a, b) => (b[0]?.updatedAt ?? "").localeCompare(a[0]?.updatedAt ?? ""));
12211
+ return [...map.values()].map((list) => sortThreadsInWorktree(list)).sort((a, b) => compareWorktreeGroups(a, b, sort, labelOf));
12033
12212
  }
12034
12213
  function classifyWorktreeColumn(group) {
12035
12214
  if (group.some((t) => isMergedPrState(t.prUrl, t.prState))) return "done";
@@ -12328,8 +12507,7 @@ function assembleHomeBoard(input) {
12328
12507
  const kind = input.kind ?? "all";
12329
12508
  const limit = Math.max(1, input.limit ?? BOARD_PAGE_SIZE);
12330
12509
  const wsName = input.workspaceName ?? (() => "");
12331
- const byUpdated = (a, b) => b.updatedAt.localeCompare(a.updatedAt);
12332
- const live = input.threads.filter((t) => t.status !== "archived" && isHomeBoardThread(t)).sort(byUpdated);
12510
+ const live = input.threads.filter((t) => t.status !== "archived" && isHomeBoardThread(t));
12333
12511
  const columns = emptyColumns();
12334
12512
  const hidden = emptyHidden();
12335
12513
  const totals = {
@@ -12384,7 +12562,7 @@ function assembleHomeBoard(input) {
12384
12562
  }
12385
12563
  return { columns, hidden, totals };
12386
12564
  }
12387
- var GLOBAL_WORKSPACE_ID2, BOARD_COLUMN_DEFS, HOME_BOARD_CACHE_TTL_MS, DEFAULTISH_BRANCH, BOARD_PAGE_SIZE, HOME_BOARD_AGENT_HINT;
12565
+ var GLOBAL_WORKSPACE_ID2, BOARD_COLUMN_DEFS, HOME_BOARD_CACHE_TTL_MS, DEFAULT_WORKTREE_SORT, DEFAULTISH_BRANCH, BOARD_PAGE_SIZE, HOME_BOARD_AGENT_HINT;
12388
12566
  var init_home_board = __esm({
12389
12567
  "src/board/home-board.ts"() {
12390
12568
  "use strict";
@@ -12397,6 +12575,7 @@ var init_home_board = __esm({
12397
12575
  { id: "done", title: "Merged" }
12398
12576
  ];
12399
12577
  HOME_BOARD_CACHE_TTL_MS = 15 * 60 * 1e3;
12578
+ DEFAULT_WORKTREE_SORT = "created";
12400
12579
  DEFAULTISH_BRANCH = /^(main|master|develop|development|trunk|default|head)$/;
12401
12580
  BOARD_PAGE_SIZE = 40;
12402
12581
  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.";
@@ -15900,6 +16079,11 @@ var init_orchestrator = __esm({
15900
16079
  turnBaselines = /* @__PURE__ */ new Map();
15901
16080
  /** Timers for orchestration session-quota auto-resume. */
15902
16081
  quotaResumeTimers = /* @__PURE__ */ new Map();
16082
+ /**
16083
+ * Threads that already got a crash-continue turn. Cleared on a successful
16084
+ * finish or a user send so a later crash can recover again.
16085
+ */
16086
+ crashContinued = /* @__PURE__ */ new Set();
15903
16087
  maxConcurrent;
15904
16088
  runningCount = 0;
15905
16089
  constructor(opts) {
@@ -16113,6 +16297,29 @@ var init_orchestrator = __esm({
16113
16297
  );
16114
16298
  }
16115
16299
  }
16300
+ /**
16301
+ * Cursor-style recovery: after a runner crash, feed the error back as the
16302
+ * next prompt so the same agent can continue instead of sitting on lastError.
16303
+ */
16304
+ maybeEnqueueCrashContinue(threadId, opts) {
16305
+ if (this.crashContinued.has(threadId)) return;
16306
+ if (this.haltDrain.has(threadId)) return;
16307
+ if (!shouldFeedErrorBackToAgent({
16308
+ detail: opts.detail,
16309
+ assistantText: opts.assistantText,
16310
+ partsCount: opts.partsCount
16311
+ })) {
16312
+ return;
16313
+ }
16314
+ const thread = readThread(threadId);
16315
+ if (!thread || thread.status === "archived") return;
16316
+ this.crashContinued.add(threadId);
16317
+ const prompt = formatAgentErrorContinuePrompt(opts.detail);
16318
+ const queue = [prompt, ...thread.queue];
16319
+ updateThread(threadId, { queue });
16320
+ this.emit({ type: "queue_changed", threadId, queue });
16321
+ this.haltDrain.delete(threadId);
16322
+ }
16116
16323
  getThreads(includeArchived = false) {
16117
16324
  return listThreads({ includeArchived });
16118
16325
  }
@@ -16212,15 +16419,20 @@ var init_orchestrator = __esm({
16212
16419
  throw new Error(`Thread is archived: ${thread.id}`);
16213
16420
  }
16214
16421
  const queue = [...current.queue, prompt];
16422
+ this.crashContinued.delete(thread.id);
16215
16423
  this.haltDrain.delete(thread.id);
16216
- const patch = { queue, status: "queued" };
16424
+ const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
16425
+ const patch = { queue };
16426
+ if (!inFlight) patch.status = "queued";
16217
16427
  const pid = current.agentPid;
16218
16428
  if (typeof pid === "number" && pid > 0 && !isPidAlive(pid)) {
16219
16429
  patch.agentPid = null;
16220
16430
  }
16221
16431
  updateThread(thread.id, patch);
16222
16432
  this.emit({ type: "queue_changed", threadId: thread.id, queue });
16223
- this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
16433
+ if (!inFlight) {
16434
+ this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
16435
+ }
16224
16436
  if (thisProcessShouldDrainAgentQueues()) {
16225
16437
  void this.drainQueue(thread.id);
16226
16438
  }
@@ -16541,7 +16753,7 @@ var init_orchestrator = __esm({
16541
16753
  let parts = result.parts;
16542
16754
  let usage = result.usage ?? void 0;
16543
16755
  let exitCode = result.exitCode;
16544
- if (this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
16756
+ if (!this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
16545
16757
  const sessionId = result.sessionId || this.requireThread(threadId).sessionId || "";
16546
16758
  if (sessionId) {
16547
16759
  const { recoverFinishedCursorRun: recoverFinishedCursorRun2 } = await Promise.resolve().then(() => (init_cursor_recover(), cursor_recover_exports));
@@ -16562,7 +16774,7 @@ var init_orchestrator = __esm({
16562
16774
  }
16563
16775
  let lastStderr = summarizeTurnStderr(stderrTail);
16564
16776
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
16565
- if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent !== "brightsy" && shouldRetryFailedAgentTurn(detail, {
16777
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && shouldRetryFailedAgentTurn(detail, {
16566
16778
  hasSession: Boolean(this.requireThread(threadId).sessionId)
16567
16779
  })) {
16568
16780
  updateThread(threadId, { sessionId: null });
@@ -16658,7 +16870,11 @@ var init_orchestrator = __esm({
16658
16870
  )) {
16659
16871
  updateThread(threadId, { sessionId: null });
16660
16872
  }
16661
- await syncThreadBranchFromGit(threadId);
16873
+ if (this.stoppedTurns.has(threadId)) {
16874
+ void syncThreadBranchFromGit(threadId).catch(() => void 0);
16875
+ } else {
16876
+ await syncThreadBranchFromGit(threadId);
16877
+ }
16662
16878
  if (this.stoppedTurns.has(threadId)) {
16663
16879
  const stopped = writeLiveStatus(threadId, "stopped");
16664
16880
  if (stopped?.status === "stopped") {
@@ -16682,9 +16898,16 @@ var init_orchestrator = __esm({
16682
16898
  });
16683
16899
  }
16684
16900
  this.emit({ type: "turn_finished", threadId, exitCode });
16685
- if (exitCode !== 0) {
16901
+ if (exitCode === 0) {
16902
+ this.crashContinued.delete(threadId);
16903
+ } else {
16686
16904
  const blob = [chatText, detail].filter(Boolean).join("\n");
16687
16905
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
16906
+ this.maybeEnqueueCrashContinue(threadId, {
16907
+ detail: detail || failDetail,
16908
+ assistantText: chatText,
16909
+ partsCount: parts.length
16910
+ });
16688
16911
  }
16689
16912
  }
16690
16913
  } catch (err) {
@@ -16704,6 +16927,11 @@ var init_orchestrator = __esm({
16704
16927
  }
16705
16928
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
16706
16929
  void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
16930
+ this.maybeEnqueueCrashContinue(threadId, {
16931
+ detail: message,
16932
+ assistantText: "",
16933
+ partsCount: 0
16934
+ });
16707
16935
  }
16708
16936
  } finally {
16709
16937
  this.startingTurns.delete(threadId);
@@ -17677,34 +17905,14 @@ init_worktree();
17677
17905
  init_run();
17678
17906
  init_app_settings();
17679
17907
 
17680
- // src/http/fetch.ts
17681
- var injected = null;
17682
- function formatFetchError(err, url) {
17683
- if (!(err instanceof Error)) return `${String(err)} (${url})`;
17684
- const cause = err.cause;
17685
- let detail = "";
17686
- if (cause instanceof Error) {
17687
- const code = typeof cause.code === "string" ? cause.code : void 0;
17688
- detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
17689
- } else if (cause != null) {
17690
- detail = ` [${String(cause)}]`;
17691
- }
17692
- return `${err.message}${detail} (${url})`;
17693
- }
17694
- async function httpFetch(input, init) {
17695
- const url = typeof input === "string" ? input : input.href;
17696
- const fn = injected ?? globalThis.fetch.bind(globalThis);
17697
- try {
17698
- return await fn(url, init);
17699
- } catch (err) {
17700
- throw new Error(formatFetchError(err, url));
17701
- }
17702
- }
17908
+ // src/integrations/linear.ts
17909
+ init_fetch();
17703
17910
 
17704
17911
  // src/integrations/linear-oauth.ts
17705
17912
  var import_node_crypto9 = require("crypto");
17706
17913
  var import_node_http = require("http");
17707
17914
  init_app_settings();
17915
+ init_fetch();
17708
17916
 
17709
17917
  // src/integrations/linear-app.ts
17710
17918
  var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
@@ -17713,7 +17921,7 @@ var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
17713
17921
  var LINEAR_OAUTH_PORT = 19848;
17714
17922
  var LINEAR_OAUTH_REDIRECT = `http://127.0.0.1:${LINEAR_OAUTH_PORT}/callback`;
17715
17923
  var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
17716
- var REFRESH_SKEW_MS = 5 * 6e4;
17924
+ var REFRESH_SKEW_MS2 = 5 * 6e4;
17717
17925
  function linearOAuthCredentials() {
17718
17926
  const settings = loadAppSettings();
17719
17927
  const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
@@ -17781,7 +17989,7 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
17781
17989
  const refresh = settings.integrations.linearRefreshToken?.trim() || "";
17782
17990
  const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
17783
17991
  const apiKey = settings.integrations.linearApiKey?.trim() || "";
17784
- if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS)) {
17992
+ if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
17785
17993
  return access;
17786
17994
  }
17787
17995
  if (refresh) {
@@ -31,12 +31,15 @@ import {
31
31
  slackTokenFor,
32
32
  syncBoardPins,
33
33
  updateSchedule
34
- } from "../chunk-FMBLOOAO.js";
34
+ } from "../chunk-SMWJAE74.js";
35
35
  import {
36
36
  listModelsForAgent,
37
37
  sideboardMcpProfile
38
- } from "../chunk-ZMUOPTKZ.js";
39
- import "../chunk-YMRT2DU6.js";
38
+ } from "../chunk-PK4PQUPU.js";
39
+ import "../chunk-LLX3OMHU.js";
40
+ import {
41
+ httpFetch
42
+ } from "../chunk-R7FGPBKL.js";
40
43
  import "../chunk-SYLTXDRF.js";
41
44
  import "../chunk-N5PM7HGQ.js";
42
45
  import "../chunk-KLBOWJ74.js";
@@ -79,30 +82,6 @@ import { z as z4 } from "zod";
79
82
  import { randomUUID as randomUUID2 } from "crypto";
80
83
  import { basename } from "path";
81
84
 
82
- // src/http/fetch.ts
83
- var injected = null;
84
- function formatFetchError(err, url) {
85
- if (!(err instanceof Error)) return `${String(err)} (${url})`;
86
- const cause = err.cause;
87
- let detail = "";
88
- if (cause instanceof Error) {
89
- const code = typeof cause.code === "string" ? cause.code : void 0;
90
- detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
91
- } else if (cause != null) {
92
- detail = ` [${String(cause)}]`;
93
- }
94
- return `${err.message}${detail} (${url})`;
95
- }
96
- async function httpFetch(input, init) {
97
- const url = typeof input === "string" ? input : input.href;
98
- const fn = injected ?? globalThis.fetch.bind(globalThis);
99
- try {
100
- return await fn(url, init);
101
- } catch (err) {
102
- throw new Error(formatFetchError(err, url));
103
- }
104
- }
105
-
106
85
  // src/integrations/linear-oauth.ts
107
86
  import { createHash, randomBytes } from "crypto";
108
87
  import { createServer } from "http";
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ brightsyAccessTokenNeedsRefresh,
5
+ ensureBrightsyLocalConfigFresh,
6
+ refreshBrightsyAccessToken
7
+ } from "./chunk-R7FGPBKL.js";
8
+ export {
9
+ brightsyAccessTokenNeedsRefresh,
10
+ ensureBrightsyLocalConfigFresh,
11
+ refreshBrightsyAccessToken
12
+ };
@@ -0,0 +1,10 @@
1
+ import {
2
+ brightsyAccessTokenNeedsRefresh,
3
+ ensureBrightsyLocalConfigFresh,
4
+ refreshBrightsyAccessToken
5
+ } from "./chunk-AQESMTCZ.js";
6
+ export {
7
+ brightsyAccessTokenNeedsRefresh,
8
+ ensureBrightsyLocalConfigFresh,
9
+ refreshBrightsyAccessToken
10
+ };
@@ -4,15 +4,16 @@ import {
4
4
  isPidAlive,
5
5
  startOrchestration,
6
6
  waitForPidExit
7
- } from "./chunk-YALVX3UE.js";
8
- import "./chunk-NV73AO7Q.js";
9
- import "./chunk-EKIDHL2T.js";
7
+ } from "./chunk-VFXZTSAD.js";
8
+ import "./chunk-MGYCQAZ6.js";
9
+ import "./chunk-SIPDL62B.js";
10
+ import "./chunk-AQESMTCZ.js";
10
11
  import "./chunk-VE22NWBD.js";
11
12
  import "./chunk-HULESWLI.js";
12
13
  import "./chunk-HZPO3SSZ.js";
13
14
  import "./chunk-QTUESPAW.js";
14
15
  import "./chunk-AXQ4ZWHK.js";
15
- import "./chunk-IUJOI7KK.js";
16
+ import "./chunk-FSCQXMGI.js";
16
17
  import "./chunk-DCOZABNT.js";
17
18
  import "./chunk-UDQI3N47.js";
18
19
  import "./chunk-FKOIHGKV.js";
@@ -6,9 +6,10 @@ import {
6
6
  isPidAlive,
7
7
  startOrchestration,
8
8
  waitForPidExit
9
- } from "./chunk-FMBLOOAO.js";
10
- import "./chunk-ZMUOPTKZ.js";
11
- import "./chunk-YMRT2DU6.js";
9
+ } from "./chunk-SMWJAE74.js";
10
+ import "./chunk-PK4PQUPU.js";
11
+ import "./chunk-LLX3OMHU.js";
12
+ import "./chunk-R7FGPBKL.js";
12
13
  import "./chunk-SYLTXDRF.js";
13
14
  import "./chunk-N5PM7HGQ.js";
14
15
  import "./chunk-KLBOWJ74.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sideboard-ai/core",
3
- "version": "0.1.130",
3
+ "version": "0.1.132",
4
4
  "description": "Sideboard core — orchestration, agents, git worktrees, MCP server",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",