@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.cjs CHANGED
@@ -4812,6 +4812,30 @@ var init_thread_store = __esm({
4812
4812
  }
4813
4813
  });
4814
4814
 
4815
+ // src/agents/cursor-stream-cutoff.ts
4816
+ function looksLikeCursorStreamCutOff(text7) {
4817
+ return CURSOR_STREAM_CUTOFF.test(text7.trim());
4818
+ }
4819
+ function cursorStreamCutOffContinueLimit(detail) {
4820
+ if (looksLikeCursorStreamCutOff(detail) || /connection stalled/i.test(detail)) {
4821
+ return CURSOR_STREAM_CUTOFF_CONTINUE_LIMIT;
4822
+ }
4823
+ return null;
4824
+ }
4825
+ function nextCrashContinueCount(detail, used) {
4826
+ const limit = cursorStreamCutOffContinueLimit(detail) ?? 1;
4827
+ if (used >= limit) return null;
4828
+ return used + 1;
4829
+ }
4830
+ var CURSOR_STREAM_CUTOFF, CURSOR_STREAM_CUTOFF_CONTINUE_LIMIT;
4831
+ var init_cursor_stream_cutoff = __esm({
4832
+ "src/agents/cursor-stream-cutoff.ts"() {
4833
+ "use strict";
4834
+ CURSOR_STREAM_CUTOFF = /nghttp2|premature close|\[unavailable\]\s+error\b|http\/2 stream closed|\bcancel \(0x8\)|error code cancel\b/i;
4835
+ CURSOR_STREAM_CUTOFF_CONTINUE_LIMIT = 3;
4836
+ }
4837
+ });
4838
+
4815
4839
  // src/agents/error-detail.ts
4816
4840
  function formatUnknownDetail(err) {
4817
4841
  if (err == null) return "";
@@ -5002,9 +5026,6 @@ function looksLikeInvalidAgentSession(text7) {
5002
5026
  if (!lower) return false;
5003
5027
  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);
5004
5028
  }
5005
- function looksLikeCursorStreamCutOff(text7) {
5006
- return /nghttp2|premature close|\[unavailable\]\s+error\b/i.test(text7.trim());
5007
- }
5008
5029
  function looksLikeBrightsyEmptyCompletion(text7) {
5009
5030
  const core = text7.trim().replace(/^exit\s*\d+:\s*/i, "").replace(/^error:\s*/i, "").trim();
5010
5031
  return /^i did not receive a valid model response\. please try again\.$/i.test(core);
@@ -5178,6 +5199,8 @@ var NODE_VERSION_FOOTER, NESTED_ELECTRON_SUMMARY, HOMEBREW_LIBUV_SUMMARY, BUNDLE
5178
5199
  var init_error_detail = __esm({
5179
5200
  "src/agents/error-detail.ts"() {
5180
5201
  "use strict";
5202
+ init_cursor_stream_cutoff();
5203
+ init_cursor_stream_cutoff();
5181
5204
  NODE_VERSION_FOOTER = /^Node\.js v\d+/i;
5182
5205
  NESTED_ELECTRON_SUMMARY = "Cursor local agent crashed at Electron startup (nested Chromium / HasCustomHostObject)";
5183
5206
  HOMEBREW_LIBUV_SUMMARY = "Cursor runner crashed in Node (Homebrew Node + shared libuv). Install Node 24 LTS (`brew install node@24`) and retry.";
@@ -11976,30 +11999,49 @@ var init_config = __esm({
11976
11999
  function setHttpFetchImpl(fetchImpl) {
11977
12000
  injected = fetchImpl;
11978
12001
  }
11979
- function tlsIssuerHint(code, message) {
11980
- const text7 = `${code ?? ""} ${message}`;
11981
- return TLS_ISSUER_RE.test(text7) ? TLS_ISSUER_HINT : "";
12002
+ function isTlsIssuerFailure(code, message) {
12003
+ return TLS_ISSUER_RE.test(`${code ?? ""} ${message}`);
11982
12004
  }
11983
- function formatFetchError(err, url) {
11984
- if (!(err instanceof Error)) return `${String(err)} (${url})`;
11985
- const cause = err.cause;
11986
- let detail = "";
11987
- let hint = tlsIssuerHint(void 0, err.message);
12005
+ function describeCause(cause, depth = 0) {
12006
+ if (depth > 5 || cause == null) return null;
12007
+ if (cause instanceof AggregateError) {
12008
+ for (const item of cause.errors) {
12009
+ const found = describeCause(item, depth + 1);
12010
+ if (found && (found.code || found.message)) return found;
12011
+ }
12012
+ return cause.message ? { message: cause.message } : null;
12013
+ }
11988
12014
  if (cause instanceof Error) {
11989
12015
  const code = typeof cause.code === "string" ? cause.code : void 0;
11990
- detail = code ? ` [${code}: ${cause.message}]` : ` [${cause.message}]`;
11991
- hint = tlsIssuerHint(code, `${err.message} ${cause.message}`) || hint;
11992
- } else if (cause != null) {
11993
- detail = ` [${String(cause)}]`;
11994
- hint = tlsIssuerHint(void 0, String(cause)) || hint;
11995
- }
11996
- return `${err.message}${detail} (${url})${hint}`;
12016
+ const nested = describeCause(cause.cause, depth + 1);
12017
+ if ((!cause.message || cause.message === "fetch failed") && nested) return nested;
12018
+ if (cause.message || code) return { code, message: cause.message };
12019
+ return nested;
12020
+ }
12021
+ return { message: String(cause) };
12022
+ }
12023
+ function causeSuffix(detail) {
12024
+ if (!detail) return "";
12025
+ if (detail.code && detail.message) return ` [${detail.code}: ${detail.message}]`;
12026
+ if (detail.code) return ` [${detail.code}]`;
12027
+ if (detail.message) return ` [${detail.message}]`;
12028
+ return "";
12029
+ }
12030
+ function formatFetchError(err, url, opts) {
12031
+ if (!(err instanceof Error)) return `${String(err)} (${url})`;
12032
+ const detail = describeCause(err.cause);
12033
+ const hintSource = `${err.message} ${detail?.code ?? ""} ${detail?.message ?? ""}`;
12034
+ const tls2 = isTlsIssuerFailure(detail?.code, hintSource);
12035
+ const hint = tls2 ? opts?.tlsHint ?? TLS_ISSUER_HINT : "";
12036
+ return `${err.message}${causeSuffix(detail)} (${url})${hint}`;
12037
+ }
12038
+ function currentHttpFetch() {
12039
+ return injected ?? globalThis.fetch.bind(globalThis);
11997
12040
  }
11998
12041
  async function httpFetch(input, init) {
11999
12042
  const url = typeof input === "string" ? input : input.href;
12000
- const fn = injected ?? globalThis.fetch.bind(globalThis);
12001
12043
  try {
12002
- return await fn(url, init);
12044
+ return await currentHttpFetch()(url, init);
12003
12045
  } catch (err) {
12004
12046
  throw new Error(formatFetchError(err, url));
12005
12047
  }
@@ -14696,10 +14738,10 @@ function eventsFromClaudeSystem(obj) {
14696
14738
  if (subtype === "api_retry") {
14697
14739
  const attempt = obj.attempt;
14698
14740
  const max = obj.max_retries;
14699
- const delay2 = obj.retry_delay_ms;
14741
+ const delay3 = obj.retry_delay_ms;
14700
14742
  return {
14701
14743
  type: "thinking",
14702
- data: `API retry ${attempt ?? "?"}/${max ?? "?"}${typeof delay2 === "number" ? ` (wait ${delay2}ms)` : ""}`
14744
+ data: `API retry ${attempt ?? "?"}/${max ?? "?"}${typeof delay3 === "number" ? ` (wait ${delay3}ms)` : ""}`
14703
14745
  };
14704
14746
  }
14705
14747
  if (subtype === "status") {
@@ -27436,10 +27478,10 @@ var init_orchestrator = __esm({
27436
27478
  /** Timers for orchestration session-quota auto-resume. */
27437
27479
  quotaResumeTimers = /* @__PURE__ */ new Map();
27438
27480
  /**
27439
- * Threads that already got a crash-continue turn. Cleared on a successful
27440
- * finish or a user send so a later crash can recover again.
27481
+ * Crash-continue count per thread. Cleared on a successful finish or a user
27482
+ * send. Stream cutoffs may resume more than once; other crashes once.
27441
27483
  */
27442
- crashContinued = /* @__PURE__ */ new Set();
27484
+ crashContinued = /* @__PURE__ */ new Map();
27443
27485
  /** Auto-continues after a worktree turn ended while a detached job still runs. */
27444
27486
  jobContinueCount = /* @__PURE__ */ new Map();
27445
27487
  jobContinueNudged = /* @__PURE__ */ new Set();
@@ -27721,8 +27763,8 @@ var init_orchestrator = __esm({
27721
27763
  scheduleQuotaResume(threadId, resumeAt) {
27722
27764
  this.clearQuotaResumeTimer(threadId);
27723
27765
  updateThread(threadId, { quotaResumeAt: resumeAt.toISOString() });
27724
- const delay2 = Math.max(5e3, resumeAt.getTime() - Date.now());
27725
- const capped = Math.min(delay2, 2147483647);
27766
+ const delay3 = Math.max(5e3, resumeAt.getTime() - Date.now());
27767
+ const capped = Math.min(delay3, 2147483647);
27726
27768
  const timer = setTimeout(() => {
27727
27769
  this.quotaResumeTimers.delete(threadId);
27728
27770
  void this.resumeAfterQuotaWait(threadId);
@@ -27878,7 +27920,6 @@ var init_orchestrator = __esm({
27878
27920
  * next prompt so the same agent can continue instead of sitting on lastError.
27879
27921
  */
27880
27922
  maybeEnqueueCrashContinue(threadId, opts) {
27881
- if (this.crashContinued.has(threadId)) return;
27882
27923
  if (this.haltDrain.has(threadId)) return;
27883
27924
  if (!shouldFeedErrorBackToAgent({
27884
27925
  detail: opts.detail,
@@ -27887,9 +27928,11 @@ var init_orchestrator = __esm({
27887
27928
  })) {
27888
27929
  return;
27889
27930
  }
27931
+ const resumeCount = nextCrashContinueCount(opts.detail, this.crashContinued.get(threadId) ?? 0);
27932
+ if (resumeCount == null) return;
27890
27933
  const thread = readThread(threadId);
27891
27934
  if (!thread || thread.status === "archived") return;
27892
- this.crashContinued.add(threadId);
27935
+ this.crashContinued.set(threadId, resumeCount);
27893
27936
  const prompt = formatAgentErrorContinuePrompt(opts.detail);
27894
27937
  const next = prependQueuedItem(thread.queue, thread.queueAttachments, prompt);
27895
27938
  updateThread(threadId, next);
@@ -30162,14 +30205,14 @@ function armOne(schedule) {
30162
30205
  if (!schedule.enabled) return;
30163
30206
  const at = new Date(schedule.nextRunAt);
30164
30207
  if (Number.isNaN(at.getTime())) return;
30165
- const delay2 = Math.max(0, at.getTime() - Date.now());
30166
- if (delay2 <= 250) {
30208
+ const delay3 = Math.max(0, at.getTime() - Date.now());
30209
+ if (delay3 <= 250) {
30167
30210
  void fireSchedule(schedule.id).then(() => {
30168
30211
  if (isThisProcessDesktopHost()) armSchedules();
30169
30212
  });
30170
30213
  return;
30171
30214
  }
30172
- const capped = Math.min(delay2, SET_TIMEOUT_MAX);
30215
+ const capped = Math.min(delay3, SET_TIMEOUT_MAX);
30173
30216
  const timer = setTimeout(() => {
30174
30217
  timers.delete(schedule.id);
30175
30218
  const live = getSchedule(schedule.id);
@@ -39714,10 +39757,7 @@ var import_ws5 = require("ws");
39714
39757
  init_orchestrator_capable();
39715
39758
  init_stage_files();
39716
39759
  init_orchestrator();
39717
- init_ask_user();
39718
39760
  init_app_settings();
39719
- init_global_workspace();
39720
- init_thread_store();
39721
39761
  init_implied_first_prompt();
39722
39762
 
39723
39763
  // src/remote/phone-media.ts
@@ -40696,7 +40736,13 @@ function deliverPhoneTurn(opts, chatId) {
40696
40736
  });
40697
40737
  }
40698
40738
 
40699
- // src/remote/listen.ts
40739
+ // src/remote/phone-turn.ts
40740
+ init_orchestrator_capable();
40741
+ init_orchestrator();
40742
+ init_ask_user();
40743
+ init_app_settings();
40744
+ init_global_workspace();
40745
+ init_thread_store();
40700
40746
  function formatRemotePrompt(text7) {
40701
40747
  return `Phone
40702
40748
 
@@ -40705,6 +40751,29 @@ ${text7.trim()}`;
40705
40751
  function isRemoteStopCommand(text7) {
40706
40752
  return text7.trim().toLowerCase() === "stop";
40707
40753
  }
40754
+ function phoneFollowUpQueues(status) {
40755
+ return followUpBehavior() === "queue" && (status === "running" || status === "queued");
40756
+ }
40757
+ var PHONE_TURN_MS = 14 * 60 * 1e3;
40758
+ function delay2(ms) {
40759
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
40760
+ }
40761
+ async function waitForPhoneTurn(threadId, stillCurrent) {
40762
+ const deadline = Date.now() + PHONE_TURN_MS;
40763
+ while (stillCurrent() && Date.now() < deadline) {
40764
+ const thread2 = readPhoneThread(threadId) ?? readThread(threadId);
40765
+ if (!thread2) return;
40766
+ if (thread2.status === "stopped" && thread2.queue.length > 0) {
40767
+ await delay2(40);
40768
+ continue;
40769
+ }
40770
+ break;
40771
+ }
40772
+ if (!stillCurrent()) return;
40773
+ const thread = readPhoneThread(threadId) ?? readThread(threadId);
40774
+ if (!thread || thread.status !== "running" && thread.status !== "queued") return;
40775
+ await getOrchestrator().waitForTurn(threadId, Math.max(1, deadline - Date.now()));
40776
+ }
40708
40777
  var handleChain2 = Promise.resolve();
40709
40778
  var inboundGeneration = 0;
40710
40779
  function enqueue(fn) {
@@ -40728,6 +40797,77 @@ function interruptRemoteCoordinator(deviceId, log = () => void 0) {
40728
40797
  return false;
40729
40798
  }
40730
40799
  }
40800
+ function runCoordinatorPrompt(opts, body, generation) {
40801
+ const current = () => generation === inboundGeneration;
40802
+ return (async () => {
40803
+ if (!current()) return;
40804
+ const agent = coerceOrchestratorAgent(opts.agent ?? resolveOrchestratorDefaults().agent);
40805
+ if (!body) return;
40806
+ if (isRemoteStopCommand(body)) {
40807
+ if (!current()) return;
40808
+ opts.onOutbound({ type: "assistant", text: REMOTE_STOPPED_REPLY });
40809
+ return;
40810
+ }
40811
+ let thread = ensureRemoteCoordinator(opts.deviceId, agent);
40812
+ thread = readThread(thread.id) ?? thread;
40813
+ const followUp = followUpBehavior();
40814
+ const orch = getOrchestrator();
40815
+ try {
40816
+ await orch.send(thread.id, formatRemotePrompt(body), { followUp });
40817
+ if (!current()) return;
40818
+ await waitForPhoneTurn(thread.id, current);
40819
+ } catch (err) {
40820
+ if (!current()) return;
40821
+ const message = err instanceof Error ? err.message : String(err);
40822
+ opts.onOutbound({ type: "assistant", text: `Sideboard failed: ${message}` });
40823
+ return;
40824
+ }
40825
+ if (!current()) return;
40826
+ thread = readThread(thread.id) ?? thread;
40827
+ if (thread.status === "stopped") return;
40828
+ const result = orch.getTurnResult(thread.id);
40829
+ const pending = extractPendingPlanQuestions(
40830
+ [...thread.messages].reverse().find((m) => m.role === "agent")?.parts
40831
+ );
40832
+ if (result.taskState === "input-required" && pending) {
40833
+ opts.onOutbound({
40834
+ type: "ask_user",
40835
+ text: result.text.trim(),
40836
+ questions: pending.questions.map((q) => ({
40837
+ question: q.question,
40838
+ options: q.options.map(
40839
+ (o) => o.description ? { label: o.label, description: o.description } : { label: o.label }
40840
+ )
40841
+ }))
40842
+ });
40843
+ return;
40844
+ }
40845
+ const reply = outboundReplyFromTurn(result, {
40846
+ inputRequired: "Sideboard is waiting for an answer.",
40847
+ canceled: REMOTE_STOPPED_REPLY,
40848
+ completedEmpty: () => "",
40849
+ failed: (detail) => detail ? `Sideboard failed: ${detail}` : "Sideboard failed before producing a result."
40850
+ });
40851
+ if (reply.trim()) opts.onOutbound({ type: "assistant", text: reply.trim() });
40852
+ })();
40853
+ }
40854
+ function beginCoordinatorPrompt(body, opts) {
40855
+ const live = body ? findRemoteCoordinator(opts.deviceId) : null;
40856
+ const fresh = live ? readThread(live.id) ?? live : null;
40857
+ if (body && !isRemoteStopCommand(body) && phoneFollowUpQueues(fresh?.status)) {
40858
+ const generation2 = inboundGeneration;
40859
+ enqueue(() => runCoordinatorPrompt(opts, body, generation2));
40860
+ return;
40861
+ }
40862
+ const generation = ++inboundGeneration;
40863
+ if (body && isRemoteStopCommand(body)) {
40864
+ interruptRemoteCoordinator(opts.deviceId, opts.onLog ?? (() => void 0));
40865
+ }
40866
+ const task = runCoordinatorPrompt(opts, body, generation);
40867
+ enqueue(() => task);
40868
+ }
40869
+
40870
+ // src/remote/listen.ts
40731
40871
  function emitOpened(opts, opened) {
40732
40872
  setPhoneOpenChat(opened.chat.id);
40733
40873
  emitControl(opts, { op: "opened", ...opened });
@@ -40997,23 +41137,27 @@ function handlePhoneControl(cmd, opts) {
40997
41137
  return;
40998
41138
  }
40999
41139
  const chatId = cmd.chatId;
41140
+ const existing = readPhoneThread(chatId);
41141
+ if (phoneFollowUpQueues(existing?.status)) {
41142
+ deliverPhoneTurn(opts, chatId);
41143
+ const generation2 = chatGeneration.get(chatId) ?? bumpChat(chatId);
41144
+ enqueueChat(chatId, () => runPhonePrompt(opts, cmd, chatId, generation2));
41145
+ return;
41146
+ }
41000
41147
  const generation = bumpChat(chatId);
41001
41148
  phoneTurnGen.set(chatId, generation);
41002
- interruptChat(chatId, log);
41003
- enqueueChat(chatId, async () => {
41004
- if (chatGeneration.get(chatId) !== generation) return;
41149
+ const task = runPhonePrompt(opts, cmd, chatId, generation);
41150
+ enqueueChat(chatId, () => task);
41151
+ }
41152
+ function runPhonePrompt(opts, cmd, chatId, generation) {
41153
+ const current = () => chatGeneration.get(chatId) === generation;
41154
+ return (async () => {
41155
+ if (!current()) return;
41005
41156
  const thread = readPhoneThread(chatId);
41006
41157
  if (!thread) {
41007
41158
  emitControl(opts, { op: "error", chatId, message: "That agent is not on this Mac." });
41008
41159
  return;
41009
41160
  }
41010
- if (thread.status === "running" || thread.status === "queued") {
41011
- try {
41012
- getOrchestrator().stop(thread.id, { clearQueue: true });
41013
- } catch {
41014
- }
41015
- }
41016
- if (chatGeneration.get(chatId) !== generation) return;
41017
41161
  const stopStream = watchPhoneStream(opts, chatId, thread.id, generation);
41018
41162
  try {
41019
41163
  try {
@@ -41021,14 +41165,19 @@ function handlePhoneControl(cmd, opts) {
41021
41165
  } catch (err) {
41022
41166
  emitControl(opts, { op: "error", chatId, message: phoneError(err) });
41023
41167
  }
41168
+ if (!current()) return;
41024
41169
  const live = readPhoneThread(thread.id) ?? thread;
41170
+ const followUp = followUpBehavior();
41025
41171
  const staged = stagePhoneTurnFiles(live.worktreePath, cmd);
41026
41172
  const text7 = cmd.text.trim() || (staged.length ? "See the attached files." : "");
41027
- const formatted = formatRemotePrompt(text7);
41028
- await getOrchestrator().send(thread.id, formatted, { attachments: staged });
41029
- await getOrchestrator().waitForTurn(thread.id, 14 * 60 * 1e3);
41173
+ await getOrchestrator().send(thread.id, formatRemotePrompt(text7), {
41174
+ attachments: staged,
41175
+ followUp
41176
+ });
41177
+ if (!current()) return;
41178
+ await waitForPhoneTurn(thread.id, current);
41030
41179
  } catch (err) {
41031
- if (chatGeneration.get(chatId) !== generation) return;
41180
+ if (!current()) return;
41032
41181
  const message = err instanceof Error ? err.message : String(err);
41033
41182
  emitControl(opts, { op: "error", chatId, message: `Sideboard failed: ${message}` });
41034
41183
  return;
@@ -41036,12 +41185,11 @@ function handlePhoneControl(cmd, opts) {
41036
41185
  stopStream();
41037
41186
  finishPhoneTurn(chatId, generation);
41038
41187
  }
41039
- if (chatGeneration.get(chatId) !== generation) return;
41188
+ if (!current()) return;
41040
41189
  emitChatTurn(opts, chatId, thread.id);
41041
- });
41190
+ })();
41042
41191
  }
41043
41192
  function handleRemoteInbound(text7, opts) {
41044
- const log = opts.onLog ?? (() => void 0);
41045
41193
  const body = text7.trim();
41046
41194
  const control = takePhoneControl(body);
41047
41195
  if (control) {
@@ -41052,64 +41200,7 @@ function handleRemoteInbound(text7, opts) {
41052
41200
  handlePhoneControl(control, opts);
41053
41201
  return;
41054
41202
  }
41055
- const generation = ++inboundGeneration;
41056
- if (body) interruptRemoteCoordinator(opts.deviceId, log);
41057
- enqueue(async () => {
41058
- if (generation !== inboundGeneration) return;
41059
- const agent = coerceOrchestratorAgent(opts.agent ?? resolveOrchestratorDefaults().agent);
41060
- if (!body) return;
41061
- if (isRemoteStopCommand(body)) {
41062
- if (generation !== inboundGeneration) return;
41063
- opts.onOutbound({ type: "assistant", text: REMOTE_STOPPED_REPLY });
41064
- return;
41065
- }
41066
- let thread = ensureRemoteCoordinator(opts.deviceId, agent);
41067
- const fresh = readThread(thread.id) ?? thread;
41068
- if (fresh.status === "running" || fresh.status === "queued") {
41069
- try {
41070
- getOrchestrator().stop(fresh.id, { clearQueue: true });
41071
- } catch {
41072
- }
41073
- }
41074
- if (generation !== inboundGeneration) return;
41075
- const orch = getOrchestrator();
41076
- try {
41077
- await orch.send(thread.id, formatRemotePrompt(body));
41078
- await orch.waitForTurn(thread.id, 14 * 60 * 1e3);
41079
- } catch (err) {
41080
- if (generation !== inboundGeneration) return;
41081
- const message = err instanceof Error ? err.message : String(err);
41082
- opts.onOutbound({ type: "assistant", text: `Sideboard failed: ${message}` });
41083
- return;
41084
- }
41085
- if (generation !== inboundGeneration) return;
41086
- thread = readThread(thread.id) ?? thread;
41087
- if (thread.status === "stopped") return;
41088
- const result = orch.getTurnResult(thread.id);
41089
- const pending = extractPendingPlanQuestions(
41090
- [...thread.messages].reverse().find((m) => m.role === "agent")?.parts
41091
- );
41092
- if (result.taskState === "input-required" && pending) {
41093
- opts.onOutbound({
41094
- type: "ask_user",
41095
- text: result.text.trim(),
41096
- questions: pending.questions.map((q) => ({
41097
- question: q.question,
41098
- options: q.options.map(
41099
- (o) => o.description ? { label: o.label, description: o.description } : { label: o.label }
41100
- )
41101
- }))
41102
- });
41103
- return;
41104
- }
41105
- const reply = outboundReplyFromTurn(result, {
41106
- inputRequired: "Sideboard is waiting for an answer.",
41107
- canceled: REMOTE_STOPPED_REPLY,
41108
- completedEmpty: () => "",
41109
- failed: (detail) => detail ? `Sideboard failed: ${detail}` : "Sideboard failed before producing a result."
41110
- });
41111
- if (reply.trim()) opts.onOutbound({ type: "assistant", text: reply.trim() });
41112
- });
41203
+ beginCoordinatorPrompt(body, opts);
41113
41204
  }
41114
41205
 
41115
41206
  // src/remote/phone-board-sync.ts
@@ -41449,6 +41540,7 @@ function runRemoteHost(opts) {
41449
41540
 
41450
41541
  // src/relay/account-login.ts
41451
41542
  var import_node_crypto28 = require("crypto");
41543
+ init_fetch();
41452
41544
 
41453
41545
  // src/relay/accounts.ts
41454
41546
  var import_node_crypto27 = require("crypto");
@@ -42049,15 +42141,43 @@ function sleep2(ms, signal) {
42049
42141
  signal?.addEventListener("abort", onAbort, { once: true });
42050
42142
  });
42051
42143
  }
42144
+ 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.";
42145
+ 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.";
42146
+ 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;
42147
+ function relayFetchError(err, url) {
42148
+ const formatted = formatFetchError(err, url, { tlsHint: RELAY_TLS_HINT });
42149
+ if (formatted.includes(RELAY_TLS_HINT) || !RELAY_NETWORK_RE.test(formatted)) {
42150
+ return new Error(formatted);
42151
+ }
42152
+ return new Error(`${formatted}${RELAY_NETWORK_HINT}`);
42153
+ }
42154
+ async function relayFetch(fetchImpl, url, init) {
42155
+ const fn = fetchImpl ?? currentHttpFetch();
42156
+ try {
42157
+ return await fn(url, init);
42158
+ } catch (err) {
42159
+ throw relayFetchError(err, url);
42160
+ }
42161
+ }
42162
+ async function readJson3(res, url) {
42163
+ try {
42164
+ return await res.json();
42165
+ } catch {
42166
+ const kind = (res.headers.get("content-type") ?? "").includes("html") ? "an HTML page" : "a non-JSON body";
42167
+ throw new Error(
42168
+ `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.`
42169
+ );
42170
+ }
42171
+ }
42052
42172
  async function startRemoteAccountLogin(opts) {
42053
42173
  if (opts.signal?.aborted) throw new RemoteAccountLoginCancelled();
42054
42174
  const origin = (opts.origin ?? remoteAccountHttpOrigin()).replace(/\/+$/, "");
42055
- const fetchImpl = opts.fetchImpl ?? fetch;
42056
42175
  const state = (0, import_node_crypto28.randomBytes)(16).toString("hex");
42057
42176
  const timeoutMs = opts.timeoutMs ?? 5 * 6e4;
42058
42177
  const pollIntervalMs = opts.pollIntervalMs ?? 400;
42059
42178
  const linkToken = opts.linkToken?.trim() ?? "";
42060
- const started = await fetchImpl(`${origin}${REMOTE_LOGIN_PATH}`, {
42179
+ const startUrl = `${origin}${REMOTE_LOGIN_PATH}`;
42180
+ const started = await relayFetch(opts.fetchImpl, startUrl, {
42061
42181
  method: "POST",
42062
42182
  headers: { "Content-Type": "application/json" },
42063
42183
  body: JSON.stringify({
@@ -42066,9 +42186,9 @@ async function startRemoteAccountLogin(opts) {
42066
42186
  ...linkToken ? { accountToken: linkToken } : {}
42067
42187
  })
42068
42188
  });
42069
- const startBody = await started.json();
42189
+ const startBody = await readJson3(started, startUrl);
42070
42190
  if (!started.ok || !startBody.url) {
42071
- throw new Error(startBody.error || "Could not start sign-in.");
42191
+ throw new Error(startBody.error || `Could not start sign-in (HTTP ${started.status}).`);
42072
42192
  }
42073
42193
  await Promise.resolve(opts.openUrl?.(startBody.url));
42074
42194
  const deadline = Date.now() + timeoutMs;
@@ -42077,9 +42197,11 @@ async function startRemoteAccountLogin(opts) {
42077
42197
  if (opts.signal?.aborted) throw new RemoteAccountLoginCancelled();
42078
42198
  let res;
42079
42199
  try {
42080
- res = await fetchImpl(resultUrl);
42081
- } catch {
42082
- if (opts.signal?.aborted) throw new RemoteAccountLoginCancelled();
42200
+ res = await relayFetch(opts.fetchImpl, resultUrl);
42201
+ } catch (err) {
42202
+ if (opts.signal?.aborted || isRemoteAccountLoginCancelled(err)) {
42203
+ throw new RemoteAccountLoginCancelled();
42204
+ }
42083
42205
  await sleep2(pollIntervalMs, opts.signal);
42084
42206
  continue;
42085
42207
  }
@@ -42104,8 +42226,7 @@ async function startRemoteAccountLogin(opts) {
42104
42226
  }
42105
42227
  async function disconnectRemoteAccount(opts) {
42106
42228
  const origin = (opts.origin ?? remoteAccountHttpOrigin()).replace(/\/+$/, "");
42107
- const fetchImpl = opts.fetchImpl ?? fetch;
42108
- await fetchImpl(`${origin}${REMOTE_LOGOUT_PATH}`, {
42229
+ await relayFetch(opts.fetchImpl, `${origin}${REMOTE_LOGOUT_PATH}`, {
42109
42230
  method: "POST",
42110
42231
  headers: { "Content-Type": "application/json" },
42111
42232
  body: JSON.stringify({ accountToken: opts.accountToken })
package/dist/index.d.cts CHANGED
@@ -3443,8 +3443,14 @@ declare function installOptionalServiceCli(id: OptionalServiceId): Promise<Agent
3443
3443
  * an opaque `TypeError: fetch failed` or `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`.
3444
3444
  */
3445
3445
  declare function setHttpFetchImpl(fetchImpl: typeof fetch | null): void;
3446
- /** Expand undici/Electron `TypeError: fetch failed` with the underlying cause. */
3447
- declare function formatFetchError(err: unknown, url: string): string;
3446
+ /**
3447
+ * Expand undici/Electron `TypeError: fetch failed` with the underlying cause.
3448
+ * `tlsHint` replaces the default Linear certificate note when the failure is a
3449
+ * corporate CA rejection. Pass '' to omit that note.
3450
+ */
3451
+ declare function formatFetchError(err: unknown, url: string, opts?: {
3452
+ tlsHint?: string;
3453
+ }): string;
3448
3454
  declare function httpFetch(input: string | URL, init?: RequestInit): Promise<Response>;
3449
3455
 
3450
3456
  /**
@@ -5536,8 +5542,8 @@ declare class Orchestrator {
5536
5542
  /** Timers for orchestration session-quota auto-resume. */
5537
5543
  private readonly quotaResumeTimers;
5538
5544
  /**
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.
5545
+ * Crash-continue count per thread. Cleared on a successful finish or a user
5546
+ * send. Stream cutoffs may resume more than once; other crashes once.
5541
5547
  */
5542
5548
  private readonly crashContinued;
5543
5549
  /** Auto-continues after a worktree turn ended while a detached job still runs. */
@@ -8011,6 +8017,7 @@ type RemoteOutbound = {
8011
8017
 
8012
8018
  declare function formatRemotePrompt(text: string): string;
8013
8019
  declare function isRemoteStopCommand(text: string): boolean;
8020
+
8014
8021
  declare function handleRemoteInbound(text: string, opts: {
8015
8022
  deviceId: string;
8016
8023
  agent?: AgentKind;
package/dist/index.d.ts CHANGED
@@ -3443,8 +3443,14 @@ declare function installOptionalServiceCli(id: OptionalServiceId): Promise<Agent
3443
3443
  * an opaque `TypeError: fetch failed` or `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`.
3444
3444
  */
3445
3445
  declare function setHttpFetchImpl(fetchImpl: typeof fetch | null): void;
3446
- /** Expand undici/Electron `TypeError: fetch failed` with the underlying cause. */
3447
- declare function formatFetchError(err: unknown, url: string): string;
3446
+ /**
3447
+ * Expand undici/Electron `TypeError: fetch failed` with the underlying cause.
3448
+ * `tlsHint` replaces the default Linear certificate note when the failure is a
3449
+ * corporate CA rejection. Pass '' to omit that note.
3450
+ */
3451
+ declare function formatFetchError(err: unknown, url: string, opts?: {
3452
+ tlsHint?: string;
3453
+ }): string;
3448
3454
  declare function httpFetch(input: string | URL, init?: RequestInit): Promise<Response>;
3449
3455
 
3450
3456
  /**
@@ -5536,8 +5542,8 @@ declare class Orchestrator {
5536
5542
  /** Timers for orchestration session-quota auto-resume. */
5537
5543
  private readonly quotaResumeTimers;
5538
5544
  /**
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.
5545
+ * Crash-continue count per thread. Cleared on a successful finish or a user
5546
+ * send. Stream cutoffs may resume more than once; other crashes once.
5541
5547
  */
5542
5548
  private readonly crashContinued;
5543
5549
  /** Auto-continues after a worktree turn ended while a detached job still runs. */
@@ -8011,6 +8017,7 @@ type RemoteOutbound = {
8011
8017
 
8012
8018
  declare function formatRemotePrompt(text: string): string;
8013
8019
  declare function isRemoteStopCommand(text: string): boolean;
8020
+
8014
8021
  declare function handleRemoteInbound(text: string, opts: {
8015
8022
  deviceId: string;
8016
8023
  agent?: AgentKind;