@schoolai/shipyard 3.1.1 → 3.2.0-rc.20260415.0

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.
@@ -37,7 +37,7 @@ import {
37
37
  VaultKeyPutRequestSchema,
38
38
  VaultKeyPutResponseSchema,
39
39
  classifyClaudeCodeCompatibility
40
- } from "./chunk-ERKQ6CBC.js";
40
+ } from "./chunk-7WMHXVZ5.js";
41
41
  import {
42
42
  loadAuthToken
43
43
  } from "./chunk-IHSXN66C.js";
@@ -24345,7 +24345,8 @@ var DiffHunkAnnotationSchema = BaseCommentZodSchema.extend({
24345
24345
  lineNumber: external_exports.number(),
24346
24346
  lineContent: external_exports.string(),
24347
24347
  lineContentHash: external_exports.string(),
24348
- side: external_exports.enum(["original", "modified"])
24348
+ side: external_exports.enum(["original", "modified"]),
24349
+ diffScope: external_exports.enum(["working-tree", "branch", "last-turn", "pr"]).optional()
24349
24350
  });
24350
24351
  var PreviewElementAnnotationSchema = BaseCommentZodSchema.extend({
24351
24352
  annotationType: external_exports.literal("preview-element"),
@@ -26709,6 +26710,17 @@ var BrowserToFileIOMessageSchema = external_exports.discriminatedUnion("type", [
26709
26710
  external_exports.object({
26710
26711
  type: external_exports.literal("git_ls_files"),
26711
26712
  requestId: external_exports.string()
26713
+ }),
26714
+ external_exports.object({
26715
+ type: external_exports.literal("git_branch_diff_files"),
26716
+ requestId: external_exports.string(),
26717
+ baseRef: external_exports.string().optional()
26718
+ }),
26719
+ external_exports.object({
26720
+ type: external_exports.literal("git_branch_diff_file"),
26721
+ requestId: external_exports.string(),
26722
+ path: external_exports.string(),
26723
+ mergeBase: external_exports.string()
26712
26724
  })
26713
26725
  ]);
26714
26726
  var FileChangeSchema = external_exports.object({
@@ -26770,6 +26782,19 @@ var DaemonToFileIOMessageSchema = external_exports.discriminatedUnion("type", [
26770
26782
  requestId: external_exports.string(),
26771
26783
  files: external_exports.array(external_exports.string())
26772
26784
  }),
26785
+ external_exports.object({
26786
+ type: external_exports.literal("git_branch_diff_files_result"),
26787
+ requestId: external_exports.string(),
26788
+ files: external_exports.array(CheckpointFileEntrySchema),
26789
+ mergeBase: external_exports.string(),
26790
+ baseRef: external_exports.string()
26791
+ }),
26792
+ external_exports.object({
26793
+ type: external_exports.literal("git_branch_diff_file_result"),
26794
+ requestId: external_exports.string(),
26795
+ originalContent: external_exports.string(),
26796
+ modifiedContent: external_exports.string()
26797
+ }),
26773
26798
  external_exports.object({ type: external_exports.literal("error"), requestId: external_exports.string(), error: external_exports.string() }),
26774
26799
  external_exports.object({
26775
26800
  type: external_exports.literal("checkpoint_ready"),
@@ -28754,6 +28779,35 @@ async function rerunChecks(cwd, prNumber) {
28754
28779
  }
28755
28780
  }
28756
28781
 
28782
+ // src/shared/capabilities/git-diff.ts
28783
+ async function getDefaultBranch(cwd) {
28784
+ try {
28785
+ const ref = await runWithTimeout(
28786
+ "git",
28787
+ ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"],
28788
+ cwd,
28789
+ TIMEOUT_MS
28790
+ );
28791
+ return ref || null;
28792
+ } catch {
28793
+ }
28794
+ for (const candidate of ["origin/main", "origin/master"]) {
28795
+ try {
28796
+ await runWithTimeout("git", ["rev-parse", "--verify", candidate], cwd, TIMEOUT_MS);
28797
+ return candidate;
28798
+ } catch {
28799
+ }
28800
+ }
28801
+ return null;
28802
+ }
28803
+ async function getMergeBase(cwd, baseBranch) {
28804
+ try {
28805
+ return await runWithTimeout("git", ["merge-base", baseBranch, "HEAD"], cwd, TIMEOUT_MS);
28806
+ } catch {
28807
+ return null;
28808
+ }
28809
+ }
28810
+
28757
28811
  // src/shared/capabilities/index.ts
28758
28812
  var AutoModeConfigSchema = external_exports.object({
28759
28813
  cachedGrowthBookFeatures: external_exports.object({ tengu_auto_mode_config: external_exports.object({ enabled: external_exports.string() }) }).passthrough()
@@ -29215,6 +29269,28 @@ function narrow(value) {
29215
29269
  import { unlinkSync } from "fs";
29216
29270
  import { readFile as readFile5, unlink as unlink2, writeFile as writeFile3 } from "fs/promises";
29217
29271
  import { join as join8 } from "path";
29272
+
29273
+ // src/services/bootstrap/classify-uncaught-error.ts
29274
+ function classifyUncaughtError(error2) {
29275
+ if (!(error2 instanceof Error)) return "fatal";
29276
+ const code2 = "code" in error2 && typeof error2.code === "string" ? error2.code : void 0;
29277
+ const msg = error2.message;
29278
+ if (code2 === "EPIPE" || code2 === "ECONNRESET" || code2 === "ECONNABORTED" || code2 === "ECONNREFUSED" || code2 === "ERR_STREAM_DESTROYED") {
29279
+ return "recoverable";
29280
+ }
29281
+ if (msg.includes("can't add channel") && msg.includes("stopped")) {
29282
+ return "recoverable";
29283
+ }
29284
+ if (msg.includes("RTCDataChannel.readyState is not")) {
29285
+ return "recoverable";
29286
+ }
29287
+ if (msg.includes("ProcessTransport is not ready")) {
29288
+ return "recoverable";
29289
+ }
29290
+ return "fatal";
29291
+ }
29292
+
29293
+ // src/services/bootstrap/lifecycle.ts
29218
29294
  function isProcessAlive(pid) {
29219
29295
  try {
29220
29296
  process.kill(pid, 0);
@@ -29244,7 +29320,12 @@ var LifecycleManager = class {
29244
29320
  process.on("SIGTERM", termHandler);
29245
29321
  process.on("SIGINT", intHandler);
29246
29322
  const exceptionHandler = (error2) => {
29247
- this.#log.error({ err: error2 }, "Uncaught exception \u2014 initiating shutdown");
29323
+ const severity = classifyUncaughtError(error2);
29324
+ if (severity === "recoverable") {
29325
+ this.#log.warn({ err: error2 }, "Recoverable uncaught exception \u2014 absorbed");
29326
+ return;
29327
+ }
29328
+ this.#log.error({ err: error2 }, "Fatal uncaught exception \u2014 initiating shutdown");
29248
29329
  void this.#shutdown("uncaughtException");
29249
29330
  };
29250
29331
  const rejectionHandler = (reason) => {
@@ -29588,7 +29669,7 @@ function nanoid(size2 = 21) {
29588
29669
  }
29589
29670
 
29590
29671
  // src/services/bootstrap/signaling.ts
29591
- var DAEMON_NPM_VERSION = true ? "3.1.1" : "unknown";
29672
+ var DAEMON_NPM_VERSION = true ? "3.2.0" : "unknown";
29592
29673
  function createDaemonSignaling(config2) {
29593
29674
  const agentId = config2.agentId ?? nanoid();
29594
29675
  function send(msg) {
@@ -29743,6 +29824,7 @@ function handleBrowserPreviewChannel(port, send, sendBinary, log, options) {
29743
29824
  const requestTimeoutMs = options?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
29744
29825
  const activeRequests = /* @__PURE__ */ new Map();
29745
29826
  const cookieJar = /* @__PURE__ */ new Map();
29827
+ const previewPrefix = `/__preview/url/${encodeURIComponent(`http://localhost:${port}`)}/`;
29746
29828
  function respond(msg) {
29747
29829
  send(JSON.stringify(msg));
29748
29830
  }
@@ -29896,7 +29978,7 @@ function handleBrowserPreviewChannel(port, send, sendBinary, log, options) {
29896
29978
  res.on("data", (chunk) => chunks.push(chunk));
29897
29979
  res.on("end", () => {
29898
29980
  const rawJs = Buffer.concat(chunks).toString("utf-8");
29899
- const rewritten = rewriteJsImportPaths(rawJs);
29981
+ const rewritten = rewriteJsImportPaths(rawJs, previewPrefix);
29900
29982
  const body = new TextEncoder().encode(rewritten);
29901
29983
  headers["content-length"] = String(body.byteLength);
29902
29984
  respond({
@@ -30019,8 +30101,9 @@ function injectBaseHref(html, origin) {
30019
30101
  function rewriteRootRelativePaths(html) {
30020
30102
  return html.replace(/((?:src|href|action)\s*=\s*["'])\/(?!\/)/gi, "$1./");
30021
30103
  }
30022
- function rewriteJsImportPaths(js) {
30023
- return js.replace(/((?:from|import)\s*\(\s*["'])\/(?!\/)/g, "$1./").replace(/(from\s+["'])\/(?!\/)/g, "$1./");
30104
+ function rewriteJsImportPaths(js, previewPrefix) {
30105
+ const replacement = previewPrefix ?? "./";
30106
+ return js.replace(/((?:from|import)\s*\(\s*["'])\/(?!\/)/g, `$1${replacement}`).replace(/(from\s+["'])\/(?!\/)/g, `$1${replacement}`);
30024
30107
  }
30025
30108
  function handleBrowserPreviewUrlChannel(send, sendBinary, log, options) {
30026
30109
  const requestTimeoutMs = options?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
@@ -30113,7 +30196,10 @@ function handleBrowserPreviewUrlChannel(send, sendBinary, log, options) {
30113
30196
  injectBaseHref(new TextDecoder().decode(rawBody), targetUrl.origin)
30114
30197
  );
30115
30198
  } else if (isJs) {
30116
- body = new TextEncoder().encode(rewriteJsImportPaths(new TextDecoder().decode(rawBody)));
30199
+ const urlPrefix = `/__preview/url/${encodeURIComponent(targetUrl.origin)}/`;
30200
+ body = new TextEncoder().encode(
30201
+ rewriteJsImportPaths(new TextDecoder().decode(rawBody), urlPrefix)
30202
+ );
30117
30203
  } else {
30118
30204
  body = rawBody;
30119
30205
  }
@@ -32301,8 +32387,7 @@ async function sweepStaleTasks(taskStateStore, taskManager, log) {
32301
32387
  const noBroadcast = { broadcast: false };
32302
32388
  for (const action of actions) {
32303
32389
  if (action.kind === "mark_input_required") {
32304
- await taskStateStore.updateTaskStatus(action.taskId, "input_required", noBroadcast);
32305
- await taskStateStore.acknowledge(action.taskId, noBroadcast);
32390
+ await taskStateStore.sweepToInputRequired(action.taskId, noBroadcast);
32306
32391
  log({
32307
32392
  event: "stale_task_swept",
32308
32393
  taskId: action.taskId,
@@ -43227,6 +43312,11 @@ Use \`add_comment\` to annotate your work and respond to reviewer feedback. Use
43227
43312
 
43228
43313
  You can **create** comments on diff and plan surfaces only (\`mode: "diff"\` and \`mode: "plan"\`). You can **reply** to comments on all 5 surfaces. Preview and canvas comments are user-originated feedback on your running app or visualizations \u2014 respond by replying to acknowledge and acting on the feedback.
43229
43314
 
43315
+ **Diff scope** \u2014 \`diff\` surface comments may include a \`diff-scope\` attribute indicating which view the comment was made on:
43316
+ - \`working-tree\` \u2014 uncommitted changes (your latest edits, like \`git diff\`)
43317
+ - \`branch\` \u2014 full branch diff vs base branch (all commits + uncommitted, like \`git diff main...HEAD\`)
43318
+ - \`last-turn\` \u2014 changes from a specific agent turn
43319
+
43230
43320
  **When publishing plans** \u2014 \`mode: "plan"\` for non-obvious design decisions, anchored to specific plan text.
43231
43321
 
43232
43322
  **Periodically during long tasks** \u2014 \`list_comments\` to catch missed feedback, especially after context loss.
@@ -75126,8 +75216,10 @@ function deriveArtifact(annotation, taskId) {
75126
75216
  }
75127
75217
  function surfaceAttrs(annotation) {
75128
75218
  switch (annotation.annotationType) {
75129
- case "diff-hunk":
75130
- return ` file="${escapeXmlAttr(annotation.filePath)}" line="${annotation.lineNumber}"`;
75219
+ case "diff-hunk": {
75220
+ const scopeAttr = annotation.diffScope ? ` diff-scope="${escapeXmlAttr(annotation.diffScope)}"` : "";
75221
+ return ` file="${escapeXmlAttr(annotation.filePath)}" line="${annotation.lineNumber}"${scopeAttr}`;
75222
+ }
75131
75223
  case "plan-text":
75132
75224
  return annotation.anchorText ? ` anchor="${escapeXmlAttr(annotation.anchorText)}"` : "";
75133
75225
  case "preview-element":
@@ -76776,8 +76868,6 @@ ${conversationReplay}` : conversationReplay;
76776
76868
  });
76777
76869
  this.#ownSessionId = null;
76778
76870
  this.#effectiveSpawnMode = { kind: "fresh" };
76779
- this.#config.sessionPersistence.clear(this.#config.channelId).catch(() => {
76780
- });
76781
76871
  }
76782
76872
  if (this.#tryForkFailedRetry(error2)) return;
76783
76873
  if (this.#tryStaleResumeRetry(error2)) return;
@@ -80354,7 +80444,7 @@ var StructuredTaskTracker = class {
80354
80444
  * Attach a file watcher for CC task files. Called after init_received
80355
80445
  * when we know the CC session ID, or immediately for resumed sessions.
80356
80446
  */
80357
- attachFileWatcher(sessionId) {
80447
+ attachFileWatcher(sessionId, options) {
80358
80448
  if (this.#ccTaskWatcherDispose) {
80359
80449
  this.#deps.log({
80360
80450
  event: "file_watcher_already_attached",
@@ -80365,17 +80455,17 @@ var StructuredTaskTracker = class {
80365
80455
  }
80366
80456
  const watcher = createCCTaskFileWatcher(sessionId, this.#deps.log);
80367
80457
  this.#ccTaskFileWriter = createCCTaskFileWriter(watcher.dir, this.#deps.log);
80368
- this.#initFileWatcher(watcher);
80458
+ this.#initFileWatcher(watcher, options);
80369
80459
  if (this.#currentOverlay) {
80370
- this.#flushStructuredTasks();
80460
+ this.#flushStructuredTasks(options);
80371
80461
  }
80372
80462
  }
80373
80463
  /**
80374
80464
  * Apply an overlay on top of CC tasks. Stores the overlay and re-flushes.
80375
80465
  */
80376
- applyOverlay(overlay) {
80466
+ applyOverlay(overlay, options) {
80377
80467
  this.#currentOverlay = overlay;
80378
- this.#flushStructuredTasks();
80468
+ this.#flushStructuredTasks(options);
80379
80469
  }
80380
80470
  processStructuredTaskEvents(content) {
80381
80471
  const events = extractStructuredTaskEvents(content);
@@ -80500,11 +80590,11 @@ var StructuredTaskTracker = class {
80500
80590
  applyDepEdgeAdditions(merged, overlay.depEdges);
80501
80591
  return merged;
80502
80592
  }
80503
- #flushStructuredTasks() {
80593
+ #flushStructuredTasks(options) {
80504
80594
  const overlay = this.#currentOverlay ?? DEFAULT_TASK_OVERLAY;
80505
80595
  const merged = this.#applyOverlayToMap(this.#structuredTasks, overlay);
80506
80596
  const todoProgress = this.#computeTodoProgress(merged);
80507
- this.#deps.updateStructuredTasks(Object.fromEntries(merged), todoProgress);
80597
+ this.#deps.updateStructuredTasks(Object.fromEntries(merged), todoProgress, options);
80508
80598
  if (!this.#suppressWriteThrough) {
80509
80599
  this.#ccTaskFileWriter?.writeMergedTasks(merged);
80510
80600
  }
@@ -80525,9 +80615,12 @@ var StructuredTaskTracker = class {
80525
80615
  currentActivity
80526
80616
  };
80527
80617
  }
80528
- #initFileWatcher(watcher) {
80618
+ #initFileWatcher(watcher, initOptions) {
80619
+ let isFirstRead = !!initOptions?.preserveUpdatedAt;
80529
80620
  this.#ccTaskWatcherDispose = watcher.watch((tasks) => {
80530
- this.#reconcileFromDisk(tasks);
80621
+ const opts = isFirstRead ? initOptions : void 0;
80622
+ isFirstRead = false;
80623
+ this.#reconcileFromDisk(tasks, opts);
80531
80624
  });
80532
80625
  }
80533
80626
  /**
@@ -80537,7 +80630,7 @@ var StructuredTaskTracker = class {
80537
80630
  * - Remove tasks that were previously from disk but are now gone
80538
80631
  * - Re-flush with overlay applied
80539
80632
  */
80540
- #reconcileFromDisk(ccTasks) {
80633
+ #reconcileFromDisk(ccTasks, options) {
80541
80634
  const currentDiskIds = /* @__PURE__ */ new Set();
80542
80635
  for (const file of ccTasks) {
80543
80636
  currentDiskIds.add(file.id);
@@ -80558,7 +80651,7 @@ var StructuredTaskTracker = class {
80558
80651
  }
80559
80652
  this.#suppressWriteThrough = true;
80560
80653
  try {
80561
- this.#flushStructuredTasks();
80654
+ this.#flushStructuredTasks(options);
80562
80655
  } finally {
80563
80656
  this.#suppressWriteThrough = false;
80564
80657
  }
@@ -81296,7 +81389,9 @@ var Task = class _Task {
81296
81389
  log: deps.log
81297
81390
  });
81298
81391
  if (deps.existingSessionId) {
81299
- this.#structuredTaskTracker.attachFileWatcher(deps.existingSessionId);
81392
+ this.#structuredTaskTracker.attachFileWatcher(deps.existingSessionId, {
81393
+ preserveUpdatedAt: true
81394
+ });
81300
81395
  }
81301
81396
  this.#subagentManager = new SubagentManager({
81302
81397
  taskId: deps.taskId,
@@ -82524,8 +82619,8 @@ Use this context to maintain continuity. You have already done this work \u2014
82524
82619
  }
82525
82620
  }
82526
82621
  }
82527
- applyOverlay(overlay) {
82528
- this.#structuredTaskTracker.applyOverlay(overlay);
82622
+ applyOverlay(overlay, options) {
82623
+ this.#structuredTaskTracker.applyOverlay(overlay, options);
82529
82624
  }
82530
82625
  /** ---------------------------------------------------------------- */
82531
82626
  /** Resource resolution */
@@ -83202,7 +83297,8 @@ var TaskManager = class {
83202
83297
  this.#tasks.set(taskId, { taskId, channelId, cwd, mode, orchestrator });
83203
83298
  this.#flushPendingStreamSubs(taskId, orchestrator);
83204
83299
  this.#deps.taskStateStore.getTask(taskId).then((record) => {
83205
- if (record?.taskOverlay) orchestrator.applyOverlay(record.taskOverlay);
83300
+ if (record?.taskOverlay)
83301
+ orchestrator.applyOverlay(record.taskOverlay, { preserveUpdatedAt: true });
83206
83302
  }).catch((err) => {
83207
83303
  this.#deps.log({
83208
83304
  event: "overlay_restore_failed",
@@ -83244,10 +83340,27 @@ var TaskManager = class {
83244
83340
  getTaskMode(taskId) {
83245
83341
  return this.#tasks.get(taskId)?.mode ?? "task";
83246
83342
  }
83247
- handleUserMessage(taskId, content, settings, cwd, participantId, senderDisplayName) {
83248
- const task = this.#tasks.get(taskId);
83343
+ async handleUserMessage(taskId, content, settings, cwd, participantId, senderDisplayName) {
83344
+ let task = this.#tasks.get(taskId);
83249
83345
  if (!task) {
83250
- throw new Error(`No orchestrator for task ${taskId}`);
83346
+ const record = await this.#deps.taskStateStore.getTask(taskId);
83347
+ if (!record) {
83348
+ throw new Error(`No orchestrator for task ${taskId}`);
83349
+ }
83350
+ this.restoreOrchestrator(taskId, record.channelId, {
83351
+ initialState: "cold_idle",
83352
+ cwd: record.cwd,
83353
+ mode: record.mode,
83354
+ costBaseline: {
83355
+ totalCostUsd: record.totalCostUsd,
83356
+ totalOutputTokens: record.totalOutputTokens
83357
+ }
83358
+ });
83359
+ task = this.#tasks.get(taskId);
83360
+ if (!task) {
83361
+ throw new Error(`Failed to restore orchestrator for task ${taskId}`);
83362
+ }
83363
+ this.#deps.log({ event: "task_lazy_restored", taskId, channelId: record.channelId });
83251
83364
  }
83252
83365
  if (cwd && cwd !== task.cwd) {
83253
83366
  task.cwd = cwd;
@@ -83567,8 +83680,13 @@ var TaskManager = class {
83567
83680
  });
83568
83681
  });
83569
83682
  },
83570
- updateStructuredTasks: (tasks, todoProgress) => {
83571
- this.#deps.taskStateStore.updateStructuredTasks(taskId, tasks, todoProgress).catch((err) => {
83683
+ updateStructuredTasks: (tasks, todoProgress, options) => {
83684
+ this.#deps.taskStateStore.updateStructuredTasks(
83685
+ taskId,
83686
+ tasks,
83687
+ todoProgress,
83688
+ options ? { preserveUpdatedAt: options.preserveUpdatedAt } : void 0
83689
+ ).catch((err) => {
83572
83690
  this.#deps.log({
83573
83691
  event: "task_state_store_structured_tasks_failed",
83574
83692
  taskId,
@@ -83715,52 +83833,29 @@ function buildTaskStateStore(dataDir) {
83715
83833
  });
83716
83834
  },
83717
83835
  async updateTaskStatus(taskId, status, options) {
83718
- const task = await store.get(taskId);
83719
- if (!task) return;
83720
- pushBroadcast(options);
83721
- await store.set(taskId, applyStatusTransition(task, status, Date.now()));
83836
+ await safeUpdate(taskId, (task) => applyStatusTransition(task, status, Date.now()), options);
83722
83837
  },
83723
83838
  async updateTitle(taskId, title, options) {
83724
- const task = await store.get(taskId);
83725
- if (!task) return;
83726
- pushBroadcast(options);
83727
- await store.set(taskId, {
83728
- ...task,
83729
- title,
83730
- updatedAt: Date.now()
83731
- });
83839
+ await safeUpdate(taskId, (task) => ({ ...task, title, updatedAt: Date.now() }), options);
83732
83840
  },
83733
83841
  async updateCwd(taskId, cwd, options) {
83734
- const task = await store.get(taskId);
83735
- if (!task) return;
83736
- pushBroadcast(options);
83737
- await store.set(taskId, {
83738
- ...task,
83739
- cwd,
83740
- updatedAt: Date.now()
83741
- });
83842
+ await safeUpdate(taskId, (task) => ({ ...task, cwd, updatedAt: Date.now() }), options);
83742
83843
  },
83743
83844
  async updateMode(taskId, mode, options) {
83744
- const task = await store.get(taskId);
83745
- if (!task) return;
83746
- pushBroadcast(options);
83747
- await store.set(taskId, {
83748
- ...task,
83749
- mode,
83750
- updatedAt: Date.now()
83751
- });
83845
+ await safeUpdate(taskId, (task) => ({ ...task, mode, updatedAt: Date.now() }), options);
83752
83846
  },
83753
83847
  async updateTodoProgress(taskId, progress, options) {
83754
- const task = await store.get(taskId);
83755
- if (!task) return;
83756
- pushBroadcast(options);
83757
- await store.set(taskId, {
83758
- ...task,
83759
- todoCompleted: progress.todoCompleted,
83760
- todoTotal: progress.todoTotal,
83761
- currentActivity: progress.currentActivity,
83762
- updatedAt: Date.now()
83763
- });
83848
+ await safeUpdate(
83849
+ taskId,
83850
+ (task) => ({
83851
+ ...task,
83852
+ todoCompleted: progress.todoCompleted,
83853
+ todoTotal: progress.todoTotal,
83854
+ currentActivity: progress.currentActivity,
83855
+ updatedAt: Date.now()
83856
+ }),
83857
+ options
83858
+ );
83764
83859
  },
83765
83860
  async acknowledge(taskId, options) {
83766
83861
  await safeUpdate(taskId, (task) => ({ ...task, acknowledgedAt: Date.now() }), options);
@@ -83769,39 +83864,42 @@ function buildTaskStateStore(dataDir) {
83769
83864
  await safeUpdate(taskId, (task) => ({ ...task, pinned: !task.pinned }), options);
83770
83865
  },
83771
83866
  async updateStructuredTasks(taskId, tasks, todoProgress, options) {
83772
- const task = await store.get(taskId);
83773
- if (!task) return;
83774
- pushBroadcast(options);
83775
- await store.set(taskId, {
83776
- ...task,
83777
- structuredTasks: tasks,
83778
- ...todoProgress && {
83779
- todoCompleted: todoProgress.todoCompleted,
83780
- todoTotal: todoProgress.todoTotal,
83781
- currentActivity: todoProgress.currentActivity
83782
- },
83783
- updatedAt: Date.now()
83784
- });
83867
+ await safeUpdate(
83868
+ taskId,
83869
+ (task) => ({
83870
+ ...task,
83871
+ structuredTasks: tasks,
83872
+ ...todoProgress && {
83873
+ todoCompleted: todoProgress.todoCompleted,
83874
+ todoTotal: todoProgress.todoTotal,
83875
+ currentActivity: todoProgress.currentActivity
83876
+ },
83877
+ updatedAt: options?.preserveUpdatedAt ? task.updatedAt : Date.now()
83878
+ }),
83879
+ options
83880
+ );
83785
83881
  },
83786
83882
  async updateTaskOverlay(taskId, overlay, options) {
83787
- const task = await store.get(taskId);
83788
- if (!task) return;
83789
- pushBroadcast(options);
83790
- await store.set(taskId, {
83791
- ...task,
83792
- taskOverlay: overlay,
83793
- updatedAt: Date.now()
83794
- });
83883
+ await safeUpdate(
83884
+ taskId,
83885
+ (task) => ({
83886
+ ...task,
83887
+ taskOverlay: overlay,
83888
+ updatedAt: options?.preserveUpdatedAt ? task.updatedAt : Date.now()
83889
+ }),
83890
+ options
83891
+ );
83795
83892
  },
83796
83893
  async updateComposerSettings(taskId, settings, options) {
83797
- const task = await store.get(taskId);
83798
- if (!task) return;
83799
- pushBroadcast(options);
83800
- await store.set(taskId, {
83801
- ...task,
83802
- composerSettings: { ...task.composerSettings, ...settings },
83803
- updatedAt: Date.now()
83804
- });
83894
+ await safeUpdate(
83895
+ taskId,
83896
+ (task) => ({
83897
+ ...task,
83898
+ composerSettings: { ...task.composerSettings, ...settings },
83899
+ updatedAt: Date.now()
83900
+ }),
83901
+ options
83902
+ );
83805
83903
  },
83806
83904
  async updateCostStats(taskId, stats, options) {
83807
83905
  await safeUpdate(
@@ -83827,12 +83925,27 @@ function buildTaskStateStore(dataDir) {
83827
83925
  options
83828
83926
  );
83829
83927
  },
83928
+ async sweepToInputRequired(taskId, options) {
83929
+ await safeUpdate(
83930
+ taskId,
83931
+ (task) => ({
83932
+ ...task,
83933
+ status: "input_required",
83934
+ taskStartedAt: null
83935
+ }),
83936
+ options
83937
+ );
83938
+ },
83830
83939
  async getTask(taskId) {
83831
83940
  return store.get(taskId);
83832
83941
  },
83833
83942
  async listTasks() {
83834
83943
  return store.list();
83835
83944
  },
83945
+ async listTasksWithVersion() {
83946
+ const tasks = await store.list();
83947
+ return { tasks, version: _version };
83948
+ },
83836
83949
  subscribe(listener) {
83837
83950
  taskListeners.add(listener);
83838
83951
  return () => {
@@ -84145,13 +84258,15 @@ ${additionalSystemPrompt}` : basePrompt;
84145
84258
  async function detectInitialCapabilities(tokenStore, settings) {
84146
84259
  return detectCapabilities(tokenStore, settings.preferredAnthropicAuth ?? void 0);
84147
84260
  }
84148
- function applyProxyFiltering(merged, proxyRef, proxyManagedNames, disabledNames) {
84149
- for (const name of proxyManagedNames) {
84261
+ function applyProxyFiltering(merged, proxyRef, disabledNames) {
84262
+ const proxy = proxyRef.current;
84263
+ if (!proxy) return;
84264
+ for (const name of proxy.getAllManagedNames()) {
84150
84265
  if (name in merged) {
84151
84266
  delete merged[name];
84152
84267
  }
84153
84268
  }
84154
- const freshConfigs = proxyRef.current?.getServerConfigs() ?? /* @__PURE__ */ new Map();
84269
+ const freshConfigs = proxy.getServerConfigs();
84155
84270
  for (const [name, config2] of freshConfigs) {
84156
84271
  if (!disabledNames.has(name)) {
84157
84272
  merged[name] = config2;
@@ -84180,7 +84295,7 @@ function collectProxyEndpoints(capabilitiesRef, claudeAiToken) {
84180
84295
  }
84181
84296
  return endpoints;
84182
84297
  }
84183
- async function initializeProxyServer(deps, capabilitiesRef, proxyManagedNames, onReauthComplete) {
84298
+ async function initializeProxyServer(deps, capabilitiesRef, onReauthComplete) {
84184
84299
  const claudeAiToken = await readAnthropicOAuthToken();
84185
84300
  const proxyEndpoints = collectProxyEndpoints(capabilitiesRef, claudeAiToken ?? void 0);
84186
84301
  if (proxyEndpoints.size === 0) return { proxyServer: null };
@@ -84192,11 +84307,14 @@ async function initializeProxyServer(deps, capabilitiesRef, proxyManagedNames, o
84192
84307
  });
84193
84308
  try {
84194
84309
  await proxy.initialize(proxyEndpoints);
84195
- for (const name of proxyEndpoints.keys()) proxyManagedNames.add(name);
84310
+ const connectedNames = proxy.getServerNames();
84311
+ const allNames = [...proxyEndpoints.keys()];
84196
84312
  deps.log({
84197
84313
  event: "mcp_proxy_initialized",
84198
84314
  serverCount: proxyEndpoints.size,
84199
- serverNames: [...proxyEndpoints.keys()]
84315
+ connectedCount: connectedNames.length,
84316
+ connectedNames,
84317
+ failedNames: allNames.filter((n) => !connectedNames.includes(n))
84200
84318
  });
84201
84319
  return { proxyServer: proxy };
84202
84320
  } catch (err) {
@@ -84409,7 +84527,6 @@ async function createDaemon(deps) {
84409
84527
  initialContent
84410
84528
  );
84411
84529
  }
84412
- const proxyManagedNames = /* @__PURE__ */ new Set();
84413
84530
  const resolveMcpServers = () => {
84414
84531
  if (isVanillaAgentMode()) return void 0;
84415
84532
  const userServers = resolveUserMcpServers(capabilitiesRef.current, deps.tokenStore);
@@ -84417,9 +84534,11 @@ async function createDaemon(deps) {
84417
84534
  capabilitiesRef.current?.mcpServers?.filter((s2) => !s2.enabled).map((s2) => s2.name) ?? []
84418
84535
  );
84419
84536
  const pluginServers = resolvePluginMcpServersMap(deps.tokenStore, deps.log, disabledNames);
84420
- if (!userServers && pluginServers.size === 0 && proxyManagedNames.size === 0) return void 0;
84537
+ const proxyManagedNames = proxyRef.current?.getAllManagedNames() ?? [];
84538
+ if (!userServers && pluginServers.size === 0 && proxyManagedNames.length === 0)
84539
+ return void 0;
84421
84540
  const merged = mergePluginAndUserServers(pluginServers, userServers, deps.log);
84422
- applyProxyFiltering(merged, proxyRef, proxyManagedNames, disabledNames);
84541
+ applyProxyFiltering(merged, proxyRef, disabledNames);
84423
84542
  deps.log({
84424
84543
  event: "resolve_mcp_servers",
84425
84544
  capabilityServerCount: capabilitiesRef.current?.mcpServers?.length ?? 0,
@@ -84427,7 +84546,7 @@ async function createDaemon(deps) {
84427
84546
  userServerCount: userServers ? Object.keys(userServers).length : 0,
84428
84547
  userServerNames: userServers ? Object.keys(userServers) : [],
84429
84548
  pluginServerCount: pluginServers.size,
84430
- proxyManaged: [...proxyManagedNames],
84549
+ proxyManaged: proxyManagedNames,
84431
84550
  mergedCount: Object.keys(merged).length,
84432
84551
  mergedNames: Object.keys(merged)
84433
84552
  });
@@ -84493,7 +84612,13 @@ async function createDaemon(deps) {
84493
84612
  taskManager.createTask(params);
84494
84613
  },
84495
84614
  sendMessage: (taskId, content, settings, cwd) => {
84496
- taskManager.handleUserMessage(taskId, content, settings, cwd);
84615
+ taskManager.handleUserMessage(taskId, content, settings, cwd).catch((err) => {
84616
+ deps.log({
84617
+ event: "schedule_send_message_error",
84618
+ taskId,
84619
+ error: err instanceof Error ? err.message : String(err)
84620
+ });
84621
+ });
84497
84622
  },
84498
84623
  getRunningScheduleIds: async () => {
84499
84624
  const running = /* @__PURE__ */ new Set();
@@ -84652,7 +84777,6 @@ async function createDaemon(deps) {
84652
84777
  const { proxyServer } = await initializeProxyServer(
84653
84778
  deps,
84654
84779
  capabilitiesRef,
84655
- proxyManagedNames,
84656
84780
  () => oauthStateStore.invalidate()
84657
84781
  );
84658
84782
  proxyRef.current = proxyServer;
@@ -87109,7 +87233,10 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
87109
87233
  if (resolved) {
87110
87234
  const without = { ...resolved };
87111
87235
  delete without[serverName];
87112
- daemon.preWarmManager.updateMcpServers(without).then(() => daemon.preWarmManager.updateMcpServers(resolved)).then(() => daemon.taskManager.triggerFastMcpPolling()).catch((err) => {
87236
+ daemon.preWarmManager.updateMcpServers(without).then(() => daemon.preWarmManager.updateMcpServers(resolved)).then(() => {
87237
+ daemon.taskManager.reconnectMcpServer(serverName);
87238
+ daemon.taskManager.triggerFastMcpPolling();
87239
+ }).catch((err) => {
87113
87240
  logAdapter({
87114
87241
  event: `${eventName}_reconnect_failed`,
87115
87242
  serverName,
@@ -87328,12 +87455,11 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
87328
87455
  });
87329
87456
  },
87330
87457
  onRequestTaskIndex: () => {
87331
- const snapshotVersion = daemon.taskStateStore.version;
87332
- daemon.taskStateStore.listTasks().then((tasks) => {
87458
+ daemon.taskStateStore.listTasksWithVersion().then(({ tasks, version }) => {
87333
87459
  controlHandler.sendControl({
87334
87460
  type: "task_index_snapshot",
87335
87461
  tasks,
87336
- version: snapshotVersion
87462
+ version
87337
87463
  });
87338
87464
  }).catch((err) => {
87339
87465
  logAdapter({
@@ -87547,19 +87673,6 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
87547
87673
  }
87548
87674
  };
87549
87675
  pushMcpStatus(5);
87550
- const initialSnapshotVersion = daemon.taskStateStore.version;
87551
- daemon.taskStateStore.listTasks().then((tasks) => {
87552
- controlHandler.sendControl({
87553
- type: "task_index_snapshot",
87554
- tasks,
87555
- version: initialSnapshotVersion
87556
- });
87557
- }).catch((err) => {
87558
- logAdapter({
87559
- event: "task_index_initial_push_failed",
87560
- error: err instanceof Error ? err.message : String(err)
87561
- });
87562
- });
87563
87676
  daemon.userSettingsStore.getSettings().then((settings) => {
87564
87677
  controlHandler.sendControl({ type: "user_settings_snapshot", settings });
87565
87678
  }).catch((err) => {
@@ -87661,7 +87774,7 @@ function handleMessageChannel(opts) {
87661
87774
  }
87662
87775
  return result.data;
87663
87776
  }
87664
- function handleSendMessage2(msg) {
87777
+ async function handleSendMessage2(msg) {
87665
87778
  try {
87666
87779
  const settings = {
87667
87780
  model: msg.model,
@@ -87672,7 +87785,7 @@ function handleMessageChannel(opts) {
87672
87785
  if (opts.onUserMessage) {
87673
87786
  opts.onUserMessage(msg.content, settings, msg.cwd, participantId, senderDisplayName);
87674
87787
  } else {
87675
- taskManager.handleUserMessage(
87788
+ await taskManager.handleUserMessage(
87676
87789
  taskId,
87677
87790
  msg.content,
87678
87791
  settings,
@@ -87755,7 +87868,7 @@ function handleMessageChannel(opts) {
87755
87868
  }
87756
87869
  switch (msg.type) {
87757
87870
  case "send_message":
87758
- handleSendMessage2(msg);
87871
+ void handleSendMessage2(msg);
87759
87872
  break;
87760
87873
  case "stop":
87761
87874
  if (opts.onStop) opts.onStop();
@@ -88445,6 +88558,12 @@ function handleFileIOChannel(initialCwd, send, log, deps) {
88445
88558
  case "git_ls_files":
88446
88559
  handleGitLsFiles(msg.requestId);
88447
88560
  break;
88561
+ case "git_branch_diff_files":
88562
+ handleGitBranchDiffFiles(msg.requestId, msg.baseRef);
88563
+ break;
88564
+ case "git_branch_diff_file":
88565
+ handleGitBranchDiffFile(msg.requestId, msg.path, msg.mergeBase);
88566
+ break;
88448
88567
  default:
88449
88568
  assertNever(msg);
88450
88569
  }
@@ -88546,6 +88665,79 @@ function handleFileIOChannel(initialCwd, send, log, deps) {
88546
88665
  respondError(requestId, formatError(err));
88547
88666
  }
88548
88667
  }
88668
+ async function handleGitBranchDiffFiles(requestId, baseRefOverride) {
88669
+ try {
88670
+ const baseRef = baseRefOverride ?? await getDefaultBranch(cwd);
88671
+ if (!baseRef) {
88672
+ respond({
88673
+ type: "git_branch_diff_files_result",
88674
+ requestId,
88675
+ files: [],
88676
+ mergeBase: "",
88677
+ baseRef: ""
88678
+ });
88679
+ return;
88680
+ }
88681
+ const mergeBase = await getMergeBase(cwd, baseRef);
88682
+ if (!mergeBase) {
88683
+ respond({
88684
+ type: "git_branch_diff_files_result",
88685
+ requestId,
88686
+ files: [],
88687
+ mergeBase: "",
88688
+ baseRef
88689
+ });
88690
+ return;
88691
+ }
88692
+ const { stdout } = await execFileAsync3(
88693
+ "git",
88694
+ ["diff", "--name-status", `${mergeBase}..HEAD`],
88695
+ { cwd, maxBuffer: 10 * 1024 * 1024 }
88696
+ );
88697
+ const files = stdout.split("\n").filter(Boolean).map((line) => {
88698
+ const parts = line.split(" ");
88699
+ const statusCode = parts[0] ?? "";
88700
+ const filePath = parts[1] ?? "";
88701
+ let status;
88702
+ if (statusCode === "A") {
88703
+ status = "added";
88704
+ } else if (statusCode === "D") {
88705
+ status = "deleted";
88706
+ } else {
88707
+ status = "modified";
88708
+ }
88709
+ return { path: filePath, status, insertions: 0, deletions: 0 };
88710
+ });
88711
+ respond({
88712
+ type: "git_branch_diff_files_result",
88713
+ requestId,
88714
+ files,
88715
+ mergeBase,
88716
+ baseRef
88717
+ });
88718
+ } catch (err) {
88719
+ respondError(requestId, formatError(err));
88720
+ }
88721
+ }
88722
+ async function handleGitBranchDiffFile(requestId, filePath, mergeBase) {
88723
+ try {
88724
+ const originalContent = await readGitObject(`${mergeBase}:${filePath}`) ?? "";
88725
+ let modifiedContent;
88726
+ try {
88727
+ modifiedContent = await readFile25(resolve(cwd, filePath), "utf-8");
88728
+ } catch {
88729
+ modifiedContent = "";
88730
+ }
88731
+ respond({
88732
+ type: "git_branch_diff_file_result",
88733
+ requestId,
88734
+ originalContent,
88735
+ modifiedContent
88736
+ });
88737
+ } catch (err) {
88738
+ respondError(requestId, formatError(err));
88739
+ }
88740
+ }
88549
88741
  async function handleGitDiffFile(requestId, safeRelPath, absPath, cached) {
88550
88742
  try {
88551
88743
  let originalContent = "";
@@ -89030,6 +89222,9 @@ function handleLSPChannel(cwd, send, log) {
89030
89222
  exited = true;
89031
89223
  child = null;
89032
89224
  });
89225
+ proc.stdin?.on("error", (err) => {
89226
+ log({ event: "lsp_stdin_error", error: err.message });
89227
+ });
89033
89228
  log({ event: "lsp_spawned", pid: proc.pid });
89034
89229
  return proc;
89035
89230
  }
@@ -89066,8 +89261,13 @@ function handleLSPChannel(cwd, send, log) {
89066
89261
  const header = `Content-Length: ${Buffer.byteLength(data)}\r
89067
89262
  \r
89068
89263
  `;
89069
- child.stdin?.write(header);
89070
- child.stdin?.write(data);
89264
+ try {
89265
+ child.stdin?.write(header);
89266
+ child.stdin?.write(data);
89267
+ } catch {
89268
+ log({ event: "lsp_write_failed", exited });
89269
+ child = null;
89270
+ }
89071
89271
  }
89072
89272
  function dispose() {
89073
89273
  disposed = true;
@@ -89248,84 +89448,94 @@ function createPeerManager(config2) {
89248
89448
  return factoryPromise;
89249
89449
  }
89250
89450
  function handleDataChannel(machineId, event) {
89251
- config2.onPeerDataChannel?.(machineId);
89252
- const channel = event.channel;
89253
- const label = channel.label ?? "";
89254
- const route = routeDataChannel(label);
89255
- switch (route.kind) {
89256
- case "thread_messages": {
89257
- config2.log.debug(
89258
- { machineId, taskId: route.taskId, threadId: route.threadId },
89259
- "Thread messages data channel received"
89260
- );
89261
- config2.onThreadMessageChannel?.(machineId, event.channel, route.taskId, route.threadId);
89262
- return;
89263
- }
89264
- case "task_messages": {
89265
- config2.log.debug(
89266
- { machineId, taskId: route.taskId },
89267
- "Task messages data channel received"
89268
- );
89269
- config2.onTaskMessageChannel?.(machineId, event.channel, route.taskId);
89270
- return;
89271
- }
89272
- case "daemon_control": {
89273
- config2.log.info({ machineId }, "Daemon control data channel received");
89274
- config2.onControlChannel?.(machineId, event.channel);
89275
- return;
89276
- }
89277
- case "loro_sync": {
89278
- config2.log.info({ machineId }, "Loro sync data channel received");
89279
- const rawChannel = event.channel;
89280
- guardLoroChannelSend(rawChannel, config2.log);
89281
- config2.webrtcAdapter.attachDataChannel(
89282
- machineIdToPeerId(machineId),
89283
- event.channel
89284
- );
89285
- return;
89286
- }
89287
- case "file_io": {
89288
- config2.log.debug({ machineId, id: route.id }, "File I/O data channel received");
89289
- config2.onFileIOChannel?.(machineId, event.channel, route.id);
89290
- return;
89291
- }
89292
- case "lsp": {
89293
- config2.log.debug({ machineId, id: route.id }, "LSP data channel received");
89294
- config2.onLSPChannel?.(machineId, event.channel, route.id);
89295
- return;
89296
- }
89297
- case "browser_preview": {
89298
- config2.log.debug({ machineId, port: route.port }, "Browser preview data channel received");
89299
- config2.onBrowserPreviewChannel?.(machineId, event.channel, route.port);
89300
- return;
89301
- }
89302
- case "browser_preview_url": {
89303
- config2.log.debug({ machineId }, "Browser preview URL data channel received");
89304
- config2.onBrowserPreviewUrlChannel?.(machineId, event.channel);
89305
- return;
89306
- }
89307
- case "terminal": {
89308
- config2.log.debug(
89309
- { machineId, taskId: route.taskId, terminalId: route.terminalId },
89310
- "Terminal data channel received"
89311
- );
89312
- config2.onTerminalChannel?.(machineId, event.channel, route.taskId, route.terminalId);
89313
- return;
89314
- }
89315
- case "asset_transfer": {
89316
- config2.log.debug({ machineId }, "Asset transfer data channel received");
89317
- config2.onAssetTransferChannel?.(machineId, event.channel);
89318
- return;
89319
- }
89320
- case "unknown": {
89321
- config2.log.warn(
89322
- { machineId, label: route.label },
89323
- "Ignoring unrecognized data channel label"
89324
- );
89325
- return;
89451
+ try {
89452
+ config2.onPeerDataChannel?.(machineId);
89453
+ const channel = event.channel;
89454
+ const label = channel.label ?? "";
89455
+ const route = routeDataChannel(label);
89456
+ switch (route.kind) {
89457
+ case "thread_messages": {
89458
+ config2.log.debug(
89459
+ { machineId, taskId: route.taskId, threadId: route.threadId },
89460
+ "Thread messages data channel received"
89461
+ );
89462
+ config2.onThreadMessageChannel?.(machineId, event.channel, route.taskId, route.threadId);
89463
+ return;
89464
+ }
89465
+ case "task_messages": {
89466
+ config2.log.debug(
89467
+ { machineId, taskId: route.taskId },
89468
+ "Task messages data channel received"
89469
+ );
89470
+ config2.onTaskMessageChannel?.(machineId, event.channel, route.taskId);
89471
+ return;
89472
+ }
89473
+ case "daemon_control": {
89474
+ config2.log.info({ machineId }, "Daemon control data channel received");
89475
+ config2.onControlChannel?.(machineId, event.channel);
89476
+ return;
89477
+ }
89478
+ case "loro_sync": {
89479
+ config2.log.info({ machineId }, "Loro sync data channel received");
89480
+ const rawChannel = event.channel;
89481
+ guardLoroChannelSend(rawChannel, config2.log);
89482
+ config2.webrtcAdapter.attachDataChannel(
89483
+ machineIdToPeerId(machineId),
89484
+ event.channel
89485
+ );
89486
+ return;
89487
+ }
89488
+ case "file_io": {
89489
+ config2.log.debug({ machineId, id: route.id }, "File I/O data channel received");
89490
+ config2.onFileIOChannel?.(machineId, event.channel, route.id);
89491
+ return;
89492
+ }
89493
+ case "lsp": {
89494
+ config2.log.debug({ machineId, id: route.id }, "LSP data channel received");
89495
+ config2.onLSPChannel?.(machineId, event.channel, route.id);
89496
+ return;
89497
+ }
89498
+ case "browser_preview": {
89499
+ config2.log.debug(
89500
+ { machineId, port: route.port },
89501
+ "Browser preview data channel received"
89502
+ );
89503
+ config2.onBrowserPreviewChannel?.(machineId, event.channel, route.port);
89504
+ return;
89505
+ }
89506
+ case "browser_preview_url": {
89507
+ config2.log.debug({ machineId }, "Browser preview URL data channel received");
89508
+ config2.onBrowserPreviewUrlChannel?.(machineId, event.channel);
89509
+ return;
89510
+ }
89511
+ case "terminal": {
89512
+ config2.log.debug(
89513
+ { machineId, taskId: route.taskId, terminalId: route.terminalId },
89514
+ "Terminal data channel received"
89515
+ );
89516
+ config2.onTerminalChannel?.(machineId, event.channel, route.taskId, route.terminalId);
89517
+ return;
89518
+ }
89519
+ case "asset_transfer": {
89520
+ config2.log.debug({ machineId }, "Asset transfer data channel received");
89521
+ config2.onAssetTransferChannel?.(machineId, event.channel);
89522
+ return;
89523
+ }
89524
+ case "unknown": {
89525
+ config2.log.warn(
89526
+ { machineId, label: route.label },
89527
+ "Ignoring unrecognized data channel label"
89528
+ );
89529
+ return;
89530
+ }
89531
+ default:
89532
+ assertNever3(route);
89326
89533
  }
89327
- default:
89328
- assertNever3(route);
89534
+ } catch (err) {
89535
+ config2.log.warn(
89536
+ { machineId, err: err instanceof Error ? err.message : String(err) },
89537
+ "Data channel routing failed (peer connection likely closing)"
89538
+ );
89329
89539
  }
89330
89540
  }
89331
89541
  function setupPeerHandlers(machineId, pc) {
@@ -89347,6 +89557,7 @@ function createPeerManager(config2) {
89347
89557
  if (state === "failed" || state === "closed") {
89348
89558
  config2.webrtcAdapter.detachDataChannel(machineIdToPeerId(machineId));
89349
89559
  peers.delete(machineId);
89560
+ pc.onconnectionstatechange = null;
89350
89561
  pc.close();
89351
89562
  }
89352
89563
  };
@@ -89402,7 +89613,20 @@ function createPeerManager(config2) {
89402
89613
  if (!pc.createOffer) {
89403
89614
  throw new Error("PeerConnection does not support createOffer");
89404
89615
  }
89405
- const channel = pc.createDataChannel(LORO_SYNC_LABEL, { ordered: true });
89616
+ let channel;
89617
+ try {
89618
+ channel = pc.createDataChannel(LORO_SYNC_LABEL, { ordered: true });
89619
+ } catch (err) {
89620
+ config2.log.warn(
89621
+ { targetMachineId, err: String(err) },
89622
+ "createDataChannel failed (connection likely closing)"
89623
+ );
89624
+ pendingCreates.delete(targetMachineId);
89625
+ peers.delete(targetMachineId);
89626
+ pc.onconnectionstatechange = null;
89627
+ pc.close();
89628
+ throw err;
89629
+ }
89406
89630
  const rawChannel = channel;
89407
89631
  guardLoroChannelSend(rawChannel, config2.log);
89408
89632
  rawChannel.onopen = () => {
@@ -89461,6 +89685,7 @@ function createPeerManager(config2) {
89461
89685
  const pc = peers.get(targetId);
89462
89686
  if (pc) {
89463
89687
  config2.webrtcAdapter.detachDataChannel(machineIdToPeerId(targetId));
89688
+ pc.onconnectionstatechange = null;
89464
89689
  pc.close();
89465
89690
  peers.delete(targetId);
89466
89691
  }
@@ -89468,6 +89693,7 @@ function createPeerManager(config2) {
89468
89693
  destroy() {
89469
89694
  for (const [machineId, pc] of peers) {
89470
89695
  config2.webrtcAdapter.detachDataChannel(machineIdToPeerId(machineId));
89696
+ pc.onconnectionstatechange = null;
89471
89697
  pc.close();
89472
89698
  }
89473
89699
  peers.clear();
@@ -91478,7 +91704,16 @@ function attachConversationHandler(daemon, dc, channelId, params, log, attempts)
91478
91704
  const handlerOpts = {
91479
91705
  taskId,
91480
91706
  channelId,
91481
- send: (data) => dc.send(data),
91707
+ send: (data) => {
91708
+ try {
91709
+ dc.send(data);
91710
+ } catch (err) {
91711
+ log({
91712
+ event: "message_channel_send_failed",
91713
+ error: err instanceof Error ? err.message : String(err)
91714
+ });
91715
+ }
91716
+ },
91482
91717
  taskManager: daemon.taskManager,
91483
91718
  store: daemon.store,
91484
91719
  log,
@@ -91602,7 +91837,16 @@ function wireThreadErrorFallback(daemon, dc, taskId, threadId, channelId, log) {
91602
91837
  const handler = handleMessageChannel({
91603
91838
  taskId,
91604
91839
  channelId,
91605
- send: (data) => dc.send(data),
91840
+ send: (data) => {
91841
+ try {
91842
+ dc.send(data);
91843
+ } catch (err) {
91844
+ log({
91845
+ event: "message_channel_send_failed",
91846
+ error: err instanceof Error ? err.message : String(err)
91847
+ });
91848
+ }
91849
+ },
91606
91850
  taskManager: daemon.taskManager,
91607
91851
  store: daemon.store,
91608
91852
  log,
@@ -91647,15 +91891,19 @@ function routeSignalingMessage(peerManager, signalingHandle, log) {
91647
91891
  switch (msg.type) {
91648
91892
  case "webrtc-offer":
91649
91893
  if (msg.fromMachineId)
91650
- peerManager?.handleOffer(msg.fromMachineId, toSDPDescription(msg.offer));
91894
+ peerManager?.handleOffer(msg.fromMachineId, toSDPDescription(msg.offer)).catch(
91895
+ (err) => log.error({ event: "webrtc_offer_failed", error: String(err) })
91896
+ );
91651
91897
  break;
91652
91898
  case "webrtc-answer":
91653
91899
  if (msg.fromMachineId)
91654
- peerManager?.handleAnswer(msg.fromMachineId, toSDPDescription(msg.answer));
91900
+ peerManager?.handleAnswer(msg.fromMachineId, toSDPDescription(msg.answer)).catch(
91901
+ (err) => log.error({ event: "webrtc_answer_failed", error: String(err) })
91902
+ );
91655
91903
  break;
91656
91904
  case "webrtc-ice":
91657
91905
  if (msg.fromMachineId)
91658
- peerManager?.handleIce(msg.fromMachineId, toICECandidate(msg.candidate));
91906
+ peerManager?.handleIce(msg.fromMachineId, toICECandidate(msg.candidate)).catch((err) => log.error({ event: "webrtc_ice_failed", error: String(err) }));
91659
91907
  break;
91660
91908
  case "ice-servers":
91661
91909
  peerManager?.updateIceServers(msg.iceServers);
@@ -91696,4 +91944,4 @@ export {
91696
91944
  classifyLogLevel,
91697
91945
  serve
91698
91946
  };
91699
- //# sourceMappingURL=serve-HDRZ2CU6.js.map
91947
+ //# sourceMappingURL=serve-ROGOK56J.js.map