@sideboard-ai/core 0.1.283 → 0.1.285

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.
Files changed (51) hide show
  1. package/dist/{abletime-WLDBJJUI.js → abletime-4CH2IOX5.js} +4 -4
  2. package/dist/{abletime-X64E3GUJ.js → abletime-I4RODUKT.js} +4 -4
  3. package/dist/{abletime-oauth-AXR676W5.js → abletime-oauth-7OZZ3ABX.js} +3 -3
  4. package/dist/{abletime-oauth-UWOCEICP.js → abletime-oauth-C2HN2Q6F.js} +3 -3
  5. package/dist/{abletime-rest-S4XWEXV2.js → abletime-rest-E4LBRFLM.js} +3 -3
  6. package/dist/{abletime-rest-LPUDJUEP.js → abletime-rest-WFI2HWBU.js} +3 -3
  7. package/dist/agents/cursor-runner.cjs +54 -14
  8. package/dist/agents/cursor-runner.js +51 -14
  9. package/dist/{agents-XA2E64AW.js → agents-FKMYAKFD.js} +5 -5
  10. package/dist/{agents-2IE4HKHA.js → agents-ITNCZ6CA.js} +7 -7
  11. package/dist/chunk-2GPQZZLY.js +59 -0
  12. package/dist/{chunk-BYZ7F2BQ.js → chunk-2K723QZH.js} +12 -10
  13. package/dist/{chunk-ROPGQRPU.js → chunk-33KDPEK4.js} +1 -1
  14. package/dist/{chunk-W7HIK2A6.js → chunk-5QOLFOAX.js} +19 -3
  15. package/dist/{chunk-I6HSGKMZ.js → chunk-5T4YWFLD.js} +2 -2
  16. package/dist/{chunk-U7HAVAGJ.js → chunk-6CVVNAIU.js} +2 -2
  17. package/dist/{chunk-TQRSVE7D.js → chunk-7IAMHN2N.js} +1 -1
  18. package/dist/{chunk-DQZPF6WL.js → chunk-BMUVNTT4.js} +3 -3
  19. package/dist/{chunk-2KNO2GJQ.js → chunk-C6NVTLZK.js} +16 -14
  20. package/dist/{chunk-VA55JG2I.js → chunk-DEJALWKM.js} +5 -5
  21. package/dist/{chunk-XTPQEZZA.js → chunk-IJMUYHPT.js} +3 -3
  22. package/dist/chunk-JAT3JFOS.js +61 -0
  23. package/dist/{chunk-EIEAR62M.js → chunk-LMWPVC5L.js} +2 -2
  24. package/dist/{chunk-DGPROCE5.js → chunk-LS4FFCPL.js} +3 -3
  25. package/dist/{chunk-P2BSMNHB.js → chunk-PXMLMBMI.js} +1 -1
  26. package/dist/{chunk-UZUXWBXK.js → chunk-S2OOM7AU.js} +1 -1
  27. package/dist/{chunk-BS3QLVPL.js → chunk-TMM4FEZ5.js} +3 -3
  28. package/dist/{chunk-ADXVTKLC.js → chunk-TXHZJESB.js} +4 -4
  29. package/dist/{chunk-K2S33ZCB.js → chunk-U6QDXT4K.js} +20 -4
  30. package/dist/{chunk-GWFVCCJZ.js → chunk-VGNLMGBK.js} +6 -6
  31. package/dist/{chunk-IFURHRAR.js → chunk-VOIWX5HA.js} +1 -1
  32. package/dist/{chunk-PY4UCMRI.js → chunk-VQEARG5H.js} +5 -5
  33. package/dist/{chunk-BDAUPNHL.js → chunk-XSDCLM3F.js} +1 -1
  34. package/dist/{chunk-OCUIZD4Y.js → chunk-XSPOJFTV.js} +6 -6
  35. package/dist/{conductor-U776JSXQ.js → conductor-GBX6LSP2.js} +3 -3
  36. package/dist/{conductor-CNSCOC54.js → conductor-MU3TYVCD.js} +2 -2
  37. package/dist/{connected-teams-JIUOSZ6C.js → connected-teams-AB7ZHJVO.js} +3 -3
  38. package/dist/{connected-teams-RU6VATCM.js → connected-teams-BJ6PMIST.js} +3 -3
  39. package/dist/index.cjs +241 -120
  40. package/dist/index.d.cts +11 -4
  41. package/dist/index.d.ts +11 -4
  42. package/dist/index.js +176 -101
  43. package/dist/mcp/run-stdio.cjs +68 -25
  44. package/dist/mcp/run-stdio.js +10 -10
  45. package/dist/{oauth-S3M7B475.js → oauth-DP4ZV2M2.js} +2 -2
  46. package/dist/{oauth-K6CWZEQO.js → oauth-WCUZ6Z5G.js} +2 -2
  47. package/dist/{orchestrator-FPGVKEID.js → orchestrator-F7KXYDQW.js} +9 -9
  48. package/dist/{orchestrator-HWJHK5NL.js → orchestrator-VUIRO6NC.js} +7 -7
  49. package/package.json +1 -1
  50. package/dist/chunk-N7WZ6NIN.js +0 -41
  51. package/dist/chunk-VKQGAWHY.js +0 -40
package/dist/index.js CHANGED
@@ -198,7 +198,7 @@ import {
198
198
  worktreeBoardStatus,
199
199
  worktreeCleanupSettings,
200
200
  writeWorktreeFile
201
- } from "./chunk-2KNO2GJQ.js";
201
+ } from "./chunk-C6NVTLZK.js";
202
202
  import {
203
203
  BASE_ALLOWED_TOOLS,
204
204
  BRIGHTSY_MCP_ALLOWED_TOOLS,
@@ -288,7 +288,7 @@ import {
288
288
  whichOnPath,
289
289
  worktreeMcpToolNames,
290
290
  writeInjectedMcpConfig
291
- } from "./chunk-OCUIZD4Y.js";
291
+ } from "./chunk-XSPOJFTV.js";
292
292
  import {
293
293
  brightsyMcpServerName,
294
294
  connectBrightsyTeam,
@@ -298,7 +298,7 @@ import {
298
298
  listBrightsyAccounts,
299
299
  listConnectedBrightsyTeams,
300
300
  switchBrightsyAccount
301
- } from "./chunk-GWFVCCJZ.js";
301
+ } from "./chunk-VGNLMGBK.js";
302
302
  import {
303
303
  BRIGHTSY_SUMMARIZE_CONTEXT_TOOL,
304
304
  CHARS_PER_CONTEXT_TOKEN,
@@ -415,7 +415,7 @@ import {
415
415
  workspaceAgentChatSummaries,
416
416
  worktreeRunPortsPath,
417
417
  wrapReviewSkillMarkdown
418
- } from "./chunk-DQZPF6WL.js";
418
+ } from "./chunk-BMUVNTT4.js";
419
419
  import {
420
420
  STDERR_EVENT_MAX_CHARS,
421
421
  STDOUT_EVENT_MAX_CHARS,
@@ -457,7 +457,7 @@ import {
457
457
  visibleToolRowDetail,
458
458
  withEventParentId,
459
459
  withEventsParentId
460
- } from "./chunk-BDAUPNHL.js";
460
+ } from "./chunk-XSDCLM3F.js";
461
461
  import "./chunk-7H655NEW.js";
462
462
  import {
463
463
  ABLETIME_OAUTH_CALLBACK_PATH,
@@ -481,7 +481,7 @@ import {
481
481
  parseAbleTimeOAuthCallbackUrl,
482
482
  resolveAbleTimeOAuthHost,
483
483
  startAbleTimeOAuth
484
- } from "./chunk-XTPQEZZA.js";
484
+ } from "./chunk-IJMUYHPT.js";
485
485
  import {
486
486
  ableTimeDependencyTarget,
487
487
  commentAbleTimeTask,
@@ -506,8 +506,8 @@ import {
506
506
  toAbleTimeIssueInfo,
507
507
  updateAbleTimeTask,
508
508
  verifyAbleTimeConnection
509
- } from "./chunk-DGPROCE5.js";
510
- import "./chunk-U7HAVAGJ.js";
509
+ } from "./chunk-LS4FFCPL.js";
510
+ import "./chunk-6CVVNAIU.js";
511
511
  import {
512
512
  ABLETIME_MCP_PATH,
513
513
  ABLETIME_MCP_PM_PATH,
@@ -528,7 +528,7 @@ import {
528
528
  normalizeAbleTimeCredential,
529
529
  normalizeAbleTimeHost,
530
530
  rewriteAbleTimeError
531
- } from "./chunk-VA55JG2I.js";
531
+ } from "./chunk-DEJALWKM.js";
532
532
  import {
533
533
  LEGACY_PLAN_FILE_REL,
534
534
  PLAN_FILE_NAME,
@@ -728,15 +728,16 @@ import {
728
728
  refreshBrightsyAccessToken,
729
729
  registerBrightsyOAuthClient,
730
730
  saveBrightsyConfig
731
- } from "./chunk-ROPGQRPU.js";
731
+ } from "./chunk-33KDPEK4.js";
732
732
  import {
733
+ currentHttpFetch,
733
734
  formatFetchError,
734
735
  httpFetch,
735
736
  setHttpFetchImpl
736
- } from "./chunk-N7WZ6NIN.js";
737
+ } from "./chunk-JAT3JFOS.js";
737
738
  import {
738
739
  clipMcpToolResult
739
- } from "./chunk-UZUXWBXK.js";
740
+ } from "./chunk-S2OOM7AU.js";
740
741
  import "./chunk-AXQ4ZWHK.js";
741
742
  import {
742
743
  AGENT_RUNNER_MAX_OLD_SPACE_MB,
@@ -752,8 +753,8 @@ import {
752
753
  trustSystemCertificates,
753
754
  turnCostUsdFromCursorUsage,
754
755
  withMaxOldSpaceSize
755
- } from "./chunk-IFURHRAR.js";
756
- import "./chunk-K2S33ZCB.js";
756
+ } from "./chunk-VOIWX5HA.js";
757
+ import "./chunk-U6QDXT4K.js";
757
758
  import {
758
759
  ATTACHMENTS_DIR,
759
760
  CLI_DIR,
@@ -5015,7 +5016,7 @@ async function handleJobStop(params, _ctx) {
5015
5016
  return stopDetachedJob(jobCwd(rec), id, reason ? { reason } : void 0);
5016
5017
  }
5017
5018
  async function defaultTurnWait(ref, timeoutMs) {
5018
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-FPGVKEID.js");
5019
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-F7KXYDQW.js");
5019
5020
  const orch = getOrchestrator2();
5020
5021
  const thread = await orch.waitForTurn(ref, timeoutMs, {
5021
5022
  resolveIfStillRunning: true
@@ -10113,7 +10114,7 @@ function deliverPhoneTurn(opts, chatId) {
10113
10114
  });
10114
10115
  }
10115
10116
 
10116
- // src/remote/listen.ts
10117
+ // src/remote/phone-turn.ts
10117
10118
  function formatRemotePrompt(text5) {
10118
10119
  return `Phone
10119
10120
 
@@ -10122,6 +10123,29 @@ ${text5.trim()}`;
10122
10123
  function isRemoteStopCommand(text5) {
10123
10124
  return text5.trim().toLowerCase() === "stop";
10124
10125
  }
10126
+ function phoneFollowUpQueues(status) {
10127
+ return followUpBehavior() === "queue" && (status === "running" || status === "queued");
10128
+ }
10129
+ var PHONE_TURN_MS = 14 * 60 * 1e3;
10130
+ function delay(ms) {
10131
+ return new Promise((resolve) => setTimeout(resolve, ms));
10132
+ }
10133
+ async function waitForPhoneTurn(threadId, stillCurrent) {
10134
+ const deadline = Date.now() + PHONE_TURN_MS;
10135
+ while (stillCurrent() && Date.now() < deadline) {
10136
+ const thread2 = readPhoneThread(threadId) ?? readThread(threadId);
10137
+ if (!thread2) return;
10138
+ if (thread2.status === "stopped" && thread2.queue.length > 0) {
10139
+ await delay(40);
10140
+ continue;
10141
+ }
10142
+ break;
10143
+ }
10144
+ if (!stillCurrent()) return;
10145
+ const thread = readPhoneThread(threadId) ?? readThread(threadId);
10146
+ if (!thread || thread.status !== "running" && thread.status !== "queued") return;
10147
+ await getOrchestrator().waitForTurn(threadId, Math.max(1, deadline - Date.now()));
10148
+ }
10125
10149
  var handleChain2 = Promise.resolve();
10126
10150
  var inboundGeneration = 0;
10127
10151
  function enqueue(fn) {
@@ -10145,6 +10169,77 @@ function interruptRemoteCoordinator(deviceId, log = () => void 0) {
10145
10169
  return false;
10146
10170
  }
10147
10171
  }
10172
+ function runCoordinatorPrompt(opts, body, generation) {
10173
+ const current = () => generation === inboundGeneration;
10174
+ return (async () => {
10175
+ if (!current()) return;
10176
+ const agent = coerceOrchestratorAgent(opts.agent ?? resolveOrchestratorDefaults().agent);
10177
+ if (!body) return;
10178
+ if (isRemoteStopCommand(body)) {
10179
+ if (!current()) return;
10180
+ opts.onOutbound({ type: "assistant", text: REMOTE_STOPPED_REPLY });
10181
+ return;
10182
+ }
10183
+ let thread = ensureRemoteCoordinator(opts.deviceId, agent);
10184
+ thread = readThread(thread.id) ?? thread;
10185
+ const followUp = followUpBehavior();
10186
+ const orch = getOrchestrator();
10187
+ try {
10188
+ await orch.send(thread.id, formatRemotePrompt(body), { followUp });
10189
+ if (!current()) return;
10190
+ await waitForPhoneTurn(thread.id, current);
10191
+ } catch (err) {
10192
+ if (!current()) return;
10193
+ const message = err instanceof Error ? err.message : String(err);
10194
+ opts.onOutbound({ type: "assistant", text: `Sideboard failed: ${message}` });
10195
+ return;
10196
+ }
10197
+ if (!current()) return;
10198
+ thread = readThread(thread.id) ?? thread;
10199
+ if (thread.status === "stopped") return;
10200
+ const result = orch.getTurnResult(thread.id);
10201
+ const pending = extractPendingPlanQuestions(
10202
+ [...thread.messages].reverse().find((m) => m.role === "agent")?.parts
10203
+ );
10204
+ if (result.taskState === "input-required" && pending) {
10205
+ opts.onOutbound({
10206
+ type: "ask_user",
10207
+ text: result.text.trim(),
10208
+ questions: pending.questions.map((q) => ({
10209
+ question: q.question,
10210
+ options: q.options.map(
10211
+ (o) => o.description ? { label: o.label, description: o.description } : { label: o.label }
10212
+ )
10213
+ }))
10214
+ });
10215
+ return;
10216
+ }
10217
+ const reply = outboundReplyFromTurn(result, {
10218
+ inputRequired: "Sideboard is waiting for an answer.",
10219
+ canceled: REMOTE_STOPPED_REPLY,
10220
+ completedEmpty: () => "",
10221
+ failed: (detail) => detail ? `Sideboard failed: ${detail}` : "Sideboard failed before producing a result."
10222
+ });
10223
+ if (reply.trim()) opts.onOutbound({ type: "assistant", text: reply.trim() });
10224
+ })();
10225
+ }
10226
+ function beginCoordinatorPrompt(body, opts) {
10227
+ const live = body ? findRemoteCoordinator(opts.deviceId) : null;
10228
+ const fresh = live ? readThread(live.id) ?? live : null;
10229
+ if (body && !isRemoteStopCommand(body) && phoneFollowUpQueues(fresh?.status)) {
10230
+ const generation2 = inboundGeneration;
10231
+ enqueue(() => runCoordinatorPrompt(opts, body, generation2));
10232
+ return;
10233
+ }
10234
+ const generation = ++inboundGeneration;
10235
+ if (body && isRemoteStopCommand(body)) {
10236
+ interruptRemoteCoordinator(opts.deviceId, opts.onLog ?? (() => void 0));
10237
+ }
10238
+ const task = runCoordinatorPrompt(opts, body, generation);
10239
+ enqueue(() => task);
10240
+ }
10241
+
10242
+ // src/remote/listen.ts
10148
10243
  function emitOpened(opts, opened) {
10149
10244
  setPhoneOpenChat(opened.chat.id);
10150
10245
  emitControl(opts, { op: "opened", ...opened });
@@ -10414,23 +10509,27 @@ function handlePhoneControl(cmd, opts) {
10414
10509
  return;
10415
10510
  }
10416
10511
  const chatId = cmd.chatId;
10512
+ const existing = readPhoneThread(chatId);
10513
+ if (phoneFollowUpQueues(existing?.status)) {
10514
+ deliverPhoneTurn(opts, chatId);
10515
+ const generation2 = chatGeneration.get(chatId) ?? bumpChat(chatId);
10516
+ enqueueChat(chatId, () => runPhonePrompt(opts, cmd, chatId, generation2));
10517
+ return;
10518
+ }
10417
10519
  const generation = bumpChat(chatId);
10418
10520
  phoneTurnGen.set(chatId, generation);
10419
- interruptChat(chatId, log);
10420
- enqueueChat(chatId, async () => {
10421
- if (chatGeneration.get(chatId) !== generation) return;
10521
+ const task = runPhonePrompt(opts, cmd, chatId, generation);
10522
+ enqueueChat(chatId, () => task);
10523
+ }
10524
+ function runPhonePrompt(opts, cmd, chatId, generation) {
10525
+ const current = () => chatGeneration.get(chatId) === generation;
10526
+ return (async () => {
10527
+ if (!current()) return;
10422
10528
  const thread = readPhoneThread(chatId);
10423
10529
  if (!thread) {
10424
10530
  emitControl(opts, { op: "error", chatId, message: "That agent is not on this Mac." });
10425
10531
  return;
10426
10532
  }
10427
- if (thread.status === "running" || thread.status === "queued") {
10428
- try {
10429
- getOrchestrator().stop(thread.id, { clearQueue: true });
10430
- } catch {
10431
- }
10432
- }
10433
- if (chatGeneration.get(chatId) !== generation) return;
10434
10533
  const stopStream = watchPhoneStream(opts, chatId, thread.id, generation);
10435
10534
  try {
10436
10535
  try {
@@ -10438,14 +10537,19 @@ function handlePhoneControl(cmd, opts) {
10438
10537
  } catch (err) {
10439
10538
  emitControl(opts, { op: "error", chatId, message: phoneError(err) });
10440
10539
  }
10540
+ if (!current()) return;
10441
10541
  const live = readPhoneThread(thread.id) ?? thread;
10542
+ const followUp = followUpBehavior();
10442
10543
  const staged = stagePhoneTurnFiles(live.worktreePath, cmd);
10443
10544
  const text5 = cmd.text.trim() || (staged.length ? "See the attached files." : "");
10444
- const formatted = formatRemotePrompt(text5);
10445
- await getOrchestrator().send(thread.id, formatted, { attachments: staged });
10446
- await getOrchestrator().waitForTurn(thread.id, 14 * 60 * 1e3);
10545
+ await getOrchestrator().send(thread.id, formatRemotePrompt(text5), {
10546
+ attachments: staged,
10547
+ followUp
10548
+ });
10549
+ if (!current()) return;
10550
+ await waitForPhoneTurn(thread.id, current);
10447
10551
  } catch (err) {
10448
- if (chatGeneration.get(chatId) !== generation) return;
10552
+ if (!current()) return;
10449
10553
  const message = err instanceof Error ? err.message : String(err);
10450
10554
  emitControl(opts, { op: "error", chatId, message: `Sideboard failed: ${message}` });
10451
10555
  return;
@@ -10453,12 +10557,11 @@ function handlePhoneControl(cmd, opts) {
10453
10557
  stopStream();
10454
10558
  finishPhoneTurn(chatId, generation);
10455
10559
  }
10456
- if (chatGeneration.get(chatId) !== generation) return;
10560
+ if (!current()) return;
10457
10561
  emitChatTurn(opts, chatId, thread.id);
10458
- });
10562
+ })();
10459
10563
  }
10460
10564
  function handleRemoteInbound(text5, opts) {
10461
- const log = opts.onLog ?? (() => void 0);
10462
10565
  const body = text5.trim();
10463
10566
  const control = takePhoneControl(body);
10464
10567
  if (control) {
@@ -10469,64 +10572,7 @@ function handleRemoteInbound(text5, opts) {
10469
10572
  handlePhoneControl(control, opts);
10470
10573
  return;
10471
10574
  }
10472
- const generation = ++inboundGeneration;
10473
- if (body) interruptRemoteCoordinator(opts.deviceId, log);
10474
- enqueue(async () => {
10475
- if (generation !== inboundGeneration) return;
10476
- const agent = coerceOrchestratorAgent(opts.agent ?? resolveOrchestratorDefaults().agent);
10477
- if (!body) return;
10478
- if (isRemoteStopCommand(body)) {
10479
- if (generation !== inboundGeneration) return;
10480
- opts.onOutbound({ type: "assistant", text: REMOTE_STOPPED_REPLY });
10481
- return;
10482
- }
10483
- let thread = ensureRemoteCoordinator(opts.deviceId, agent);
10484
- const fresh = readThread(thread.id) ?? thread;
10485
- if (fresh.status === "running" || fresh.status === "queued") {
10486
- try {
10487
- getOrchestrator().stop(fresh.id, { clearQueue: true });
10488
- } catch {
10489
- }
10490
- }
10491
- if (generation !== inboundGeneration) return;
10492
- const orch = getOrchestrator();
10493
- try {
10494
- await orch.send(thread.id, formatRemotePrompt(body));
10495
- await orch.waitForTurn(thread.id, 14 * 60 * 1e3);
10496
- } catch (err) {
10497
- if (generation !== inboundGeneration) return;
10498
- const message = err instanceof Error ? err.message : String(err);
10499
- opts.onOutbound({ type: "assistant", text: `Sideboard failed: ${message}` });
10500
- return;
10501
- }
10502
- if (generation !== inboundGeneration) return;
10503
- thread = readThread(thread.id) ?? thread;
10504
- if (thread.status === "stopped") return;
10505
- const result = orch.getTurnResult(thread.id);
10506
- const pending = extractPendingPlanQuestions(
10507
- [...thread.messages].reverse().find((m) => m.role === "agent")?.parts
10508
- );
10509
- if (result.taskState === "input-required" && pending) {
10510
- opts.onOutbound({
10511
- type: "ask_user",
10512
- text: result.text.trim(),
10513
- questions: pending.questions.map((q) => ({
10514
- question: q.question,
10515
- options: q.options.map(
10516
- (o) => o.description ? { label: o.label, description: o.description } : { label: o.label }
10517
- )
10518
- }))
10519
- });
10520
- return;
10521
- }
10522
- const reply = outboundReplyFromTurn(result, {
10523
- inputRequired: "Sideboard is waiting for an answer.",
10524
- canceled: REMOTE_STOPPED_REPLY,
10525
- completedEmpty: () => "",
10526
- failed: (detail) => detail ? `Sideboard failed: ${detail}` : "Sideboard failed before producing a result."
10527
- });
10528
- if (reply.trim()) opts.onOutbound({ type: "assistant", text: reply.trim() });
10529
- });
10575
+ beginCoordinatorPrompt(body, opts);
10530
10576
  }
10531
10577
 
10532
10578
  // src/remote/phone-board-sync.ts
@@ -11463,15 +11509,43 @@ function sleep2(ms, signal) {
11463
11509
  signal?.addEventListener("abort", onAbort, { once: true });
11464
11510
  });
11465
11511
  }
11512
+ var RELAY_TLS_HINT = " \u2014 the TLS certificate was rejected. A work proxy often does this when Sideboard is not allowlisted, or when the app does not trust the corporate CA.";
11513
+ var RELAY_NETWORK_HINT = " Sideboard could not reach the relay. On a work computer this is often a VPN, a proxy, or an allowlist that does not include Sideboard.";
11514
+ var RELAY_NETWORK_RE = /fetch failed|Failed to fetch|ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EHOSTUNREACH|ENETUNREACH|EAI_AGAIN|EPERM|UND_ERR_|ERR_CONNECTION|ERR_NETWORK|ERR_PROXY|ERR_TUNNEL|ERR_INTERNET|ERR_BLOCKED|ERR_NAME_NOT_RESOLVED|ERR_ADDRESS_UNREACHABLE|ERR_CERT|CERT_|UNABLE_TO_GET_ISSUER|SELF_SIGNED/i;
11515
+ function relayFetchError(err, url) {
11516
+ const formatted = formatFetchError(err, url, { tlsHint: RELAY_TLS_HINT });
11517
+ if (formatted.includes(RELAY_TLS_HINT) || !RELAY_NETWORK_RE.test(formatted)) {
11518
+ return new Error(formatted);
11519
+ }
11520
+ return new Error(`${formatted}${RELAY_NETWORK_HINT}`);
11521
+ }
11522
+ async function relayFetch(fetchImpl, url, init) {
11523
+ const fn = fetchImpl ?? currentHttpFetch();
11524
+ try {
11525
+ return await fn(url, init);
11526
+ } catch (err) {
11527
+ throw relayFetchError(err, url);
11528
+ }
11529
+ }
11530
+ async function readJson2(res, url) {
11531
+ try {
11532
+ return await res.json();
11533
+ } catch {
11534
+ const kind = (res.headers.get("content-type") ?? "").includes("html") ? "an HTML page" : "a non-JSON body";
11535
+ throw new Error(
11536
+ `Relay sign-in returned HTTP ${res.status} with ${kind} (${url}). A work proxy or allowlist often answers with a block page instead of the relay.`
11537
+ );
11538
+ }
11539
+ }
11466
11540
  async function startRemoteAccountLogin(opts) {
11467
11541
  if (opts.signal?.aborted) throw new RemoteAccountLoginCancelled();
11468
11542
  const origin = (opts.origin ?? remoteAccountHttpOrigin()).replace(/\/+$/, "");
11469
- const fetchImpl = opts.fetchImpl ?? fetch;
11470
11543
  const state = randomBytes7(16).toString("hex");
11471
11544
  const timeoutMs = opts.timeoutMs ?? 5 * 6e4;
11472
11545
  const pollIntervalMs = opts.pollIntervalMs ?? 400;
11473
11546
  const linkToken = opts.linkToken?.trim() ?? "";
11474
- const started = await fetchImpl(`${origin}${REMOTE_LOGIN_PATH}`, {
11547
+ const startUrl = `${origin}${REMOTE_LOGIN_PATH}`;
11548
+ const started = await relayFetch(opts.fetchImpl, startUrl, {
11475
11549
  method: "POST",
11476
11550
  headers: { "Content-Type": "application/json" },
11477
11551
  body: JSON.stringify({
@@ -11480,9 +11554,9 @@ async function startRemoteAccountLogin(opts) {
11480
11554
  ...linkToken ? { accountToken: linkToken } : {}
11481
11555
  })
11482
11556
  });
11483
- const startBody = await started.json();
11557
+ const startBody = await readJson2(started, startUrl);
11484
11558
  if (!started.ok || !startBody.url) {
11485
- throw new Error(startBody.error || "Could not start sign-in.");
11559
+ throw new Error(startBody.error || `Could not start sign-in (HTTP ${started.status}).`);
11486
11560
  }
11487
11561
  await Promise.resolve(opts.openUrl?.(startBody.url));
11488
11562
  const deadline = Date.now() + timeoutMs;
@@ -11491,9 +11565,11 @@ async function startRemoteAccountLogin(opts) {
11491
11565
  if (opts.signal?.aborted) throw new RemoteAccountLoginCancelled();
11492
11566
  let res;
11493
11567
  try {
11494
- res = await fetchImpl(resultUrl);
11495
- } catch {
11496
- if (opts.signal?.aborted) throw new RemoteAccountLoginCancelled();
11568
+ res = await relayFetch(opts.fetchImpl, resultUrl);
11569
+ } catch (err) {
11570
+ if (opts.signal?.aborted || isRemoteAccountLoginCancelled(err)) {
11571
+ throw new RemoteAccountLoginCancelled();
11572
+ }
11497
11573
  await sleep2(pollIntervalMs, opts.signal);
11498
11574
  continue;
11499
11575
  }
@@ -11518,8 +11594,7 @@ async function startRemoteAccountLogin(opts) {
11518
11594
  }
11519
11595
  async function disconnectRemoteAccount(opts) {
11520
11596
  const origin = (opts.origin ?? remoteAccountHttpOrigin()).replace(/\/+$/, "");
11521
- const fetchImpl = opts.fetchImpl ?? fetch;
11522
- await fetchImpl(`${origin}${REMOTE_LOGOUT_PATH}`, {
11597
+ await relayFetch(opts.fetchImpl, `${origin}${REMOTE_LOGOUT_PATH}`, {
11523
11598
  method: "POST",
11524
11599
  headers: { "Content-Type": "application/json" },
11525
11600
  body: JSON.stringify({ accountToken: opts.accountToken })
@@ -12411,7 +12486,7 @@ async function continueSourceThread(threadId, prompt) {
12411
12486
  await continueOnReply(threadId, prompt);
12412
12487
  return;
12413
12488
  }
12414
- const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-FPGVKEID.js");
12489
+ const { getOrchestrator: getOrchestrator2 } = await import("./orchestrator-F7KXYDQW.js");
12415
12490
  await getOrchestrator2().send(threadId, prompt);
12416
12491
  } catch {
12417
12492
  }
@@ -800,6 +800,30 @@ var init_workspace_scratch = __esm({
800
800
  }
801
801
  });
802
802
 
803
+ // src/agents/cursor-stream-cutoff.ts
804
+ function looksLikeCursorStreamCutOff(text7) {
805
+ return CURSOR_STREAM_CUTOFF.test(text7.trim());
806
+ }
807
+ function cursorStreamCutOffContinueLimit(detail) {
808
+ if (looksLikeCursorStreamCutOff(detail) || /connection stalled/i.test(detail)) {
809
+ return CURSOR_STREAM_CUTOFF_CONTINUE_LIMIT;
810
+ }
811
+ return null;
812
+ }
813
+ function nextCrashContinueCount(detail, used) {
814
+ const limit = cursorStreamCutOffContinueLimit(detail) ?? 1;
815
+ if (used >= limit) return null;
816
+ return used + 1;
817
+ }
818
+ var CURSOR_STREAM_CUTOFF, CURSOR_STREAM_CUTOFF_CONTINUE_LIMIT;
819
+ var init_cursor_stream_cutoff = __esm({
820
+ "src/agents/cursor-stream-cutoff.ts"() {
821
+ "use strict";
822
+ CURSOR_STREAM_CUTOFF = /nghttp2|premature close|\[unavailable\]\s+error\b|http\/2 stream closed|\bcancel \(0x8\)|error code cancel\b/i;
823
+ CURSOR_STREAM_CUTOFF_CONTINUE_LIMIT = 3;
824
+ }
825
+ });
826
+
803
827
  // src/agents/error-detail.ts
804
828
  function formatUnknownDetail(err) {
805
829
  if (err == null) return "";
@@ -990,9 +1014,6 @@ function looksLikeInvalidAgentSession(text7) {
990
1014
  if (!lower) return false;
991
1015
  return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower) || /corrupt local agent checkpoint/.test(lower) || /missing root blob/.test(lower) || /\bagent\b.{0,120}\bnot found\b/.test(lower) || /\brun\b.{0,80}\bnot found for agent\b/.test(lower);
992
1016
  }
993
- function looksLikeCursorStreamCutOff(text7) {
994
- return /nghttp2|premature close|\[unavailable\]\s+error\b/i.test(text7.trim());
995
- }
996
1017
  function looksLikeBrightsyEmptyCompletion(text7) {
997
1018
  const core = text7.trim().replace(/^exit\s*\d+:\s*/i, "").replace(/^error:\s*/i, "").trim();
998
1019
  return /^i did not receive a valid model response\. please try again\.$/i.test(core);
@@ -1166,6 +1187,8 @@ var NODE_VERSION_FOOTER, NESTED_ELECTRON_SUMMARY, HOMEBREW_LIBUV_SUMMARY, BUNDLE
1166
1187
  var init_error_detail = __esm({
1167
1188
  "src/agents/error-detail.ts"() {
1168
1189
  "use strict";
1190
+ init_cursor_stream_cutoff();
1191
+ init_cursor_stream_cutoff();
1169
1192
  NODE_VERSION_FOOTER = /^Node\.js v\d+/i;
1170
1193
  NESTED_ELECTRON_SUMMARY = "Cursor local agent crashed at Electron startup (nested Chromium / HasCustomHostObject)";
1171
1194
  HOMEBREW_LIBUV_SUMMARY = "Cursor runner crashed in Node (Homebrew Node + shared libuv). Install Node 24 LTS (`brew install node@24`) and retry.";
@@ -10231,30 +10254,49 @@ var init_accounts = __esm({
10231
10254
  });
10232
10255
 
10233
10256
  // src/http/fetch.ts
10234
- function tlsIssuerHint(code, message) {
10235
- const text7 = `${code ?? ""} ${message}`;
10236
- return TLS_ISSUER_RE.test(text7) ? TLS_ISSUER_HINT : "";
10257
+ function isTlsIssuerFailure(code, message) {
10258
+ return TLS_ISSUER_RE.test(`${code ?? ""} ${message}`);
10237
10259
  }
10238
- function formatFetchError(err, url) {
10239
- if (!(err instanceof Error)) return `${String(err)} (${url})`;
10240
- const cause = err.cause;
10241
- let detail = "";
10242
- let hint = tlsIssuerHint(void 0, err.message);
10260
+ function describeCause(cause, depth = 0) {
10261
+ if (depth > 5 || cause == null) return null;
10262
+ if (cause instanceof AggregateError) {
10263
+ for (const item of cause.errors) {
10264
+ const found = describeCause(item, depth + 1);
10265
+ if (found && (found.code || found.message)) return found;
10266
+ }
10267
+ return cause.message ? { message: cause.message } : null;
10268
+ }
10243
10269
  if (cause instanceof Error) {
10244
10270
  const code = typeof cause.code === "string" ? cause.code : void 0;
10245
- detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
10246
- hint = tlsIssuerHint(code, `${err.message} ${cause.message}`) || hint;
10247
- } else if (cause != null) {
10248
- detail = ` [${String(cause)}]`;
10249
- hint = tlsIssuerHint(void 0, String(cause)) || hint;
10250
- }
10251
- return `${err.message}${detail} (${url})${hint}`;
10271
+ const nested = describeCause(cause.cause, depth + 1);
10272
+ if ((!cause.message || cause.message === "fetch failed") && nested) return nested;
10273
+ if (cause.message || code) return { code, message: cause.message };
10274
+ return nested;
10275
+ }
10276
+ return { message: String(cause) };
10277
+ }
10278
+ function causeSuffix(detail) {
10279
+ if (!detail) return "";
10280
+ if (detail.code && detail.message) return ` [${detail.code}: ${detail.message}]`;
10281
+ if (detail.code) return ` [${detail.code}]`;
10282
+ if (detail.message) return ` [${detail.message}]`;
10283
+ return "";
10284
+ }
10285
+ function formatFetchError(err, url, opts) {
10286
+ if (!(err instanceof Error)) return `${String(err)} (${url})`;
10287
+ const detail = describeCause(err.cause);
10288
+ const hintSource = `${err.message} ${detail?.code ?? ""} ${detail?.message ?? ""}`;
10289
+ const tls2 = isTlsIssuerFailure(detail?.code, hintSource);
10290
+ const hint = tls2 ? opts?.tlsHint ?? TLS_ISSUER_HINT : "";
10291
+ return `${err.message}${causeSuffix(detail)} (${url})${hint}`;
10292
+ }
10293
+ function currentHttpFetch() {
10294
+ return injected ?? globalThis.fetch.bind(globalThis);
10252
10295
  }
10253
10296
  async function httpFetch(input, init) {
10254
10297
  const url = typeof input === "string" ? input : input.href;
10255
- const fn = injected ?? globalThis.fetch.bind(globalThis);
10256
10298
  try {
10257
- return await fn(url, init);
10299
+ return await currentHttpFetch()(url, init);
10258
10300
  } catch (err) {
10259
10301
  throw new Error(formatFetchError(err, url));
10260
10302
  }
@@ -25408,10 +25450,10 @@ var init_orchestrator = __esm({
25408
25450
  /** Timers for orchestration session-quota auto-resume. */
25409
25451
  quotaResumeTimers = /* @__PURE__ */ new Map();
25410
25452
  /**
25411
- * Threads that already got a crash-continue turn. Cleared on a successful
25412
- * finish or a user send so a later crash can recover again.
25453
+ * Crash-continue count per thread. Cleared on a successful finish or a user
25454
+ * send. Stream cutoffs may resume more than once; other crashes once.
25413
25455
  */
25414
- crashContinued = /* @__PURE__ */ new Set();
25456
+ crashContinued = /* @__PURE__ */ new Map();
25415
25457
  /** Auto-continues after a worktree turn ended while a detached job still runs. */
25416
25458
  jobContinueCount = /* @__PURE__ */ new Map();
25417
25459
  jobContinueNudged = /* @__PURE__ */ new Set();
@@ -25850,7 +25892,6 @@ var init_orchestrator = __esm({
25850
25892
  * next prompt so the same agent can continue instead of sitting on lastError.
25851
25893
  */
25852
25894
  maybeEnqueueCrashContinue(threadId, opts) {
25853
- if (this.crashContinued.has(threadId)) return;
25854
25895
  if (this.haltDrain.has(threadId)) return;
25855
25896
  if (!shouldFeedErrorBackToAgent({
25856
25897
  detail: opts.detail,
@@ -25859,9 +25900,11 @@ var init_orchestrator = __esm({
25859
25900
  })) {
25860
25901
  return;
25861
25902
  }
25903
+ const resumeCount = nextCrashContinueCount(opts.detail, this.crashContinued.get(threadId) ?? 0);
25904
+ if (resumeCount == null) return;
25862
25905
  const thread = readThread(threadId);
25863
25906
  if (!thread || thread.status === "archived") return;
25864
- this.crashContinued.add(threadId);
25907
+ this.crashContinued.set(threadId, resumeCount);
25865
25908
  const prompt = formatAgentErrorContinuePrompt(opts.detail);
25866
25909
  const next = prependQueuedItem(thread.queue, thread.queueAttachments, prompt);
25867
25910
  updateThread(threadId, next);
@@ -18,9 +18,9 @@ import {
18
18
  searchAbleTimeTasks,
19
19
  toAbleTimeIssueInfo,
20
20
  updateAbleTimeTask
21
- } from "../chunk-BS3QLVPL.js";
22
- import "../chunk-I6HSGKMZ.js";
23
- import "../chunk-PY4UCMRI.js";
21
+ } from "../chunk-TMM4FEZ5.js";
22
+ import "../chunk-5T4YWFLD.js";
23
+ import "../chunk-VQEARG5H.js";
24
24
  import {
25
25
  AGENT_GIT_ACTIONS,
26
26
  BOARD_COLUMN_DEFS,
@@ -71,7 +71,7 @@ import {
71
71
  syncBoardPins,
72
72
  updateSchedule,
73
73
  workspaceTagTargetRef
74
- } from "../chunk-BYZ7F2BQ.js";
74
+ } from "../chunk-2K723QZH.js";
75
75
  import "../chunk-LX5R5JOR.js";
76
76
  import {
77
77
  PRESENT_PLAN_REQUIRES_PLAN_MODE,
@@ -90,12 +90,12 @@ import {
90
90
  sideboardMcpProfile,
91
91
  trustSystemCertificates,
92
92
  worktreeMcpToolNames
93
- } from "../chunk-ADXVTKLC.js";
94
- import "../chunk-TQRSVE7D.js";
95
- import "../chunk-P2BSMNHB.js";
93
+ } from "../chunk-TXHZJESB.js";
94
+ import "../chunk-7IAMHN2N.js";
95
+ import "../chunk-PXMLMBMI.js";
96
96
  import {
97
97
  httpFetch
98
- } from "../chunk-VKQGAWHY.js";
98
+ } from "../chunk-2GPQZZLY.js";
99
99
  import {
100
100
  mcpWaitForJobTimeoutMs,
101
101
  mcpWaitForTurnTimeoutMs,
@@ -106,12 +106,12 @@ import {
106
106
  waitForDetachedJob,
107
107
  waitForTurnToolResult,
108
108
  workspaceAgentChatSummaries
109
- } from "../chunk-EIEAR62M.js";
109
+ } from "../chunk-LMWPVC5L.js";
110
110
  import {
111
111
  clipToolResultForStore,
112
112
  isInternalAgentStatusText,
113
113
  looksLikeHugeToolResultDump
114
- } from "../chunk-W7HIK2A6.js";
114
+ } from "../chunk-5QOLFOAX.js";
115
115
  import {
116
116
  GLOBAL_WORKSPACE_ID,
117
117
  isCloudCoordinatorThread
@@ -7,8 +7,8 @@ import {
7
7
  normalizeBrightsyEndpoint,
8
8
  refreshBrightsyAccessToken,
9
9
  registerBrightsyOAuthClient
10
- } from "./chunk-ROPGQRPU.js";
11
- import "./chunk-N7WZ6NIN.js";
10
+ } from "./chunk-33KDPEK4.js";
11
+ import "./chunk-JAT3JFOS.js";
12
12
  export {
13
13
  BRIGHTSY_OAUTH_CLIENT_ID,
14
14
  BRIGHTSY_OAUTH_REDIRECT,