@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.
@@ -1,3 +1,10 @@
1
+ import {
2
+ brightsyAccessTokenNeedsRefresh,
3
+ ensureBrightsyLocalConfigFresh,
4
+ loadBrightsyConfig,
5
+ refreshBrightsyAccessToken,
6
+ saveBrightsyConfig
7
+ } from "./chunk-AQESMTCZ.js";
1
8
  import {
2
9
  appDataDir
3
10
  } from "./chunk-QZQEXME2.js";
@@ -6,37 +13,12 @@ import {
6
13
  } from "./chunk-DVM4ID64.js";
7
14
 
8
15
  // src/brightsy/connected-teams.ts
9
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
10
- import { join as join2 } from "path";
11
-
12
- // src/brightsy/config.ts
13
- import { existsSync, readFileSync, writeFileSync } from "fs";
14
- import { homedir } from "os";
16
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
15
17
  import { join } from "path";
16
- function brightsyConfigPath() {
17
- return join(homedir(), ".brightsy", "config.json");
18
- }
19
- function loadBrightsyConfig() {
20
- const path = brightsyConfigPath();
21
- if (!existsSync(path)) {
22
- throw new Error("Brightsy not logged in \u2014 run `brightsy login` first");
23
- }
24
- const raw = JSON.parse(readFileSync(path, "utf8"));
25
- if (!raw.access_token || !raw.account_id) {
26
- throw new Error("Brightsy config incomplete \u2014 run `brightsy login`");
27
- }
28
- return raw;
29
- }
30
- function saveBrightsyConfig(cfg) {
31
- writeFileSync(brightsyConfigPath(), `${JSON.stringify(cfg, null, 2)}
32
- `, {
33
- mode: 384
34
- });
35
- }
36
18
 
37
19
  // src/brightsy/accounts.ts
38
20
  async function loadConnectedTeams() {
39
- const { listConnectedBrightsyTeams: listConnectedBrightsyTeams2 } = await import("./connected-teams-ZCOU6KCD.js");
21
+ const { listConnectedBrightsyTeams: listConnectedBrightsyTeams2 } = await import("./connected-teams-S6E3D5C7.js");
40
22
  return listConnectedBrightsyTeams2();
41
23
  }
42
24
  async function runBrightsyTeamsJson(args) {
@@ -64,11 +46,15 @@ async function listBrightsyAccounts() {
64
46
  }
65
47
  async function getBrightsySession() {
66
48
  try {
49
+ const { ensureBrightsyLocalConfigFresh: ensureBrightsyLocalConfigFresh2 } = await import("./oauth-FT7EL2PY.js");
50
+ const { ensureConnectedBrightsyTeamTokens: ensureConnectedBrightsyTeamTokens2 } = await import("./connected-teams-S6E3D5C7.js");
51
+ await ensureBrightsyLocalConfigFresh2();
52
+ await ensureConnectedBrightsyTeamTokens2();
67
53
  loadBrightsyConfig();
68
54
  const data = await runBrightsyTeamsJson([]);
69
55
  const accounts = Array.isArray(data.teams) ? data.teams : [];
70
56
  const active = data.active ?? accounts.find((a) => a.active) ?? null;
71
- const { ensureCliTeamTracked: ensureCliTeamTracked2 } = await import("./connected-teams-ZCOU6KCD.js");
57
+ const { ensureCliTeamTracked: ensureCliTeamTracked2 } = await import("./connected-teams-S6E3D5C7.js");
72
58
  const connectedTeams = ensureCliTeamTracked2(
73
59
  active ? { id: active.id, slug: active.slug, name: active.name } : void 0
74
60
  );
@@ -108,20 +94,20 @@ async function performBrightsyAccountSwitch(accountIdOrSlug) {
108
94
  return { cfg, target: data.active };
109
95
  }
110
96
  async function switchBrightsyAccount(accountIdOrSlug) {
111
- const { connectBrightsyTeam: connectBrightsyTeam2 } = await import("./connected-teams-ZCOU6KCD.js");
97
+ const { connectBrightsyTeam: connectBrightsyTeam2 } = await import("./connected-teams-S6E3D5C7.js");
112
98
  await connectBrightsyTeam2(accountIdOrSlug);
113
99
  return getBrightsySession();
114
100
  }
115
101
 
116
102
  // src/brightsy/connected-teams.ts
117
103
  function storePath() {
118
- return join2(appDataDir(), "brightsy-teams.json");
104
+ return join(appDataDir(), "brightsy-teams.json");
119
105
  }
120
106
  function readStore() {
121
107
  const path = storePath();
122
- if (!existsSync2(path)) return [];
108
+ if (!existsSync(path)) return [];
123
109
  try {
124
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
110
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
125
111
  return Array.isArray(parsed.teams) ? parsed.teams : [];
126
112
  } catch {
127
113
  return [];
@@ -130,7 +116,7 @@ function readStore() {
130
116
  function writeStore(teams) {
131
117
  mkdirSync(appDataDir(), { recursive: true });
132
118
  const path = storePath();
133
- writeFileSync2(path, `${JSON.stringify({ teams }, null, 2)}
119
+ writeFileSync(path, `${JSON.stringify({ teams }, null, 2)}
134
120
  `, {
135
121
  mode: 384
136
122
  });
@@ -166,7 +152,6 @@ function applyConnectedTeamToCli(team) {
166
152
  }
167
153
  async function refreshTeamToken(team) {
168
154
  if (!team.refresh_token) return team;
169
- const endpoint = (team.endpoint || "https://brightsy.ai").replace(/\/$/, "");
170
155
  const cfg = (() => {
171
156
  try {
172
157
  return loadBrightsyConfig();
@@ -174,39 +159,34 @@ async function refreshTeamToken(team) {
174
159
  return null;
175
160
  }
176
161
  })();
177
- const clientId = cfg?.oauth_client_id || "brightsy-cli";
178
- const res = await fetch(`${endpoint}/oauth/token`, {
179
- method: "POST",
180
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
181
- body: new URLSearchParams({
182
- grant_type: "refresh_token",
183
- refresh_token: team.refresh_token,
184
- client_id: clientId
185
- })
162
+ const grant = await refreshBrightsyAccessToken({
163
+ endpoint: team.endpoint || "https://brightsy.ai",
164
+ refreshToken: team.refresh_token,
165
+ clientId: cfg?.oauth_client_id
186
166
  });
187
- if (!res.ok) return team;
188
- const data = await res.json();
189
- if (!data.access_token) return team;
167
+ if (!grant) return team;
190
168
  return {
191
169
  ...team,
192
- access_token: data.access_token,
193
- refresh_token: data.refresh_token || team.refresh_token,
194
- expires_at: data.expires_in ? Date.now() + data.expires_in * 1e3 : team.expires_at
170
+ access_token: grant.access_token,
171
+ refresh_token: grant.refresh_token || team.refresh_token,
172
+ expires_at: grant.expires_at ?? team.expires_at
195
173
  };
196
174
  }
197
175
  async function ensureConnectedBrightsyTeamTokens() {
176
+ await ensureBrightsyLocalConfigFresh().catch(() => null);
198
177
  const teams = readStore();
199
178
  if (teams.length === 0) return [];
200
179
  const next = [];
201
180
  let changed = false;
202
181
  for (const team of teams) {
203
- const expired = typeof team.expires_at === "number" && Date.now() >= team.expires_at - 6e4;
204
- if (!expired) {
182
+ if (!team.refresh_token || !brightsyAccessTokenNeedsRefresh(team.expires_at)) {
205
183
  next.push(team);
206
184
  continue;
207
185
  }
208
186
  const refreshed = await refreshTeamToken(team);
209
- if (refreshed.access_token !== team.access_token) changed = true;
187
+ if (refreshed.access_token !== team.access_token || refreshed.refresh_token !== team.refresh_token || refreshed.expires_at !== team.expires_at) {
188
+ changed = true;
189
+ }
210
190
  next.push(refreshed);
211
191
  }
212
192
  if (changed) writeStore(next);
@@ -294,9 +274,6 @@ function brightsyMcpServerName(slug) {
294
274
  }
295
275
 
296
276
  export {
297
- brightsyConfigPath,
298
- loadBrightsyConfig,
299
- saveBrightsyConfig,
300
277
  listBrightsyAccounts,
301
278
  getBrightsySession,
302
279
  switchBrightsyAccount,
@@ -8,6 +8,7 @@ import {
8
8
  contextTokens,
9
9
  fallbackTurnFailDetail,
10
10
  finalizeParts,
11
+ formatAgentErrorContinuePrompt,
11
12
  formatTurnExitError,
12
13
  getAdapter,
13
14
  isBrightsyNdjsonLine,
@@ -23,13 +24,14 @@ import {
23
24
  partsToAssistantText,
24
25
  pushTurnStderr,
25
26
  resolveQuotaFallbackAgent,
27
+ shouldFeedErrorBackToAgent,
26
28
  shouldRetryFailedAgentTurn,
27
29
  stripBrightsyNdjsonNoise,
28
30
  sumUsageList,
29
31
  summarizeTurnStderr,
30
32
  toolDescription,
31
33
  turnFailChatText
32
- } from "./chunk-ZMUOPTKZ.js";
34
+ } from "./chunk-PK4PQUPU.js";
33
35
  import {
34
36
  addWorkspace,
35
37
  ensureWorkspace,
@@ -380,7 +382,7 @@ async function continueSourceThread(threadId, prompt) {
380
382
  await continueOnReply(threadId, prompt);
381
383
  return;
382
384
  }
383
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-3ZSFTG72.js");
385
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-JA65XKB6.js");
384
386
  await getOrchestrator2().send(threadId, prompt);
385
387
  } catch {
386
388
  }
@@ -2288,7 +2290,49 @@ function isMergedPrState(prUrl, prState) {
2288
2290
  if (!prUrl?.trim()) return false;
2289
2291
  return (prState ?? "").trim().toUpperCase() === "MERGED";
2290
2292
  }
2291
- function groupHomeBoardWorktrees(threads) {
2293
+ var DEFAULT_WORKTREE_SORT = "created";
2294
+ function groupCreatedAt(group) {
2295
+ let min = group[0]?.createdAt ?? "";
2296
+ for (const t of group) {
2297
+ if (t.createdAt && t.createdAt < min) min = t.createdAt;
2298
+ }
2299
+ return min;
2300
+ }
2301
+ function groupUpdatedAt(group) {
2302
+ let max = group[0]?.updatedAt ?? "";
2303
+ for (const t of group) {
2304
+ if (t.updatedAt && t.updatedAt > max) max = t.updatedAt;
2305
+ }
2306
+ return max;
2307
+ }
2308
+ function sortThreadsInWorktree(list) {
2309
+ return [...list].sort((a, b) => {
2310
+ const created = a.createdAt.localeCompare(b.createdAt);
2311
+ if (created !== 0) return created;
2312
+ return a.id.localeCompare(b.id);
2313
+ });
2314
+ }
2315
+ function compareWorktreeGroups(a, b, mode, labelOf) {
2316
+ if (mode === "name") {
2317
+ const named = (labelOf?.(a) ?? a[0]?.id ?? "").localeCompare(
2318
+ labelOf?.(b) ?? b[0]?.id ?? "",
2319
+ void 0,
2320
+ { sensitivity: "base" }
2321
+ );
2322
+ if (named !== 0) return named;
2323
+ } else if (mode === "activity") {
2324
+ const activity = groupUpdatedAt(b).localeCompare(groupUpdatedAt(a));
2325
+ if (activity !== 0) return activity;
2326
+ } else {
2327
+ const created = groupCreatedAt(b).localeCompare(groupCreatedAt(a));
2328
+ if (created !== 0) return created;
2329
+ }
2330
+ return (a[0]?.id ?? "").localeCompare(b[0]?.id ?? "");
2331
+ }
2332
+ function defaultWorktreeGroupLabel(group) {
2333
+ return worktreeDisplayLabelForGroup(group);
2334
+ }
2335
+ function groupHomeBoardWorktrees(threads, sort = DEFAULT_WORKTREE_SORT, labelOf = defaultWorktreeGroupLabel) {
2292
2336
  const map = /* @__PURE__ */ new Map();
2293
2337
  for (const t of threads) {
2294
2338
  const key = normalizeWorktreePath(t.worktreePath);
@@ -2296,7 +2340,7 @@ function groupHomeBoardWorktrees(threads) {
2296
2340
  list.push(t);
2297
2341
  map.set(key, list);
2298
2342
  }
2299
- return [...map.values()].map((list) => list.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))).sort((a, b) => (b[0]?.updatedAt ?? "").localeCompare(a[0]?.updatedAt ?? ""));
2343
+ return [...map.values()].map((list) => sortThreadsInWorktree(list)).sort((a, b) => compareWorktreeGroups(a, b, sort, labelOf));
2300
2344
  }
2301
2345
  function classifyWorktreeColumn(group) {
2302
2346
  if (group.some((t) => isMergedPrState(t.prUrl, t.prState))) return "done";
@@ -2597,8 +2641,7 @@ function assembleHomeBoard(input) {
2597
2641
  const kind = input.kind ?? "all";
2598
2642
  const limit = Math.max(1, input.limit ?? BOARD_PAGE_SIZE);
2599
2643
  const wsName = input.workspaceName ?? (() => "");
2600
- const byUpdated = (a, b) => b.updatedAt.localeCompare(a.updatedAt);
2601
- const live = input.threads.filter((t) => t.status !== "archived" && isHomeBoardThread(t)).sort(byUpdated);
2644
+ const live = input.threads.filter((t) => t.status !== "archived" && isHomeBoardThread(t));
2602
2645
  const columns = emptyColumns();
2603
2646
  const hidden = emptyHidden();
2604
2647
  const totals = {
@@ -2796,7 +2839,7 @@ async function createThread(input, _onSetupLine) {
2796
2839
  return readThread(thread.id) ?? thread;
2797
2840
  }
2798
2841
  async function listLinearIssues(agent, repoPath) {
2799
- const { getAdapter: getAdapter2 } = await import("./agents-7BUQY2WX.js");
2842
+ const { getAdapter: getAdapter2 } = await import("./agents-LGJLZMM3.js");
2800
2843
  await requireAgent(agent, { requireLinear: true });
2801
2844
  const adapter = getAdapter2(agent);
2802
2845
  if (!adapter.listLinearIssues) {
@@ -5341,7 +5384,7 @@ function formatScheduledPrompt(name, prompt) {
5341
5384
  ${prompt}`;
5342
5385
  }
5343
5386
  async function defaultDeps() {
5344
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-3ZSFTG72.js");
5387
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-JA65XKB6.js");
5345
5388
  const orch = getOrchestrator2();
5346
5389
  return {
5347
5390
  findThread: (id) => findThreadByRef(id),
@@ -5492,6 +5535,11 @@ var Orchestrator = class {
5492
5535
  turnBaselines = /* @__PURE__ */ new Map();
5493
5536
  /** Timers for orchestration session-quota auto-resume. */
5494
5537
  quotaResumeTimers = /* @__PURE__ */ new Map();
5538
+ /**
5539
+ * Threads that already got a crash-continue turn. Cleared on a successful
5540
+ * finish or a user send so a later crash can recover again.
5541
+ */
5542
+ crashContinued = /* @__PURE__ */ new Set();
5495
5543
  maxConcurrent;
5496
5544
  runningCount = 0;
5497
5545
  constructor(opts) {
@@ -5705,6 +5753,29 @@ var Orchestrator = class {
5705
5753
  );
5706
5754
  }
5707
5755
  }
5756
+ /**
5757
+ * Cursor-style recovery: after a runner crash, feed the error back as the
5758
+ * next prompt so the same agent can continue instead of sitting on lastError.
5759
+ */
5760
+ maybeEnqueueCrashContinue(threadId, opts) {
5761
+ if (this.crashContinued.has(threadId)) return;
5762
+ if (this.haltDrain.has(threadId)) return;
5763
+ if (!shouldFeedErrorBackToAgent({
5764
+ detail: opts.detail,
5765
+ assistantText: opts.assistantText,
5766
+ partsCount: opts.partsCount
5767
+ })) {
5768
+ return;
5769
+ }
5770
+ const thread = readThread(threadId);
5771
+ if (!thread || thread.status === "archived") return;
5772
+ this.crashContinued.add(threadId);
5773
+ const prompt = formatAgentErrorContinuePrompt(opts.detail);
5774
+ const queue = [prompt, ...thread.queue];
5775
+ updateThread(threadId, { queue });
5776
+ this.emit({ type: "queue_changed", threadId, queue });
5777
+ this.haltDrain.delete(threadId);
5778
+ }
5708
5779
  getThreads(includeArchived = false) {
5709
5780
  return listThreads({ includeArchived });
5710
5781
  }
@@ -5804,15 +5875,20 @@ var Orchestrator = class {
5804
5875
  throw new Error(`Thread is archived: ${thread.id}`);
5805
5876
  }
5806
5877
  const queue = [...current.queue, prompt];
5878
+ this.crashContinued.delete(thread.id);
5807
5879
  this.haltDrain.delete(thread.id);
5808
- const patch = { queue, status: "queued" };
5880
+ const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
5881
+ const patch = { queue };
5882
+ if (!inFlight) patch.status = "queued";
5809
5883
  const pid = current.agentPid;
5810
5884
  if (typeof pid === "number" && pid > 0 && !isPidAlive(pid)) {
5811
5885
  patch.agentPid = null;
5812
5886
  }
5813
5887
  updateThread(thread.id, patch);
5814
5888
  this.emit({ type: "queue_changed", threadId: thread.id, queue });
5815
- this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
5889
+ if (!inFlight) {
5890
+ this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
5891
+ }
5816
5892
  if (thisProcessShouldDrainAgentQueues()) {
5817
5893
  void this.drainQueue(thread.id);
5818
5894
  }
@@ -6133,7 +6209,7 @@ var Orchestrator = class {
6133
6209
  let parts = result.parts;
6134
6210
  let usage = result.usage ?? void 0;
6135
6211
  let exitCode = result.exitCode;
6136
- if (this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
6212
+ if (!this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
6137
6213
  const sessionId = result.sessionId || this.requireThread(threadId).sessionId || "";
6138
6214
  if (sessionId) {
6139
6215
  const { recoverFinishedCursorRun } = await import("./cursor-recover-NOQYOFRF.js");
@@ -6154,7 +6230,7 @@ var Orchestrator = class {
6154
6230
  }
6155
6231
  let lastStderr = summarizeTurnStderr(stderrTail);
6156
6232
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
6157
- if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent !== "brightsy" && shouldRetryFailedAgentTurn(detail, {
6233
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && shouldRetryFailedAgentTurn(detail, {
6158
6234
  hasSession: Boolean(this.requireThread(threadId).sessionId)
6159
6235
  })) {
6160
6236
  updateThread(threadId, { sessionId: null });
@@ -6250,7 +6326,11 @@ var Orchestrator = class {
6250
6326
  )) {
6251
6327
  updateThread(threadId, { sessionId: null });
6252
6328
  }
6253
- await syncThreadBranchFromGit(threadId);
6329
+ if (this.stoppedTurns.has(threadId)) {
6330
+ void syncThreadBranchFromGit(threadId).catch(() => void 0);
6331
+ } else {
6332
+ await syncThreadBranchFromGit(threadId);
6333
+ }
6254
6334
  if (this.stoppedTurns.has(threadId)) {
6255
6335
  const stopped = writeLiveStatus(threadId, "stopped");
6256
6336
  if (stopped?.status === "stopped") {
@@ -6274,9 +6354,16 @@ var Orchestrator = class {
6274
6354
  });
6275
6355
  }
6276
6356
  this.emit({ type: "turn_finished", threadId, exitCode });
6277
- if (exitCode !== 0) {
6357
+ if (exitCode === 0) {
6358
+ this.crashContinued.delete(threadId);
6359
+ } else {
6278
6360
  const blob = [chatText, detail].filter(Boolean).join("\n");
6279
6361
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
6362
+ this.maybeEnqueueCrashContinue(threadId, {
6363
+ detail: detail || failDetail,
6364
+ assistantText: chatText,
6365
+ partsCount: parts.length
6366
+ });
6280
6367
  }
6281
6368
  }
6282
6369
  } catch (err) {
@@ -6296,6 +6383,11 @@ var Orchestrator = class {
6296
6383
  }
6297
6384
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
6298
6385
  void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
6386
+ this.maybeEnqueueCrashContinue(threadId, {
6387
+ detail: message,
6388
+ assistantText: "",
6389
+ partsCount: 0
6390
+ });
6299
6391
  }
6300
6392
  } finally {
6301
6393
  this.startingTurns.delete(threadId);
@@ -18,7 +18,7 @@ import {
18
18
  stripBrightsyNdjsonNoise,
19
19
  sumUsageList,
20
20
  toolDescription
21
- } from "./chunk-NV73AO7Q.js";
21
+ } from "./chunk-MGYCQAZ6.js";
22
22
  import {
23
23
  addWorkspace,
24
24
  ensureWorkspace,
@@ -56,15 +56,17 @@ import {
56
56
  import {
57
57
  applyAgentRunnerHeapEnv,
58
58
  fallbackTurnFailDetail,
59
+ formatAgentErrorContinuePrompt,
59
60
  formatTurnExitError,
60
61
  looksLikeAgentFailureMessage,
61
62
  looksLikeInvalidAgentSession,
62
63
  looksLikeV8Oom,
63
64
  pushTurnStderr,
65
+ shouldFeedErrorBackToAgent,
64
66
  shouldRetryFailedAgentTurn,
65
67
  summarizeTurnStderr,
66
68
  turnFailChatText
67
- } from "./chunk-IUJOI7KK.js";
69
+ } from "./chunk-FSCQXMGI.js";
68
70
  import {
69
71
  releaseCaffeinateHoldForThread
70
72
  } from "./chunk-DCOZABNT.js";
@@ -441,7 +443,7 @@ async function continueSourceThread(threadId, prompt) {
441
443
  await continueOnReply(threadId, prompt);
442
444
  return;
443
445
  }
444
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-G6DU3AF3.js");
446
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-5TN2DJWO.js");
445
447
  await getOrchestrator2().send(threadId, prompt);
446
448
  } catch {
447
449
  }
@@ -2320,7 +2322,49 @@ function isMergedPrState(prUrl, prState) {
2320
2322
  if (!prUrl?.trim()) return false;
2321
2323
  return (prState ?? "").trim().toUpperCase() === "MERGED";
2322
2324
  }
2323
- function groupHomeBoardWorktrees(threads) {
2325
+ var DEFAULT_WORKTREE_SORT = "created";
2326
+ function groupCreatedAt(group) {
2327
+ let min = group[0]?.createdAt ?? "";
2328
+ for (const t of group) {
2329
+ if (t.createdAt && t.createdAt < min) min = t.createdAt;
2330
+ }
2331
+ return min;
2332
+ }
2333
+ function groupUpdatedAt(group) {
2334
+ let max = group[0]?.updatedAt ?? "";
2335
+ for (const t of group) {
2336
+ if (t.updatedAt && t.updatedAt > max) max = t.updatedAt;
2337
+ }
2338
+ return max;
2339
+ }
2340
+ function sortThreadsInWorktree(list) {
2341
+ return [...list].sort((a, b) => {
2342
+ const created = a.createdAt.localeCompare(b.createdAt);
2343
+ if (created !== 0) return created;
2344
+ return a.id.localeCompare(b.id);
2345
+ });
2346
+ }
2347
+ function compareWorktreeGroups(a, b, mode, labelOf) {
2348
+ if (mode === "name") {
2349
+ const named = (labelOf?.(a) ?? a[0]?.id ?? "").localeCompare(
2350
+ labelOf?.(b) ?? b[0]?.id ?? "",
2351
+ void 0,
2352
+ { sensitivity: "base" }
2353
+ );
2354
+ if (named !== 0) return named;
2355
+ } else if (mode === "activity") {
2356
+ const activity = groupUpdatedAt(b).localeCompare(groupUpdatedAt(a));
2357
+ if (activity !== 0) return activity;
2358
+ } else {
2359
+ const created = groupCreatedAt(b).localeCompare(groupCreatedAt(a));
2360
+ if (created !== 0) return created;
2361
+ }
2362
+ return (a[0]?.id ?? "").localeCompare(b[0]?.id ?? "");
2363
+ }
2364
+ function defaultWorktreeGroupLabel(group) {
2365
+ return worktreeDisplayLabelForGroup(group);
2366
+ }
2367
+ function groupHomeBoardWorktrees(threads, sort = DEFAULT_WORKTREE_SORT, labelOf = defaultWorktreeGroupLabel) {
2324
2368
  const map = /* @__PURE__ */ new Map();
2325
2369
  for (const t of threads) {
2326
2370
  const key = normalizeWorktreePath(t.worktreePath);
@@ -2328,7 +2372,7 @@ function groupHomeBoardWorktrees(threads) {
2328
2372
  list.push(t);
2329
2373
  map.set(key, list);
2330
2374
  }
2331
- return [...map.values()].map((list) => list.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))).sort((a, b) => (b[0]?.updatedAt ?? "").localeCompare(a[0]?.updatedAt ?? ""));
2375
+ return [...map.values()].map((list) => sortThreadsInWorktree(list)).sort((a, b) => compareWorktreeGroups(a, b, sort, labelOf));
2332
2376
  }
2333
2377
  function classifyWorktreeColumn(group) {
2334
2378
  if (group.some((t) => isMergedPrState(t.prUrl, t.prState))) return "done";
@@ -2636,8 +2680,7 @@ function assembleHomeBoard(input) {
2636
2680
  const kind = input.kind ?? "all";
2637
2681
  const limit = Math.max(1, input.limit ?? BOARD_PAGE_SIZE);
2638
2682
  const wsName = input.workspaceName ?? (() => "");
2639
- const byUpdated = (a, b) => b.updatedAt.localeCompare(a.updatedAt);
2640
- const live = input.threads.filter((t) => t.status !== "archived" && isHomeBoardThread(t)).sort(byUpdated);
2683
+ const live = input.threads.filter((t) => t.status !== "archived" && isHomeBoardThread(t));
2641
2684
  const columns = emptyColumns();
2642
2685
  const hidden = emptyHidden();
2643
2686
  const totals = {
@@ -2835,7 +2878,7 @@ async function createThread(input, _onSetupLine) {
2835
2878
  return readThread(thread.id) ?? thread;
2836
2879
  }
2837
2880
  async function listLinearIssues(agent, repoPath) {
2838
- const { getAdapter: getAdapter2 } = await import("./agents-X2I3RNCY.js");
2881
+ const { getAdapter: getAdapter2 } = await import("./agents-CI6NGKRO.js");
2839
2882
  await requireAgent(agent, { requireLinear: true });
2840
2883
  const adapter = getAdapter2(agent);
2841
2884
  if (!adapter.listLinearIssues) {
@@ -5506,7 +5549,7 @@ function formatScheduledPrompt(name, prompt) {
5506
5549
  ${prompt}`;
5507
5550
  }
5508
5551
  async function defaultDeps() {
5509
- const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-G6DU3AF3.js");
5552
+ const { getOrchestrator: getOrchestrator2, startOrchestration: startOrchestration2 } = await import("./orchestrator-5TN2DJWO.js");
5510
5553
  const orch = getOrchestrator2();
5511
5554
  return {
5512
5555
  findThread: (id) => findThreadByRef(id),
@@ -5657,6 +5700,11 @@ var Orchestrator = class {
5657
5700
  turnBaselines = /* @__PURE__ */ new Map();
5658
5701
  /** Timers for orchestration session-quota auto-resume. */
5659
5702
  quotaResumeTimers = /* @__PURE__ */ new Map();
5703
+ /**
5704
+ * Threads that already got a crash-continue turn. Cleared on a successful
5705
+ * finish or a user send so a later crash can recover again.
5706
+ */
5707
+ crashContinued = /* @__PURE__ */ new Set();
5660
5708
  maxConcurrent;
5661
5709
  runningCount = 0;
5662
5710
  constructor(opts) {
@@ -5870,6 +5918,29 @@ var Orchestrator = class {
5870
5918
  );
5871
5919
  }
5872
5920
  }
5921
+ /**
5922
+ * Cursor-style recovery: after a runner crash, feed the error back as the
5923
+ * next prompt so the same agent can continue instead of sitting on lastError.
5924
+ */
5925
+ maybeEnqueueCrashContinue(threadId, opts) {
5926
+ if (this.crashContinued.has(threadId)) return;
5927
+ if (this.haltDrain.has(threadId)) return;
5928
+ if (!shouldFeedErrorBackToAgent({
5929
+ detail: opts.detail,
5930
+ assistantText: opts.assistantText,
5931
+ partsCount: opts.partsCount
5932
+ })) {
5933
+ return;
5934
+ }
5935
+ const thread = readThread(threadId);
5936
+ if (!thread || thread.status === "archived") return;
5937
+ this.crashContinued.add(threadId);
5938
+ const prompt = formatAgentErrorContinuePrompt(opts.detail);
5939
+ const queue = [prompt, ...thread.queue];
5940
+ updateThread(threadId, { queue });
5941
+ this.emit({ type: "queue_changed", threadId, queue });
5942
+ this.haltDrain.delete(threadId);
5943
+ }
5873
5944
  getThreads(includeArchived = false) {
5874
5945
  return listThreads({ includeArchived });
5875
5946
  }
@@ -5969,15 +6040,20 @@ var Orchestrator = class {
5969
6040
  throw new Error(`Thread is archived: ${thread.id}`);
5970
6041
  }
5971
6042
  const queue = [...current.queue, prompt];
6043
+ this.crashContinued.delete(thread.id);
5972
6044
  this.haltDrain.delete(thread.id);
5973
- const patch = { queue, status: "queued" };
6045
+ const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
6046
+ const patch = { queue };
6047
+ if (!inFlight) patch.status = "queued";
5974
6048
  const pid = current.agentPid;
5975
6049
  if (typeof pid === "number" && pid > 0 && !isPidAlive(pid)) {
5976
6050
  patch.agentPid = null;
5977
6051
  }
5978
6052
  updateThread(thread.id, patch);
5979
6053
  this.emit({ type: "queue_changed", threadId: thread.id, queue });
5980
- this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
6054
+ if (!inFlight) {
6055
+ this.emit({ type: "status_changed", threadId: thread.id, status: "queued" });
6056
+ }
5981
6057
  if (thisProcessShouldDrainAgentQueues()) {
5982
6058
  void this.drainQueue(thread.id);
5983
6059
  }
@@ -6298,7 +6374,7 @@ var Orchestrator = class {
6298
6374
  let parts = result.parts;
6299
6375
  let usage = result.usage ?? void 0;
6300
6376
  let exitCode = result.exitCode;
6301
- if (this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
6377
+ if (!this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent === "cursor" && exitCode !== 0 && !assistantText && parts.length === 0) {
6302
6378
  const sessionId = result.sessionId || this.requireThread(threadId).sessionId || "";
6303
6379
  if (sessionId) {
6304
6380
  const { recoverFinishedCursorRun } = await import("./cursor-recover-QSLWEZNA.js");
@@ -6319,7 +6395,7 @@ var Orchestrator = class {
6319
6395
  }
6320
6396
  let lastStderr = summarizeTurnStderr(stderrTail);
6321
6397
  let detail = lastStderr || (exitCode !== 0 ? fallbackTurnFailDetail(assistantText) : "");
6322
- if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && this.requireThread(threadId).agent !== "brightsy" && shouldRetryFailedAgentTurn(detail, {
6398
+ if (exitCode !== 0 && !assistantText && parts.length === 0 && !this.stoppedTurns.has(threadId) && shouldRetryFailedAgentTurn(detail, {
6323
6399
  hasSession: Boolean(this.requireThread(threadId).sessionId)
6324
6400
  })) {
6325
6401
  updateThread(threadId, { sessionId: null });
@@ -6415,7 +6491,11 @@ var Orchestrator = class {
6415
6491
  )) {
6416
6492
  updateThread(threadId, { sessionId: null });
6417
6493
  }
6418
- await syncThreadBranchFromGit(threadId);
6494
+ if (this.stoppedTurns.has(threadId)) {
6495
+ void syncThreadBranchFromGit(threadId).catch(() => void 0);
6496
+ } else {
6497
+ await syncThreadBranchFromGit(threadId);
6498
+ }
6419
6499
  if (this.stoppedTurns.has(threadId)) {
6420
6500
  const stopped = writeLiveStatus(threadId, "stopped");
6421
6501
  if (stopped?.status === "stopped") {
@@ -6439,9 +6519,16 @@ var Orchestrator = class {
6439
6519
  });
6440
6520
  }
6441
6521
  this.emit({ type: "turn_finished", threadId, exitCode });
6442
- if (exitCode !== 0) {
6522
+ if (exitCode === 0) {
6523
+ this.crashContinued.delete(threadId);
6524
+ } else {
6443
6525
  const blob = [chatText, detail].filter(Boolean).join("\n");
6444
6526
  void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
6527
+ this.maybeEnqueueCrashContinue(threadId, {
6528
+ detail: detail || failDetail,
6529
+ assistantText: chatText,
6530
+ partsCount: parts.length
6531
+ });
6445
6532
  }
6446
6533
  }
6447
6534
  } catch (err) {
@@ -6461,6 +6548,11 @@ var Orchestrator = class {
6461
6548
  }
6462
6549
  this.emit({ type: "turn_finished", threadId, exitCode: 1 });
6463
6550
  void this.maybeHandleOrchestrationQuotaFailover(threadId, message);
6551
+ this.maybeEnqueueCrashContinue(threadId, {
6552
+ detail: message,
6553
+ assistantText: "",
6554
+ partsCount: 0
6555
+ });
6464
6556
  }
6465
6557
  } finally {
6466
6558
  this.startingTurns.delete(threadId);
@@ -7593,6 +7685,7 @@ export {
7593
7685
  isHomeBoardThread,
7594
7686
  BOARD_COLUMN_DEFS,
7595
7687
  HOME_BOARD_CACHE_TTL_MS,
7688
+ DEFAULT_WORKTREE_SORT,
7596
7689
  groupHomeBoardWorktrees,
7597
7690
  classifyWorktreeColumn,
7598
7691
  worktreeBoardStatus,
@@ -9,7 +9,8 @@ import {
9
9
  ensureConnectedBrightsyTeamTokens,
10
10
  getConnectedBrightsyTeamsRaw,
11
11
  listConnectedBrightsyTeams
12
- } from "./chunk-YMRT2DU6.js";
12
+ } from "./chunk-LLX3OMHU.js";
13
+ import "./chunk-R7FGPBKL.js";
13
14
  import "./chunk-KPIYENTF.js";
14
15
  import "./chunk-BD2ICC4D.js";
15
16
  export {