@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.
@@ -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.";
@@ -14901,7 +15080,8 @@ function formatArtifactDirective() {
14901
15080
  "```html",
14902
15081
  "<!DOCTYPE html><html><head><title>Demo</title></head><body><h1>Hi</h1></body></html>",
14903
15082
  "```",
14904
- "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.",
15083
+ "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.",
15084
+ " 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.",
14905
15085
  "CMS / JSON Schema forms & tables (Brightsy or any schema+schemaUi source):",
14906
15086
  "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:",
14907
15087
  " - datasource=brightsy + resource_id (record type UUID) after fetching types via Brightsy MCP, or",
@@ -14916,7 +15096,7 @@ function formatArtifactDirective() {
14916
15096
  ].join("\n");
14917
15097
  }
14918
15098
  function formatUiReminder() {
14919
- 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.";
15099
+ 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.";
14920
15100
  }
14921
15101
  var import_node_fs40, import_node_path37;
14922
15102
  var init_instructions = __esm({
@@ -15899,6 +16079,11 @@ var init_orchestrator = __esm({
15899
16079
  turnBaselines = /* @__PURE__ */ new Map();
15900
16080
  /** Timers for orchestration session-quota auto-resume. */
15901
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();
15902
16087
  maxConcurrent;
15903
16088
  runningCount = 0;
15904
16089
  constructor(opts) {
@@ -16112,6 +16297,29 @@ var init_orchestrator = __esm({
16112
16297
  );
16113
16298
  }
16114
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
+ }
16115
16323
  getThreads(includeArchived = false) {
16116
16324
  return listThreads({ includeArchived });
16117
16325
  }
@@ -16211,15 +16419,20 @@ var init_orchestrator = __esm({
16211
16419
  throw new Error(`Thread is archived: ${thread.id}`);
16212
16420
  }
16213
16421
  const queue = [...current.queue, prompt];
16422
+ this.crashContinued.delete(thread.id);
16214
16423
  this.haltDrain.delete(thread.id);
16215
- 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";
16216
16427
  const pid = current.agentPid;
16217
16428
  if (typeof pid === "number" && pid > 0 && !isPidAlive(pid)) {
16218
16429
  patch.agentPid = null;
16219
16430
  }
16220
16431
  updateThread(thread.id, patch);
16221
16432
  this.emit({ type: "queue_changed", threadId: thread.id, queue });
16222
- 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
+ }
16223
16436
  if (thisProcessShouldDrainAgentQueues()) {
16224
16437
  void this.drainQueue(thread.id);
16225
16438
  }
@@ -16540,7 +16753,7 @@ var init_orchestrator = __esm({
16540
16753
  let parts = result.parts;
16541
16754
  let usage = result.usage ?? void 0;
16542
16755
  let exitCode = result.exitCode;
16543
- 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) {
16544
16757
  const sessionId = result.sessionId || this.requireThread(threadId).sessionId || "";
16545
16758
  if (sessionId) {
16546
16759
  const { recoverFinishedCursorRun: recoverFinishedCursorRun2 } = await Promise.resolve().then(() => (init_cursor_recover(), cursor_recover_exports));
@@ -16561,7 +16774,7 @@ var init_orchestrator = __esm({
16561
16774
  }
16562
16775
  let lastStderr = summarizeTurnStderr(stderrTail);
16563
16776
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
16564
- 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, {
16565
16778
  hasSession: Boolean(this.requireThread(threadId).sessionId)
16566
16779
  })) {
16567
16780
  updateThread(threadId, { sessionId: null });
@@ -16657,7 +16870,11 @@ var init_orchestrator = __esm({
16657
16870
  )) {
16658
16871
  updateThread(threadId, { sessionId: null });
16659
16872
  }
16660
- await syncThreadBranchFromGit(threadId);
16873
+ if (this.stoppedTurns.has(threadId)) {
16874
+ void syncThreadBranchFromGit(threadId).catch(() => void 0);
16875
+ } else {
16876
+ await syncThreadBranchFromGit(threadId);
16877
+ }
16661
16878
  if (this.stoppedTurns.has(threadId)) {
16662
16879
  const stopped = writeLiveStatus(threadId, "stopped");
16663
16880
  if (stopped?.status === "stopped") {
@@ -16681,9 +16898,16 @@ var init_orchestrator = __esm({
16681
16898
  });
16682
16899
  }
16683
16900
  this.emit({ type: "turn_finished", threadId, exitCode });
16684
- if (exitCode !== 0) {
16901
+ if (exitCode === 0) {
16902
+ this.crashContinued.delete(threadId);
16903
+ } else {
16685
16904
  const blob = [chatText, detail].filter(Boolean).join("\n");
16686
16905
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
16906
+ this.maybeEnqueueCrashContinue(threadId, {
16907
+ detail: detail || failDetail,
16908
+ assistantText: chatText,
16909
+ partsCount: parts.length
16910
+ });
16687
16911
  }
16688
16912
  }
16689
16913
  } catch (err) {
@@ -16703,6 +16927,11 @@ var init_orchestrator = __esm({
16703
16927
  }
16704
16928
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
16705
16929
  void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
16930
+ this.maybeEnqueueCrashContinue(threadId, {
16931
+ detail: message,
16932
+ assistantText: "",
16933
+ partsCount: 0
16934
+ });
16706
16935
  }
16707
16936
  } finally {
16708
16937
  this.startingTurns.delete(threadId);
@@ -17676,34 +17905,14 @@ init_worktree();
17676
17905
  init_run();
17677
17906
  init_app_settings();
17678
17907
 
17679
- // src/http/fetch.ts
17680
- var injected = null;
17681
- function formatFetchError(err, url) {
17682
- if (!(err instanceof Error)) return `${String(err)} (${url})`;
17683
- const cause = err.cause;
17684
- let detail = "";
17685
- if (cause instanceof Error) {
17686
- const code = typeof cause.code === "string" ? cause.code : void 0;
17687
- detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
17688
- } else if (cause != null) {
17689
- detail = ` [${String(cause)}]`;
17690
- }
17691
- return `${err.message}${detail} (${url})`;
17692
- }
17693
- async function httpFetch(input, init) {
17694
- const url = typeof input === "string" ? input : input.href;
17695
- const fn = injected ?? globalThis.fetch.bind(globalThis);
17696
- try {
17697
- return await fn(url, init);
17698
- } catch (err) {
17699
- throw new Error(formatFetchError(err, url));
17700
- }
17701
- }
17908
+ // src/integrations/linear.ts
17909
+ init_fetch();
17702
17910
 
17703
17911
  // src/integrations/linear-oauth.ts
17704
17912
  var import_node_crypto9 = require("crypto");
17705
17913
  var import_node_http = require("http");
17706
17914
  init_app_settings();
17915
+ init_fetch();
17707
17916
 
17708
17917
  // src/integrations/linear-app.ts
17709
17918
  var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
@@ -17712,7 +17921,7 @@ var BAKED_LINEAR_CLIENT_ID = "39a15abc7ea5bea17c47f47f85ff5fd0";
17712
17921
  var LINEAR_OAUTH_PORT = 19848;
17713
17922
  var LINEAR_OAUTH_REDIRECT = `http://127.0.0.1:${LINEAR_OAUTH_PORT}/callback`;
17714
17923
  var LINEAR_TOKEN = "https://api.linear.app/oauth/token";
17715
- var REFRESH_SKEW_MS = 5 * 6e4;
17924
+ var REFRESH_SKEW_MS2 = 5 * 6e4;
17716
17925
  function linearOAuthCredentials() {
17717
17926
  const settings = loadAppSettings();
17718
17927
  const clientId = process.env.SIDEBOARD_LINEAR_CLIENT_ID?.trim() || settings.integrations.linearClientId?.trim() || BAKED_LINEAR_CLIENT_ID.trim();
@@ -17780,7 +17989,7 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
17780
17989
  const refresh = settings.integrations.linearRefreshToken?.trim() || "";
17781
17990
  const expiresAt = settings.integrations.linearTokenExpiresAt ?? 0;
17782
17991
  const apiKey = settings.integrations.linearApiKey?.trim() || "";
17783
- if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS)) {
17992
+ if (access && (!refresh || Date.now() < expiresAt - REFRESH_SKEW_MS2)) {
17784
17993
  return access;
17785
17994
  }
17786
17995
  if (refresh) {
@@ -17799,6 +18008,16 @@ async function getLinearAuthToken(settings = loadAppSettings()) {
17799
18008
 
17800
18009
  // src/integrations/linear.ts
17801
18010
  var LINEAR_GRAPHQL = "https://api.linear.app/graphql";
18011
+ var LIST_ISSUE_FIELDS = `
18012
+ id
18013
+ identifier
18014
+ title
18015
+ url
18016
+ assignee { id name }
18017
+ team { id key }
18018
+ labels(first: 10) { nodes { name } }
18019
+ cycle { name number startsAt endsAt completedAt }
18020
+ `;
17802
18021
  var ISSUE_FIELDS = `
17803
18022
  id
17804
18023
  identifier
@@ -17808,9 +18027,9 @@ var ISSUE_FIELDS = `
17808
18027
  priority
17809
18028
  state { id name type }
17810
18029
  assignee { id name }
17811
- team { id key name states { nodes { id name type } } }
17812
- labels { nodes { name } }
17813
- cycle { id name number startsAt endsAt completedAt }
18030
+ team { id key name states(first: 50) { nodes { id name type } } }
18031
+ labels(first: 50) { nodes { name } }
18032
+ cycle { name number startsAt endsAt completedAt }
17814
18033
  `;
17815
18034
  var ASSIGNED_ISSUES_QUERY = `
17816
18035
  query SideboardAssignedIssues($first: Int!) {
@@ -17822,7 +18041,7 @@ query SideboardAssignedIssues($first: Int!) {
17822
18041
  orderBy: updatedAt
17823
18042
  filter: { state: { type: { nin: ["completed", "canceled"] } } }
17824
18043
  ) {
17825
- nodes { ${ISSUE_FIELDS} }
18044
+ nodes { ${LIST_ISSUE_FIELDS} }
17826
18045
  }
17827
18046
  }
17828
18047
  }
@@ -17835,7 +18054,7 @@ query SideboardTeams {
17835
18054
  id
17836
18055
  key
17837
18056
  name
17838
- states { nodes { id name type } }
18057
+ states(first: 50) { nodes { id name type } }
17839
18058
  }
17840
18059
  }
17841
18060
  }
@@ -19490,25 +19709,31 @@ async function startMcpServer() {
19490
19709
  }
19491
19710
  server.tool(
19492
19711
  "present_artifact",
19493
- "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.",
19712
+ "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.",
19494
19713
  {
19495
19714
  title: import_zod4.z.string().describe("Short title shown in the artifact pane header"),
19496
- type: import_zod4.z.enum(["html", "svg", "markdown", "react"]).describe(
19497
- "Artifact kind \u2014 html opens an iframe preview; react transpiles and renders a default-exported component in a sandboxed iframe"
19715
+ type: import_zod4.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
19716
+ "html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
19498
19717
  ),
19499
19718
  content: import_zod4.z.string().describe(
19500
- "Full document body (complete HTML page, SVG markup, markdown, or a React component module with a default export)"
19719
+ "html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
19501
19720
  ),
19502
- artifact_id: import_zod4.z.string().optional().describe("Stable id when updating the same artifact across turns")
19721
+ artifact_id: import_zod4.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
19722
+ status: import_zod4.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
19723
+ phase: import_zod4.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
19724
+ mode: import_zod4.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
19503
19725
  },
19504
- async ({ title, type, artifact_id }) => {
19726
+ async ({ title, type, artifact_id, status, phase, mode }) => {
19505
19727
  const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
19506
19728
  const payload = {
19507
19729
  ok: true,
19508
19730
  artifact_id: id,
19509
19731
  title,
19510
19732
  type,
19511
- message: "Artifact accepted. Sideboard desktop opens it in the side column beside chat."
19733
+ status,
19734
+ phase,
19735
+ mode: type === "log" ? mode ?? "append" : void 0,
19736
+ 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."
19512
19737
  };
19513
19738
  return { content: [{ type: "text", text: JSON.stringify(payload) }] };
19514
19739
  }