@schoolai/shipyard 3.13.0 → 3.14.0-rc.20260616.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.
@@ -12,7 +12,7 @@ import {
12
12
  guardedSubscribe,
13
13
  makeInitialAttemptState,
14
14
  shutdownFileWatcherGuard
15
- } from "./chunk-7MVFHLYV.js";
15
+ } from "./chunk-3KE2VDKA.js";
16
16
  import {
17
17
  TRAILER_SCHEMA_VERSION,
18
18
  appendTrailerToMessage,
@@ -45,7 +45,7 @@ import {
45
45
  } from "./chunk-TFWDJCUJ.js";
46
46
  import {
47
47
  getDaemonVersion
48
- } from "./chunk-MR6HRO7R.js";
48
+ } from "./chunk-WGS7ZW6N.js";
49
49
  import {
50
50
  collectPriorCrashDetail,
51
51
  getRecentWasmPanicMessages
@@ -482,7 +482,7 @@ import "./chunk-2H7UOFLK.js";
482
482
  // src/services/serve.ts
483
483
  import { mkdir as mkdir44, realpath as realpath3 } from "fs/promises";
484
484
  import { homedir as homedir15 } from "os";
485
- import { join as join86 } from "path";
485
+ import { join as join87 } from "path";
486
486
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
487
487
 
488
488
  // ../../node_modules/.pnpm/@levischuck+tiny-cbor@0.3.2/node_modules/@levischuck/tiny-cbor/esm/cbor/cbor_internal.js
@@ -5764,7 +5764,7 @@ function nanoid(size = 21) {
5764
5764
  }
5765
5765
 
5766
5766
  // src/services/bootstrap/signaling.ts
5767
- var DAEMON_NPM_VERSION = true ? "3.13.0" : "unknown";
5767
+ var DAEMON_NPM_VERSION = true ? "3.14.0" : "unknown";
5768
5768
  function createDaemonSignaling(config) {
5769
5769
  const agentId = config.agentId ?? nanoid();
5770
5770
  function send(msg) {
@@ -6526,6 +6526,583 @@ async function createCapabilityWatcher(deps) {
6526
6526
  };
6527
6527
  }
6528
6528
 
6529
+ // src/services/collab/crdt-permissions.ts
6530
+ function isPersonalPeer(entry) {
6531
+ return entry?.source === "personal";
6532
+ }
6533
+ function findEntry(registry, peer) {
6534
+ const direct = registry.getEntry(peer.peerId);
6535
+ if (direct) return direct;
6536
+ if (peer.peerType === "user") {
6537
+ return registry.findBySource("personal");
6538
+ }
6539
+ return void 0;
6540
+ }
6541
+ var COLLAB_VISIBLE_PREFIXES = {
6542
+ owner: /* @__PURE__ */ new Set(["plan", "canvas"]),
6543
+ "collaborator-full": /* @__PURE__ */ new Set(["plan", "canvas"]),
6544
+ "collaborator-review": /* @__PURE__ */ new Set(["plan", "canvas"]),
6545
+ viewer: /* @__PURE__ */ new Set([])
6546
+ };
6547
+ var COLLAB_MUTABLE_PREFIXES = {
6548
+ owner: /* @__PURE__ */ new Set(["plan", "canvas"]),
6549
+ "collaborator-full": /* @__PURE__ */ new Set(["plan", "canvas"]),
6550
+ "collaborator-review": /* @__PURE__ */ new Set(["plan"]),
6551
+ viewer: /* @__PURE__ */ new Set([])
6552
+ };
6553
+ function buildCollabPermissions(registry) {
6554
+ function isAllowed(_doc, peer) {
6555
+ if (peer.channelKind === "storage") return true;
6556
+ if (peer.peerType === "service") return true;
6557
+ const entry = findEntry(registry, peer);
6558
+ if (!entry) return false;
6559
+ return isPersonalPeer(entry);
6560
+ }
6561
+ return {
6562
+ visibility: isAllowed,
6563
+ mutability: isAllowed,
6564
+ creation(docId, peer) {
6565
+ const colonIdx = docId.indexOf(":");
6566
+ if (colonIdx > 0 && OBSOLETE_PREFIXES.has(docId.slice(0, colonIdx))) return false;
6567
+ if (peer.channelKind === "storage") return true;
6568
+ if (peer.peerType === "service") return true;
6569
+ const entry = findEntry(registry, peer);
6570
+ if (!entry) return false;
6571
+ return isPersonalPeer(entry);
6572
+ },
6573
+ deletion(_doc, _peer) {
6574
+ return false;
6575
+ }
6576
+ };
6577
+ }
6578
+ function isCollabPrefixVisible(role, prefix) {
6579
+ return COLLAB_VISIBLE_PREFIXES[role].has(prefix);
6580
+ }
6581
+ function isCollabPrefixMutable(role, prefix) {
6582
+ return COLLAB_MUTABLE_PREFIXES[role].has(prefix);
6583
+ }
6584
+
6585
+ // src/services/collab/collab-repo-manager.ts
6586
+ var BRIDGE_EPHEMERAL_HOPS = 1;
6587
+ var DAEMON_IDENTITY = { name: "shipyard-daemon", type: "service" };
6588
+ function restoreEphemeralHops(message) {
6589
+ switch (message.type) {
6590
+ case "channel/ephemeral":
6591
+ if (message.hopsRemaining >= BRIDGE_EPHEMERAL_HOPS) return message;
6592
+ return { ...message, hopsRemaining: BRIDGE_EPHEMERAL_HOPS };
6593
+ case "channel/batch": {
6594
+ let changed = false;
6595
+ const nextMessages = message.messages.map((inner) => {
6596
+ if (inner.type !== "channel/ephemeral") return inner;
6597
+ if (inner.hopsRemaining >= BRIDGE_EPHEMERAL_HOPS) return inner;
6598
+ changed = true;
6599
+ return { ...inner, hopsRemaining: BRIDGE_EPHEMERAL_HOPS };
6600
+ });
6601
+ if (!changed) return message;
6602
+ return { ...message, messages: nextMessages };
6603
+ }
6604
+ case "channel/establish-request":
6605
+ case "channel/establish-response":
6606
+ case "channel/sync-request":
6607
+ case "channel/sync-response":
6608
+ case "channel/update":
6609
+ case "channel/directory-request":
6610
+ case "channel/directory-response":
6611
+ case "channel/new-doc":
6612
+ case "channel/delete-request":
6613
+ case "channel/delete-response":
6614
+ return message;
6615
+ default: {
6616
+ const _exhaustive = message;
6617
+ void _exhaustive;
6618
+ return message;
6619
+ }
6620
+ }
6621
+ }
6622
+ var HopRestoringBridgeAdapter = class extends BridgeAdapter {
6623
+ generate(context) {
6624
+ const channel = super.generate(context);
6625
+ const originalSend = channel.send;
6626
+ return {
6627
+ ...channel,
6628
+ send: (msg) => originalSend(restoreEphemeralHops(msg))
6629
+ };
6630
+ }
6631
+ };
6632
+ function planCollabRoomResources(taskId, roomId, epochs) {
6633
+ return {
6634
+ docIds: [buildPlanDocId(taskId, epochs.plan), buildCanvasDocId(taskId, epochs.canvas)],
6635
+ personalBridgeType: `bridge-personal-${roomId}`,
6636
+ collabBridgeType: `bridge-collab-${roomId}`
6637
+ };
6638
+ }
6639
+ function buildCollabRepoPermissions(taskId, peerRoleRegistry) {
6640
+ return {
6641
+ visibility(doc, peer) {
6642
+ if (peer.channelKind === "storage") return true;
6643
+ if (peer.peerType === "service") return true;
6644
+ const parsed = parseDocumentId(doc.id);
6645
+ if (!parsed) return false;
6646
+ if (parsed.key !== taskId) return false;
6647
+ const entry = peerRoleRegistry.getEntry(peer.peerId);
6648
+ if (!entry) return false;
6649
+ return isCollabPrefixVisible(entry.role, parsed.prefix);
6650
+ },
6651
+ mutability(doc, peer) {
6652
+ if (peer.channelKind === "storage") return true;
6653
+ if (peer.peerType === "service") return true;
6654
+ const parsed = parseDocumentId(doc.id);
6655
+ if (!parsed) return false;
6656
+ if (parsed.key !== taskId) return false;
6657
+ const entry = peerRoleRegistry.getEntry(peer.peerId);
6658
+ if (!entry) return false;
6659
+ return isCollabPrefixMutable(entry.role, parsed.prefix);
6660
+ },
6661
+ creation(_docId, _peer) {
6662
+ return false;
6663
+ },
6664
+ deletion(_doc, _peer) {
6665
+ return false;
6666
+ }
6667
+ };
6668
+ }
6669
+ function createCollabRepo(personalRepo, taskId, roomId, epochs, peerRoleRegistry) {
6670
+ const resources = planCollabRoomResources(taskId, roomId, epochs);
6671
+ const bridge = new Bridge();
6672
+ const personalBridgeAdapter = new HopRestoringBridgeAdapter({
6673
+ adapterType: resources.personalBridgeType,
6674
+ bridge
6675
+ });
6676
+ const collabBridgeAdapter = new HopRestoringBridgeAdapter({
6677
+ adapterType: resources.collabBridgeType,
6678
+ bridge
6679
+ });
6680
+ const collabWebrtcAdapter = new WebRtcDataChannelAdapter();
6681
+ const collabRepo = new Repo({
6682
+ identity: { ...DAEMON_IDENTITY, name: `collab-room-${roomId}` },
6683
+ adapters: [collabBridgeAdapter, collabWebrtcAdapter],
6684
+ permissions: buildCollabRepoPermissions(taskId, peerRoleRegistry)
6685
+ });
6686
+ personalRepo.addAdapter(personalBridgeAdapter);
6687
+ return {
6688
+ repo: collabRepo,
6689
+ webrtcAdapter: collabWebrtcAdapter,
6690
+ async destroy() {
6691
+ personalRepo.removeAdapter(personalBridgeAdapter.adapterId);
6692
+ await collabRepo.shutdown();
6693
+ }
6694
+ };
6695
+ }
6696
+
6697
+ // src/services/collab/collab-room-manager.ts
6698
+ function assertNever2(x2) {
6699
+ throw new Error(`Unhandled message type: ${JSON.stringify(x2)}`);
6700
+ }
6701
+ function namespacePeerId(roomId, remoteUserId) {
6702
+ return `collab:${roomId}:${remoteUserId}`;
6703
+ }
6704
+ function stripCollabRoomPrefix(roomId, namespacedId) {
6705
+ const roomPrefix = `collab:${roomId}:`;
6706
+ if (!namespacedId.startsWith(roomPrefix)) {
6707
+ throw new Error(
6708
+ `Expected collab-namespaced peer ID with prefix "${roomPrefix}", got "${namespacedId}"`
6709
+ );
6710
+ }
6711
+ const browserMarker = ":browser:";
6712
+ const browserIdx = namespacedId.lastIndexOf(browserMarker);
6713
+ if (browserIdx === -1) {
6714
+ throw new Error(`Expected collab peer ID to contain "${browserMarker}", got "${namespacedId}"`);
6715
+ }
6716
+ return namespacedId.slice(browserIdx + browserMarker.length);
6717
+ }
6718
+ function bumpAndNotify(room, deps) {
6719
+ room.generation += 1;
6720
+ deps.onRoomStateChanged(room.taskId);
6721
+ }
6722
+ function handleParticipantsList(room, participants, deps) {
6723
+ if (!room.myUserId) return;
6724
+ room.participants = participants.map((p2) => ({
6725
+ userId: p2.userId,
6726
+ username: p2.username,
6727
+ avatarUrl: p2.avatarUrl,
6728
+ role: p2.role,
6729
+ connectionId: p2.connectionId,
6730
+ isHub: p2.isHub
6731
+ }));
6732
+ room.connectionIdByUser = new Map(participants.map((p2) => [p2.userId, p2.connectionId]));
6733
+ bumpAndNotify(room, deps);
6734
+ for (const participant of participants) {
6735
+ if (participant.userId === room.myUserId) continue;
6736
+ if (room.knownPeers.has(participant.userId)) continue;
6737
+ registerCollabParticipant(
6738
+ room,
6739
+ participant.userId,
6740
+ participant.role,
6741
+ participant.username,
6742
+ deps
6743
+ );
6744
+ offerOrEnqueueParticipant(room, participant.userId, deps);
6745
+ }
6746
+ }
6747
+ function handleParticipantJoined(room, participant, deps) {
6748
+ if (!room.myUserId) return;
6749
+ if (participant.userId === room.myUserId) return;
6750
+ room.connectionIdByUser.set(participant.userId, participant.connectionId);
6751
+ if (room.knownPeers.has(participant.userId)) return;
6752
+ room.participants = [
6753
+ ...room.participants,
6754
+ {
6755
+ userId: participant.userId,
6756
+ username: participant.username,
6757
+ avatarUrl: participant.avatarUrl,
6758
+ role: participant.role,
6759
+ connectionId: participant.connectionId,
6760
+ isHub: participant.isHub
6761
+ }
6762
+ ];
6763
+ bumpAndNotify(room, deps);
6764
+ registerCollabParticipant(room, participant.userId, participant.role, participant.username, deps);
6765
+ offerOrEnqueueParticipant(room, participant.userId, deps);
6766
+ }
6767
+ function handleParticipantLeft(room, msg, deps) {
6768
+ const { userId, connectionId } = msg;
6769
+ const tracked = room.connectionIdByUser.get(userId);
6770
+ if (tracked !== void 0 && tracked !== connectionId) {
6771
+ deps.log({
6772
+ event: "participant_left_stale_dropped",
6773
+ roomId: room.roomId,
6774
+ userId,
6775
+ incomingConnectionId: connectionId,
6776
+ trackedConnectionId: tracked
6777
+ });
6778
+ return;
6779
+ }
6780
+ room.knownPeers.delete(userId);
6781
+ room.pendingOfferUserIds.delete(userId);
6782
+ room.participants = room.participants.filter((p2) => p2.userId !== userId);
6783
+ room.connectionIdByUser.delete(userId);
6784
+ bumpAndNotify(room, deps);
6785
+ const peerId = namespacePeerId(room.roomId, userId);
6786
+ deps.peerRoleRegistry.unregisterPeer(peerId);
6787
+ room.peerManager.closePeer(peerId);
6788
+ deps.log({ event: "collab_participant_left", roomId: room.roomId, userId });
6789
+ }
6790
+ function handleWebrtcSignaling(room, type, targetUserId, generationId, payload, deps) {
6791
+ const peerId = namespacePeerId(room.roomId, targetUserId);
6792
+ const actions = {
6793
+ // eslint-disable-next-line no-restricted-syntax -- SDP is opaque z.unknown() from signaling
6794
+ offer: () => room.peerManager.handleOffer(peerId, payload, generationId),
6795
+ // eslint-disable-next-line no-restricted-syntax -- SDP is opaque z.unknown() from signaling
6796
+ answer: () => room.peerManager.handleAnswer(peerId, payload, generationId),
6797
+ // eslint-disable-next-line no-restricted-syntax -- ICE candidate is opaque z.unknown() from signaling
6798
+ ice: () => room.peerManager.handleIce(peerId, payload, generationId)
6799
+ };
6800
+ actions[type]().catch((err3) => {
6801
+ deps.log({
6802
+ event: `collab_${type}_failed`,
6803
+ roomId: room.roomId,
6804
+ generationId,
6805
+ error: String(err3)
6806
+ });
6807
+ });
6808
+ }
6809
+ function handleCollabRoomMessage(room, msg, deps) {
6810
+ switch (msg.type) {
6811
+ case "authenticated":
6812
+ room.myUserId = msg.userId;
6813
+ deps.log({ event: "collab_authenticated", roomId: room.roomId, userId: msg.userId });
6814
+ break;
6815
+ case "participants-list":
6816
+ handleParticipantsList(room, msg.participants, deps);
6817
+ break;
6818
+ case "participant-joined":
6819
+ handleParticipantJoined(room, msg.participant, deps);
6820
+ break;
6821
+ case "participant-left":
6822
+ handleParticipantLeft(room, { userId: msg.userId, connectionId: msg.connectionId }, deps);
6823
+ break;
6824
+ case "webrtc-offer":
6825
+ handleWebrtcSignaling(room, "offer", msg.targetUserId, msg.generationId, msg.offer, deps);
6826
+ break;
6827
+ case "webrtc-answer":
6828
+ handleWebrtcSignaling(room, "answer", msg.targetUserId, msg.generationId, msg.answer, deps);
6829
+ break;
6830
+ case "webrtc-ice":
6831
+ handleWebrtcSignaling(room, "ice", msg.targetUserId, msg.generationId, msg.candidate, deps);
6832
+ break;
6833
+ case "ice-servers":
6834
+ applyIceServers(room, msg.iceServers, deps);
6835
+ break;
6836
+ case "error":
6837
+ deps.log({ event: "collab_room_error", roomId: room.roomId, message: msg.message });
6838
+ break;
6839
+ default:
6840
+ assertNever2(msg);
6841
+ }
6842
+ }
6843
+ function registerCollabParticipant(room, userId, role, displayName, deps) {
6844
+ const peerId = namespacePeerId(room.roomId, userId);
6845
+ deps.peerRoleRegistry.registerCollabPeer(peerId, role, userId, displayName, room.taskId);
6846
+ }
6847
+ function initiateOfferToParticipant(room, remoteUserId, deps) {
6848
+ if (!room.myUserId) return;
6849
+ room.knownPeers.add(remoteUserId);
6850
+ deps.log({ event: "collab_initiating_offer", roomId: room.roomId, target: remoteUserId });
6851
+ const peerId = namespacePeerId(room.roomId, remoteUserId);
6852
+ room.peerManager.initiateOffer(peerId).catch((err3) => {
6853
+ deps.log({ event: "collab_offer_initiate_failed", roomId: room.roomId, error: String(err3) });
6854
+ });
6855
+ }
6856
+ function offerOrEnqueueParticipant(room, remoteUserId, deps) {
6857
+ if (room.iceServersReady) {
6858
+ initiateOfferToParticipant(room, remoteUserId, deps);
6859
+ return;
6860
+ }
6861
+ room.pendingOfferUserIds.add(remoteUserId);
6862
+ deps.log({ event: "collab_offer_deferred_no_ice", roomId: room.roomId, target: remoteUserId });
6863
+ }
6864
+ function drainPendingOffers(room, deps) {
6865
+ if (room.pendingOfferUserIds.size === 0) return;
6866
+ const pending = [...room.pendingOfferUserIds];
6867
+ room.pendingOfferUserIds.clear();
6868
+ for (const userId of pending) {
6869
+ if (userId === room.myUserId) continue;
6870
+ if (room.knownPeers.has(userId)) continue;
6871
+ initiateOfferToParticipant(room, userId, deps);
6872
+ }
6873
+ }
6874
+ function applyIceServers(room, iceServers, deps) {
6875
+ room.iceServers = iceServers;
6876
+ room.iceServersReady = true;
6877
+ room.peerManager.updateIceServers(iceServers);
6878
+ deps.log({ event: "collab_ice_servers", roomId: room.roomId, count: iceServers.length });
6879
+ drainPendingOffers(room, deps);
6880
+ }
6881
+ function resetCollabRoomForReconnect(room, deps) {
6882
+ room.peerManager.destroy();
6883
+ const adapter = room.collabRepoHandle?.webrtcAdapter;
6884
+ if (adapter) {
6885
+ room.peerManager = deps.createPeerManagerForRoom(
6886
+ room.roomId,
6887
+ room.connection,
6888
+ room.taskId,
6889
+ adapter
6890
+ );
6891
+ if (room.iceServers) {
6892
+ room.peerManager.updateIceServers(room.iceServers);
6893
+ }
6894
+ } else {
6895
+ deps.log({ event: "collab_reconnect_no_adapter", roomId: room.roomId });
6896
+ }
6897
+ room.myUserId = null;
6898
+ room.knownPeers.clear();
6899
+ room.pendingOfferUserIds.clear();
6900
+ room.iceServersReady = adapter !== void 0 && room.iceServers !== null;
6901
+ }
6902
+ function shouldBroadcastRoomState(state) {
6903
+ if (state === null) return true;
6904
+ return state.ownerId !== "";
6905
+ }
6906
+ function createCollabRoomManager(deps) {
6907
+ const rooms = /* @__PURE__ */ new Map();
6908
+ async function leave(roomId, opts) {
6909
+ const room = rooms.get(roomId);
6910
+ if (!room) return;
6911
+ deps.log({ event: "collab_leaving", roomId });
6912
+ clearTimeout(room.expiryTimer);
6913
+ room.unsubMessage();
6914
+ room.unsubState();
6915
+ for (const userId of room.knownPeers) {
6916
+ deps.peerRoleRegistry.unregisterPeer(namespacePeerId(roomId, userId));
6917
+ }
6918
+ const destroyPromise = room.collabRepoHandle?.destroy();
6919
+ room.peerManager.destroy();
6920
+ room.connection.disconnect();
6921
+ const leftTaskId = room.taskId;
6922
+ rooms.delete(roomId);
6923
+ if (opts?.removePersisted !== false) {
6924
+ deps.collabHostingStore.remove(roomId).catch((err3) => {
6925
+ deps.log({
6926
+ event: "collab_hosting_remove_failed",
6927
+ roomId,
6928
+ error: err3 instanceof Error ? err3.message : String(err3)
6929
+ });
6930
+ });
6931
+ }
6932
+ deps.onRoomStateChanged(leftTaskId);
6933
+ if (destroyPromise) {
6934
+ await destroyPromise.catch((err3) => {
6935
+ deps.log({
6936
+ event: "collab_repo_destroy_failed",
6937
+ roomId,
6938
+ error: err3 instanceof Error ? err3.message : String(err3)
6939
+ });
6940
+ });
6941
+ }
6942
+ }
6943
+ return {
6944
+ joinRoom(config) {
6945
+ const { roomId, taskId, token, expiresAt, signalingBaseUrl, userToken, machineId } = config;
6946
+ const existing = rooms.get(roomId);
6947
+ if (existing) {
6948
+ const existingState = existing.connection.getState();
6949
+ if (existing.taskId === taskId && (existingState === "connected" || existingState === "connecting")) {
6950
+ deps.log({ event: "collab_join_dedup", roomId, state: existingState });
6951
+ return {
6952
+ roomId,
6953
+ taskId,
6954
+ async destroy() {
6955
+ await leave(roomId);
6956
+ }
6957
+ };
6958
+ }
6959
+ deps.log({ event: "collab_replacing", roomId });
6960
+ leave(roomId, { removePersisted: false }).catch((err3) => {
6961
+ deps.log({
6962
+ event: "collab_replace_leave_failed",
6963
+ roomId,
6964
+ error: err3 instanceof Error ? err3.message : String(err3)
6965
+ });
6966
+ });
6967
+ }
6968
+ const wsUrl = new URL(signalingBaseUrl);
6969
+ wsUrl.pathname = ROUTES.WS_COLLAB.replace(":roomId", roomId);
6970
+ wsUrl.searchParams.set("token", token);
6971
+ wsUrl.searchParams.set("userToken", userToken);
6972
+ wsUrl.searchParams.set("clientType", "agent");
6973
+ wsUrl.searchParams.set("machineId", machineId);
6974
+ const connection = new CollabRoomConnection({
6975
+ url: wsUrl.toString(),
6976
+ maxRetries: -1,
6977
+ initialDelayMs: 1e3,
6978
+ maxDelayMs: 3e4,
6979
+ backoffMultiplier: 2
6980
+ });
6981
+ const collabRepoHandle = createCollabRepo(
6982
+ deps.personalRepo,
6983
+ taskId,
6984
+ roomId,
6985
+ { plan: deps.planEpoch, canvas: deps.canvasEpoch },
6986
+ deps.peerRoleRegistry
6987
+ );
6988
+ const peerManager = deps.createPeerManagerForRoom(
6989
+ roomId,
6990
+ connection,
6991
+ taskId,
6992
+ collabRepoHandle.webrtcAdapter
6993
+ );
6994
+ const handle = {
6995
+ roomId,
6996
+ taskId,
6997
+ async destroy() {
6998
+ await leave(roomId);
6999
+ }
7000
+ };
7001
+ const delayMs = expiresAt - Date.now();
7002
+ if (delayMs <= 0 || !Number.isFinite(delayMs)) {
7003
+ deps.log({ event: "collab_expired", roomId, expiresAt });
7004
+ peerManager.destroy();
7005
+ collabRepoHandle.destroy().catch((err3) => {
7006
+ deps.log({
7007
+ event: "collab_repo_destroy_failed",
7008
+ roomId,
7009
+ error: err3 instanceof Error ? err3.message : String(err3)
7010
+ });
7011
+ });
7012
+ return handle;
7013
+ }
7014
+ const safeDelayMs = Math.min(delayMs, 2147483647);
7015
+ const room = {
7016
+ roomId,
7017
+ taskId,
7018
+ connection,
7019
+ peerManager,
7020
+ collabRepoHandle,
7021
+ myUserId: null,
7022
+ knownPeers: /* @__PURE__ */ new Set(),
7023
+ participants: [],
7024
+ connectionIdByUser: /* @__PURE__ */ new Map(),
7025
+ iceServers: null,
7026
+ iceServersReady: false,
7027
+ pendingOfferUserIds: /* @__PURE__ */ new Set(),
7028
+ expiresAt,
7029
+ generation: 0,
7030
+ expiryTimer: setTimeout(() => {
7031
+ deps.log({ event: "collab_token_expired", roomId });
7032
+ leave(roomId).catch((err3) => {
7033
+ deps.log({
7034
+ event: "collab_expiry_leave_failed",
7035
+ roomId,
7036
+ error: err3 instanceof Error ? err3.message : String(err3)
7037
+ });
7038
+ });
7039
+ }, safeDelayMs),
7040
+ unsubMessage: () => {
7041
+ },
7042
+ unsubState: () => {
7043
+ }
7044
+ };
7045
+ rooms.set(roomId, room);
7046
+ deps.collabHostingStore.persist({ roomId, taskId, token, expiresAt }).catch((err3) => {
7047
+ deps.log({
7048
+ event: "collab_hosting_persist_failed",
7049
+ roomId,
7050
+ error: err3 instanceof Error ? err3.message : String(err3)
7051
+ });
7052
+ });
7053
+ room.unsubMessage = connection.onMessage((msg) => {
7054
+ handleCollabRoomMessage(room, msg, deps);
7055
+ });
7056
+ room.unsubState = connection.onStateChange((state) => {
7057
+ deps.log({ event: "collab_state_change", roomId, state });
7058
+ if (state === "disconnected" || state === "error") {
7059
+ resetCollabRoomForReconnect(room, deps);
7060
+ }
7061
+ });
7062
+ connection.connect();
7063
+ deps.log({ event: "collab_joining", roomId, taskId });
7064
+ bumpAndNotify(room, deps);
7065
+ return handle;
7066
+ },
7067
+ async leaveRoom(roomId) {
7068
+ await leave(roomId);
7069
+ },
7070
+ getParticipantsForTask(taskId) {
7071
+ for (const room of rooms.values()) {
7072
+ if (room.taskId === taskId) {
7073
+ return room.participants.filter((p2) => p2.userId !== room.myUserId).map((p2) => ({ name: p2.username, role: p2.role }));
7074
+ }
7075
+ }
7076
+ return [];
7077
+ },
7078
+ listActiveTaskIds() {
7079
+ const ids = [];
7080
+ for (const room of rooms.values()) ids.push(room.taskId);
7081
+ return ids;
7082
+ },
7083
+ getStateForTask(taskId) {
7084
+ for (const room of rooms.values()) {
7085
+ if (room.taskId !== taskId) continue;
7086
+ const owner = room.participants.find((p2) => p2.role === "owner");
7087
+ return {
7088
+ roomId: room.roomId,
7089
+ taskId: room.taskId,
7090
+ ownerId: owner?.userId ?? "",
7091
+ participants: room.participants,
7092
+ expiresAt: room.expiresAt,
7093
+ generation: room.generation
7094
+ };
7095
+ }
7096
+ return null;
7097
+ },
7098
+ async destroy() {
7099
+ await Promise.all(
7100
+ [...rooms.keys()].map((roomId) => leave(roomId, { removePersisted: false }))
7101
+ );
7102
+ }
7103
+ };
7104
+ }
7105
+
6529
7106
  // src/services/mcp/state-snapshot.ts
6530
7107
  function computeTransportEnabled(server, live) {
6531
7108
  if (live) return live.status !== "disabled";
@@ -6855,6 +7432,7 @@ function sendInitialCollabSnapshots(handler, deps) {
6855
7432
  const mgr = deps.collabRoomManager;
6856
7433
  for (const taskId of mgr.listActiveTaskIds()) {
6857
7434
  const state = mgr.getStateForTask(taskId);
7435
+ if (!shouldBroadcastRoomState(state)) continue;
6858
7436
  handler.sendControl({
6859
7437
  type: "collab_room_state",
6860
7438
  taskId,
@@ -12938,10 +13516,94 @@ function buildBrowserMetricsTelemetry(msg) {
12938
13516
  eventLoopDriftP99Ms: m2.eventLoopDriftMs?.p99 ?? null,
12939
13517
  longTaskTotalMs: m2.longTasks?.totalMs ?? null,
12940
13518
  longTaskMaxMs: m2.longTasks?.maxMs ?? null,
13519
+ rtcSelectedCandidatePair: toTelemetryCandidatePair(m2.rtcSelectedCandidatePair ?? null),
13520
+ networkEffectiveType: toTelemetryNetworkEffectiveType(m2.network?.effectiveType ?? null),
12941
13521
  controlChannelTotalBytes: m2.controlChannel?.totalBytes ?? null,
12942
13522
  runtimeErrorCount: m2.runtimeErrors?.errorCount ?? null
12943
13523
  };
12944
13524
  }
13525
+ function toTelemetryCandidatePair(pair) {
13526
+ if (!pair) return null;
13527
+ return {
13528
+ localCandidateType: toTelemetryCandidateType(pair.localCandidateType),
13529
+ remoteCandidateType: toTelemetryCandidateType(pair.remoteCandidateType),
13530
+ localProtocol: toTelemetryProtocol(pair.localProtocol),
13531
+ remoteProtocol: toTelemetryProtocol(pair.remoteProtocol),
13532
+ protocol: formatCandidatePairProtocol(
13533
+ toTelemetryProtocol(pair.localProtocol),
13534
+ toTelemetryProtocol(pair.remoteProtocol)
13535
+ ),
13536
+ relayProtocol: toTelemetryRelayProtocol(pair.relayProtocol),
13537
+ networkType: toTelemetryCandidateNetworkType(pair.networkType)
13538
+ };
13539
+ }
13540
+ function formatCandidatePairProtocol(localProtocol, remoteProtocol) {
13541
+ if (localProtocol && remoteProtocol) {
13542
+ return localProtocol === remoteProtocol ? localProtocol : `${localProtocol}/${remoteProtocol}`;
13543
+ }
13544
+ return localProtocol ?? remoteProtocol;
13545
+ }
13546
+ function toTelemetryCandidateType(value) {
13547
+ if (!value) return null;
13548
+ switch (value) {
13549
+ case "host":
13550
+ case "prflx":
13551
+ case "relay":
13552
+ case "srflx":
13553
+ return value;
13554
+ default:
13555
+ return "unknown";
13556
+ }
13557
+ }
13558
+ function toTelemetryProtocol(value) {
13559
+ if (!value) return null;
13560
+ switch (value) {
13561
+ case "tcp":
13562
+ case "udp":
13563
+ return value;
13564
+ default:
13565
+ return "unknown";
13566
+ }
13567
+ }
13568
+ function toTelemetryRelayProtocol(value) {
13569
+ if (!value) return null;
13570
+ switch (value) {
13571
+ case "tcp":
13572
+ case "tls":
13573
+ case "udp":
13574
+ return value;
13575
+ default:
13576
+ return "unknown";
13577
+ }
13578
+ }
13579
+ function toTelemetryNetworkEffectiveType(value) {
13580
+ if (!value) return null;
13581
+ switch (value) {
13582
+ case "2g":
13583
+ case "3g":
13584
+ case "4g":
13585
+ case "slow-2g":
13586
+ return value;
13587
+ default:
13588
+ return "unknown";
13589
+ }
13590
+ }
13591
+ function toTelemetryCandidateNetworkType(value) {
13592
+ if (!value) return null;
13593
+ switch (value) {
13594
+ case "bluetooth":
13595
+ case "cellular":
13596
+ case "ethernet":
13597
+ case "vpn":
13598
+ case "wifi":
13599
+ case "wimax":
13600
+ case "wlan":
13601
+ case "unknown":
13602
+ return value;
13603
+ default:
13604
+ return "unknown";
13605
+ }
13606
+ }
12945
13607
  function buildBrowserMetricsBaseLogEntry(msg, controlPeerId, daemonTs) {
12946
13608
  const m2 = msg.metrics;
12947
13609
  return {
@@ -18505,6 +19167,15 @@ function sendAttachSnapshot(handler, daemon, deps, logAdapter) {
18505
19167
 
18506
19168
  // src/shared/capabilities/anthropic-login.ts
18507
19169
  import { spawn as spawn7 } from "child_process";
19170
+ var VERSION_INCOMPAT_MESSAGE = "Your installed Claude CLI is too old for this sign-in method. Update it (run `claude update`, or reinstall the Claude CLI) and try again.";
19171
+ var VERSION_INCOMPAT_PATTERN = /error: unknown (option|command)/i;
19172
+ function classifyLoginExit(exitCode, output) {
19173
+ if (exitCode === 0) return { kind: "success" };
19174
+ if (VERSION_INCOMPAT_PATTERN.test(output)) {
19175
+ return { kind: "version-incompatible", error: VERSION_INCOMPAT_MESSAGE };
19176
+ }
19177
+ return { kind: "failed", error: `Login failed (exit code ${exitCode})` };
19178
+ }
18508
19179
  function buildLoginArgs(method) {
18509
19180
  switch (method) {
18510
19181
  case "claude-ai":
@@ -18517,6 +19188,63 @@ function buildLoginArgs(method) {
18517
19188
  return ["auth", "login"];
18518
19189
  }
18519
19190
  }
19191
+ function resolveLoginBinary(log) {
19192
+ const resolved = resolveClaudeBinaryPath(log);
19193
+ return resolved ? { binary: resolved, usedPathFallback: false } : { binary: "claude", usedPathFallback: true };
19194
+ }
19195
+ function handleLoginSuccess(ctx) {
19196
+ const { requestId, method, sendStatus, onAuthRefreshed, log } = ctx;
19197
+ log({ event: "anthropic_login_completed", requestId });
19198
+ detectAnthropicAuth(method).then((result) => {
19199
+ if (result.kind === "preserved") {
19200
+ log({ event: "anthropic_auth_refresh_preserved", requestId, reason: result.reason });
19201
+ return;
19202
+ }
19203
+ onAuthRefreshed(result.auth);
19204
+ log({ event: "anthropic_auth_refreshed", requestId, authStatus: result.auth });
19205
+ }).catch((err3) => {
19206
+ log({
19207
+ event: "anthropic_auth_refresh_failed",
19208
+ requestId,
19209
+ error: err3 instanceof Error ? err3.message : String(err3)
19210
+ });
19211
+ }).finally(() => {
19212
+ sendStatus({
19213
+ type: "anthropic_login_status",
19214
+ requestId,
19215
+ status: "done",
19216
+ loginUrl: null,
19217
+ error: null
19218
+ });
19219
+ });
19220
+ }
19221
+ function handleLoginExit(exitCode, output, ctx) {
19222
+ const classification = classifyLoginExit(exitCode, output);
19223
+ switch (classification.kind) {
19224
+ case "success":
19225
+ handleLoginSuccess(ctx);
19226
+ return;
19227
+ case "version-incompatible":
19228
+ case "failed":
19229
+ ctx.log({
19230
+ event: "anthropic_login_failed",
19231
+ requestId: ctx.requestId,
19232
+ exitCode,
19233
+ output,
19234
+ reason: classification.kind
19235
+ });
19236
+ ctx.sendStatus({
19237
+ type: "anthropic_login_status",
19238
+ requestId: ctx.requestId,
19239
+ status: "error",
19240
+ loginUrl: null,
19241
+ error: classification.error
19242
+ });
19243
+ return;
19244
+ default:
19245
+ assertNever(classification);
19246
+ }
19247
+ }
18520
19248
  function spawnAnthropicLogin(callbacks) {
18521
19249
  const requestId = crypto.randomUUID();
18522
19250
  const { sendStatus, onAuthRefreshed, log, method } = callbacks;
@@ -18529,7 +19257,9 @@ function spawnAnthropicLogin(callbacks) {
18529
19257
  error: null
18530
19258
  });
18531
19259
  const args = buildLoginArgs(method);
18532
- const child = spawn7("claude", args, {
19260
+ const { binary, usedPathFallback } = resolveLoginBinary(log);
19261
+ log({ event: "anthropic_login_spawn", requestId, binaryPath: binary, usedPathFallback });
19262
+ const child = spawn7(binary, args, {
18533
19263
  stdio: ["ignore", "pipe", "pipe"]
18534
19264
  });
18535
19265
  let output = "";
@@ -18550,44 +19280,7 @@ function spawnAnthropicLogin(callbacks) {
18550
19280
  child.stdout?.on("data", handleOutput);
18551
19281
  child.stderr?.on("data", handleOutput);
18552
19282
  child.on("exit", (exitCode) => {
18553
- if (exitCode === 0) {
18554
- log({ event: "anthropic_login_completed", requestId });
18555
- detectAnthropicAuth(method).then((result) => {
18556
- if (result.kind === "preserved") {
18557
- log({
18558
- event: "anthropic_auth_refresh_preserved",
18559
- requestId,
18560
- reason: result.reason
18561
- });
18562
- return;
18563
- }
18564
- onAuthRefreshed(result.auth);
18565
- log({ event: "anthropic_auth_refreshed", requestId, authStatus: result.auth });
18566
- }).catch((err3) => {
18567
- log({
18568
- event: "anthropic_auth_refresh_failed",
18569
- requestId,
18570
- error: err3 instanceof Error ? err3.message : String(err3)
18571
- });
18572
- }).finally(() => {
18573
- sendStatus({
18574
- type: "anthropic_login_status",
18575
- requestId,
18576
- status: "done",
18577
- loginUrl: null,
18578
- error: null
18579
- });
18580
- });
18581
- } else {
18582
- log({ event: "anthropic_login_failed", requestId, exitCode, output });
18583
- sendStatus({
18584
- type: "anthropic_login_status",
18585
- requestId,
18586
- status: "error",
18587
- loginUrl: null,
18588
- error: `Login failed (exit code ${exitCode})`
18589
- });
18590
- }
19283
+ handleLoginExit(exitCode, output, { requestId, method, sendStatus, onAuthRefreshed, log });
18591
19284
  });
18592
19285
  child.on("error", (err3) => {
18593
19286
  log({ event: "anthropic_login_spawn_failed", requestId, error: err3.message });
@@ -18604,7 +19297,9 @@ function spawnAnthropicLogin(callbacks) {
18604
19297
  function spawnAnthropicLogout(callbacks) {
18605
19298
  const { onComplete, log } = callbacks;
18606
19299
  log({ event: "anthropic_logout_requested" });
18607
- const child = spawn7("claude", ["auth", "logout"], {
19300
+ const { binary, usedPathFallback } = resolveLoginBinary(log);
19301
+ log({ event: "anthropic_logout_spawn", binaryPath: binary, usedPathFallback });
19302
+ const child = spawn7(binary, ["auth", "logout"], {
18608
19303
  stdio: ["ignore", "pipe", "pipe"]
18609
19304
  });
18610
19305
  child.on("exit", (exitCode) => {
@@ -18637,7 +19332,7 @@ function spawnAnthropicLogout(callbacks) {
18637
19332
  }
18638
19333
 
18639
19334
  // src/shared/capabilities/codex-account-event.ts
18640
- function assertNever2(x2) {
19335
+ function assertNever3(x2) {
18641
19336
  throw new Error(`Unhandled Codex AuthMode: ${JSON.stringify(x2)}`);
18642
19337
  }
18643
19338
  function decideCodexAuthFromAccountUpdated(authMode) {
@@ -18650,7 +19345,7 @@ function decideCodexAuthFromAccountUpdated(authMode) {
18650
19345
  case "agentIdentity":
18651
19346
  return { status: "authenticated", method: "chatgpt" };
18652
19347
  default:
18653
- return assertNever2(authMode);
19348
+ return assertNever3(authMode);
18654
19349
  }
18655
19350
  }
18656
19351
 
@@ -28525,60 +29220,306 @@ function wireControlChannel(rawChannel, daemon, logAdapter, deps) {
28525
29220
  });
28526
29221
  }
28527
29222
 
28528
- // src/services/collab/crdt-permissions.ts
28529
- function isPersonalPeer(entry) {
28530
- return entry?.source === "personal";
29223
+ // src/services/collab/collab-hosting-rehydrate.ts
29224
+ function classifyPersistedRooms(records, now) {
29225
+ const plan = { rejoin: [], skip: [] };
29226
+ for (const record of records) {
29227
+ if (!record.token) {
29228
+ plan.skip.push({ record, reason: "missing-token" });
29229
+ } else if (record.expiresAt <= now) {
29230
+ plan.skip.push({ record, reason: "expired" });
29231
+ } else {
29232
+ plan.rejoin.push(record);
29233
+ }
29234
+ }
29235
+ return plan;
28531
29236
  }
28532
- function findEntry(registry, peer) {
28533
- const direct = registry.getEntry(peer.peerId);
28534
- if (direct) return direct;
28535
- if (peer.peerType === "user") {
28536
- return registry.findBySource("personal");
29237
+ async function rehydrateCollabRooms(deps) {
29238
+ const records = await deps.store.list();
29239
+ if (records.length === 0) return;
29240
+ const plan = classifyPersistedRooms(records, deps.now);
29241
+ for (const { record, reason } of plan.skip) {
29242
+ deps.log({ event: "collab_rehydrate_skip", roomId: record.roomId, reason });
29243
+ await deps.store.remove(record.roomId).catch((err3) => {
29244
+ deps.log({
29245
+ event: "collab_rehydrate_sweep_failed",
29246
+ roomId: record.roomId,
29247
+ error: err3 instanceof Error ? err3.message : String(err3)
29248
+ });
29249
+ });
29250
+ }
29251
+ for (const record of plan.rejoin) {
29252
+ try {
29253
+ deps.joinRoom({
29254
+ roomId: record.roomId,
29255
+ taskId: record.taskId,
29256
+ token: record.token,
29257
+ expiresAt: record.expiresAt,
29258
+ signalingBaseUrl: deps.signalingBaseUrl,
29259
+ userToken: deps.userToken,
29260
+ machineId: deps.machineId
29261
+ });
29262
+ deps.log({ event: "collab_rehydrate_rejoin", roomId: record.roomId, taskId: record.taskId });
29263
+ } catch (err3) {
29264
+ deps.log({
29265
+ event: "collab_rehydrate_rejoin_failed",
29266
+ roomId: record.roomId,
29267
+ error: err3 instanceof Error ? err3.message : String(err3)
29268
+ });
29269
+ }
28537
29270
  }
28538
- return void 0;
28539
29271
  }
28540
- var COLLAB_VISIBLE_PREFIXES = {
28541
- owner: /* @__PURE__ */ new Set(["plan", "canvas"]),
28542
- "collaborator-full": /* @__PURE__ */ new Set(["plan", "canvas"]),
28543
- "collaborator-review": /* @__PURE__ */ new Set(["plan", "canvas"]),
28544
- viewer: /* @__PURE__ */ new Set([])
28545
- };
28546
- var COLLAB_MUTABLE_PREFIXES = {
28547
- owner: /* @__PURE__ */ new Set(["plan", "canvas"]),
28548
- "collaborator-full": /* @__PURE__ */ new Set(["plan", "canvas"]),
28549
- "collaborator-review": /* @__PURE__ */ new Set(["plan"]),
28550
- viewer: /* @__PURE__ */ new Set([])
28551
- };
28552
- function buildCollabPermissions(registry) {
28553
- function isAllowed(_doc, peer) {
28554
- if (peer.channelKind === "storage") return true;
28555
- if (peer.peerType === "service") return true;
28556
- const entry = findEntry(registry, peer);
28557
- if (!entry) return false;
28558
- return isPersonalPeer(entry);
29272
+ function startCollabRehydrate(args) {
29273
+ const signalingBaseUrl = args.signalingBaseUrl;
29274
+ if (!signalingBaseUrl) return;
29275
+ void rehydrateCollabRooms({
29276
+ store: args.store,
29277
+ joinRoom: (config) => {
29278
+ args.collabRoomManager.joinRoom(config);
29279
+ },
29280
+ signalingBaseUrl,
29281
+ userToken: args.userToken,
29282
+ machineId: args.machineId,
29283
+ now: Date.now(),
29284
+ log: args.log
29285
+ }).catch((err3) => {
29286
+ args.log({
29287
+ event: "collab_rehydrate_failed",
29288
+ error: err3 instanceof Error ? err3.message : String(err3)
29289
+ });
29290
+ });
29291
+ }
29292
+
29293
+ // src/services/collab/collab-hosting-store.ts
29294
+ import { join as join34 } from "path";
29295
+
29296
+ // src/services/storage/json-document-store.ts
29297
+ import { mkdir as mkdir14, readFile as readFile20 } from "fs/promises";
29298
+ import { dirname as dirname15 } from "path";
29299
+ var WRITE_DEBOUNCE_MS = 50;
29300
+ function applyMutations(records, mutations) {
29301
+ for (const [id, mutation] of mutations) {
29302
+ if (mutation.kind === "delete") {
29303
+ delete records[id];
29304
+ } else {
29305
+ records[id] = mutation.data;
29306
+ }
29307
+ }
29308
+ }
29309
+ function resolveWaiters(waiters) {
29310
+ for (const waiter of waiters) waiter.resolve();
29311
+ }
29312
+ function rejectWaiters(waiters, error) {
29313
+ for (const waiter of waiters) waiter.reject(error);
29314
+ }
29315
+ function buildJsonDocumentStore(opts) {
29316
+ const { filePath, recordSchema, currentVersion, migrate, storeName, docType } = opts;
29317
+ let cache2 = null;
29318
+ const listeners = /* @__PURE__ */ new Set();
29319
+ let writeQueue = Promise.resolve();
29320
+ const lockPath2 = `${filePath}.lock`;
29321
+ let pendingMutations = /* @__PURE__ */ new Map();
29322
+ let pendingFlushTimer = null;
29323
+ let pendingFlushWaiters = [];
29324
+ function notify(event) {
29325
+ for (const listener of listeners) {
29326
+ try {
29327
+ listener(event);
29328
+ } catch {
29329
+ }
29330
+ }
29331
+ }
29332
+ async function ensureDir() {
29333
+ await mkdir14(dirname15(filePath), { recursive: true });
29334
+ }
29335
+ async function readFromDisk() {
29336
+ try {
29337
+ const raw = await readFile20(filePath, "utf-8");
29338
+ return migrate(JSON.parse(raw));
29339
+ } catch (err3) {
29340
+ if (isEnoent(err3)) {
29341
+ return { schemaVersion: currentVersion, records: {} };
29342
+ }
29343
+ if (isCorruptionError(err3)) {
29344
+ const event = await quarantineCorruptFile({
29345
+ path: filePath,
29346
+ storeName,
29347
+ docType,
29348
+ error: err3,
29349
+ defaultsApplied: true,
29350
+ logger: opts.logger
29351
+ });
29352
+ opts.onCorrupt?.(event);
29353
+ return { schemaVersion: currentVersion, records: {} };
29354
+ }
29355
+ throw err3;
29356
+ }
29357
+ }
29358
+ async function readStore() {
29359
+ if (cache2) return cache2;
29360
+ cache2 = await readFromDisk();
29361
+ return cache2;
29362
+ }
29363
+ async function flushPendingWrite() {
29364
+ pendingFlushTimer = null;
29365
+ const waiters = pendingFlushWaiters;
29366
+ pendingFlushWaiters = [];
29367
+ const mutations = pendingMutations;
29368
+ pendingMutations = /* @__PURE__ */ new Map();
29369
+ if (mutations.size === 0) {
29370
+ resolveWaiters(waiters);
29371
+ return;
29372
+ }
29373
+ try {
29374
+ await commitMutations(mutations);
29375
+ resolveWaiters(waiters);
29376
+ } catch (err3) {
29377
+ requeueMutations(mutations);
29378
+ if (pendingMutations.size > 0 && pendingFlushTimer === null) {
29379
+ pendingFlushTimer = setTimeout(() => {
29380
+ void flushPendingWrite();
29381
+ }, WRITE_DEBOUNCE_MS);
29382
+ }
29383
+ rejectWaiters(waiters, err3);
29384
+ }
29385
+ }
29386
+ async function commitMutations(mutations) {
29387
+ await withFileLock(lockPath2, async () => {
29388
+ const disk = await readFromDisk();
29389
+ applyMutations(disk.records, mutations);
29390
+ await ensureDir();
29391
+ await atomicWriteFile(filePath, JSON.stringify(disk));
29392
+ applyMutations(disk.records, pendingMutations);
29393
+ cache2 = disk;
29394
+ });
29395
+ }
29396
+ function requeueMutations(mutations) {
29397
+ for (const [id, mutation] of mutations) {
29398
+ if (!pendingMutations.has(id)) pendingMutations.set(id, mutation);
29399
+ }
29400
+ }
29401
+ function scheduleFlush() {
29402
+ return new Promise((resolve12, reject) => {
29403
+ pendingFlushWaiters.push({ resolve: resolve12, reject });
29404
+ if (pendingFlushTimer) return;
29405
+ pendingFlushTimer = setTimeout(() => {
29406
+ void flushPendingWrite();
29407
+ }, WRITE_DEBOUNCE_MS);
29408
+ });
29409
+ }
29410
+ function enqueueWrite(op) {
29411
+ let resultFlush;
29412
+ const next = writeQueue.then(
29413
+ async () => {
29414
+ const out = await op();
29415
+ resultFlush = out?.flush;
29416
+ },
29417
+ async () => {
29418
+ const out = await op();
29419
+ resultFlush = out?.flush;
29420
+ }
29421
+ );
29422
+ writeQueue = next;
29423
+ return next.then(() => resultFlush);
28559
29424
  }
28560
29425
  return {
28561
- visibility: isAllowed,
28562
- mutability: isAllowed,
28563
- creation(docId, peer) {
28564
- const colonIdx = docId.indexOf(":");
28565
- if (colonIdx > 0 && OBSOLETE_PREFIXES.has(docId.slice(0, colonIdx))) return false;
28566
- if (peer.channelKind === "storage") return true;
28567
- if (peer.peerType === "service") return true;
28568
- const entry = findEntry(registry, peer);
28569
- if (!entry) return false;
28570
- return isPersonalPeer(entry);
29426
+ async get(id) {
29427
+ const store = await readStore();
29428
+ const record = store.records[id];
29429
+ return record ?? null;
28571
29430
  },
28572
- deletion(_doc, _peer) {
28573
- return false;
29431
+ async set(id, data) {
29432
+ await enqueueWrite(async () => {
29433
+ const store = await readStore();
29434
+ store.records[id] = data;
29435
+ pendingMutations.set(id, { kind: "set", data });
29436
+ const flush = scheduleFlush();
29437
+ notify({ kind: "set", id, data });
29438
+ return { flush };
29439
+ });
29440
+ },
29441
+ async update(id, fn) {
29442
+ await enqueueWrite(async () => {
29443
+ const store = await readStore();
29444
+ const existing = store.records[id];
29445
+ if (!existing) return void 0;
29446
+ const parsed = recordSchema.parse(existing);
29447
+ const updated = fn(parsed);
29448
+ if (updated === parsed) return void 0;
29449
+ store.records[id] = updated;
29450
+ pendingMutations.set(id, { kind: "set", data: updated });
29451
+ const flush = scheduleFlush();
29452
+ notify({ kind: "set", id, data: updated });
29453
+ return { flush };
29454
+ });
29455
+ },
29456
+ async delete(id) {
29457
+ await enqueueWrite(async () => {
29458
+ const store = await readStore();
29459
+ if (!(id in store.records)) return void 0;
29460
+ delete store.records[id];
29461
+ pendingMutations.set(id, { kind: "delete" });
29462
+ const flush = scheduleFlush();
29463
+ notify({ kind: "delete", id });
29464
+ return { flush };
29465
+ });
29466
+ },
29467
+ async list() {
29468
+ const store = await readStore();
29469
+ return { ...store.records };
29470
+ },
29471
+ subscribe(listener) {
29472
+ listeners.add(listener);
29473
+ return () => {
29474
+ listeners.delete(listener);
29475
+ };
28574
29476
  }
28575
29477
  };
28576
29478
  }
28577
- function isCollabPrefixVisible(role, prefix) {
28578
- return COLLAB_VISIBLE_PREFIXES[role].has(prefix);
29479
+
29480
+ // src/services/collab/collab-hosting-store.ts
29481
+ var CollabHostingRecordSchema = external_exports.object({
29482
+ roomId: external_exports.string().min(1),
29483
+ taskId: external_exports.string().min(1),
29484
+ token: external_exports.string().min(1),
29485
+ expiresAt: external_exports.number()
29486
+ });
29487
+ var COLLAB_HOSTING_STORE_VERSION = 1;
29488
+ var StoreEnvelopeSchema = external_exports.object({
29489
+ records: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
29490
+ });
29491
+ function migrateCollabHostingStore(raw) {
29492
+ const records = {};
29493
+ const envelope = StoreEnvelopeSchema.safeParse(raw);
29494
+ if (!envelope.success || !envelope.data.records) {
29495
+ return { schemaVersion: COLLAB_HOSTING_STORE_VERSION, records };
29496
+ }
29497
+ for (const [id, value] of Object.entries(envelope.data.records)) {
29498
+ const parsed = CollabHostingRecordSchema.safeParse(value);
29499
+ if (parsed.success) records[id] = parsed.data;
29500
+ }
29501
+ return { schemaVersion: COLLAB_HOSTING_STORE_VERSION, records };
28579
29502
  }
28580
- function isCollabPrefixMutable(role, prefix) {
28581
- return COLLAB_MUTABLE_PREFIXES[role].has(prefix);
29503
+ function buildCollabHostingStore(dataDir) {
29504
+ const store = buildJsonDocumentStore({
29505
+ storeName: "collab-hosting",
29506
+ docType: "generic",
29507
+ filePath: join34(dataDir, "collab-rooms.json"),
29508
+ recordSchema: CollabHostingRecordSchema,
29509
+ currentVersion: COLLAB_HOSTING_STORE_VERSION,
29510
+ migrate: migrateCollabHostingStore
29511
+ });
29512
+ return {
29513
+ async persist(record) {
29514
+ await store.set(record.roomId, record);
29515
+ },
29516
+ async remove(roomId) {
29517
+ await store.delete(roomId);
29518
+ },
29519
+ async list() {
29520
+ return Object.values(await store.list());
29521
+ }
29522
+ };
28582
29523
  }
28583
29524
 
28584
29525
  // src/services/collab/peer-role-registry.ts
@@ -28651,7 +29592,7 @@ function createPeerRoleRegistry() {
28651
29592
 
28652
29593
  // src/services/epoch-pruning.ts
28653
29594
  import { readdir as readdir13, rm as rm7 } from "fs/promises";
28654
- import { join as join34 } from "path";
29595
+ import { join as join35 } from "path";
28655
29596
  var LEGACY_PREFIXES = [
28656
29597
  "task-meta",
28657
29598
  "task-conv",
@@ -28691,7 +29632,7 @@ async function pruneOldEpochData(dataDir, log) {
28691
29632
  }
28692
29633
  if (!shouldPrune(decoded)) continue;
28693
29634
  removals.push(
28694
- rm7(join34(dataDir, entry.name), { recursive: true }).then(() => {
29635
+ rm7(join35(dataDir, entry.name), { recursive: true }).then(() => {
28695
29636
  pruned++;
28696
29637
  }).catch((err3) => {
28697
29638
  log({
@@ -28710,7 +29651,7 @@ async function pruneOldEpochData(dataDir, log) {
28710
29651
 
28711
29652
  // src/services/file-watcher.ts
28712
29653
  import { existsSync as existsSync4 } from "fs";
28713
- import { dirname as dirname15, relative as relative6 } from "path";
29654
+ import { dirname as dirname16, relative as relative6 } from "path";
28714
29655
  var IGNORE_GLOBS = [
28715
29656
  ".git",
28716
29657
  "node_modules",
@@ -28844,7 +29785,7 @@ async function startWatcher(cwd, entry, log) {
28844
29785
  event: "file_watcher_pool_create_failed",
28845
29786
  cwd,
28846
29787
  cwdExists: pathExistsForLog(cwd),
28847
- parentExists: pathExistsForLog(dirname15(cwd)),
29788
+ parentExists: pathExistsForLog(dirname16(cwd)),
28848
29789
  error: err3 instanceof Error ? err3.message : String(err3)
28849
29790
  });
28850
29791
  }
@@ -29310,7 +30251,7 @@ function routeDataChannel(label) {
29310
30251
  }
29311
30252
  return { kind: "unknown", label };
29312
30253
  }
29313
- function assertNever3(x2) {
30254
+ function assertNever4(x2) {
29314
30255
  throw new Error(`Unhandled channel route: ${JSON.stringify(x2)}`);
29315
30256
  }
29316
30257
  function machineIdToPeerId(machineId) {
@@ -29463,7 +30404,14 @@ function installControlGuard(dc, label, machineId, log, logAdapter, dcBufferedSa
29463
30404
  var HANDSHAKE_TIMEOUT_MS = 3e4;
29464
30405
  function noopLogAdapter(_entry) {
29465
30406
  }
30407
+ function decideStaleSignal(entry, incoming) {
30408
+ if (!entry) return { action: "drop" };
30409
+ if (entry.generationId === incoming.generationId) return { action: "apply" };
30410
+ if (entry.lastState === "new") return { action: "reoffer", generationId: entry.generationId };
30411
+ return { action: "drop" };
30412
+ }
29466
30413
  var DISCONNECT_WATCHDOG_MS = 5e3;
30414
+ var REOFFER_THROTTLE_MS = 1e3;
29467
30415
  function createPeerManager(config) {
29468
30416
  const peers = /* @__PURE__ */ new Map();
29469
30417
  const pendingCreates = /* @__PURE__ */ new Map();
@@ -29594,6 +30542,46 @@ function createPeerManager(config) {
29594
30542
  }
29595
30543
  };
29596
30544
  }
30545
+ function logStaleAnswer(fromMachineId, currentGenerationId, staleGenerationId) {
30546
+ config.logAdapter?.({
30547
+ event: "webrtc_answer_stale",
30548
+ fromMachineId,
30549
+ currentGenerationId,
30550
+ staleGenerationId
30551
+ });
30552
+ config.log.debug(
30553
+ { fromMachineId, currentGenerationId, staleGenerationId },
30554
+ "Dropping stale WebRTC answer"
30555
+ );
30556
+ }
30557
+ function logStaleIce(fromMachineId, currentGenerationId, staleGenerationId) {
30558
+ config.logAdapter?.({
30559
+ event: "webrtc_ice_candidate_stale",
30560
+ fromMachineId,
30561
+ currentGenerationId,
30562
+ staleGenerationId
30563
+ });
30564
+ config.log.debug(
30565
+ { fromMachineId, currentGenerationId, staleGenerationId },
30566
+ "Dropping stale ICE candidate"
30567
+ );
30568
+ }
30569
+ function reemitOfferForDesync(machineId, entry, trigger) {
30570
+ if (!entry.offerSdp || !config.onOffer) return false;
30571
+ const now = Date.now();
30572
+ if (entry.lastReofferAt !== void 0 && now - entry.lastReofferAt < REOFFER_THROTTLE_MS) {
30573
+ return true;
30574
+ }
30575
+ entry.lastReofferAt = now;
30576
+ config.onOffer(machineId, { type: "offer", sdp: entry.offerSdp }, entry.generationId);
30577
+ config.logAdapter?.({
30578
+ event: "webrtc_reoffer_sent",
30579
+ machineId,
30580
+ generationId: entry.generationId,
30581
+ trigger
30582
+ });
30583
+ return true;
30584
+ }
29597
30585
  function tearDownPeer(machineId, reason) {
29598
30586
  const peerId = machineIdToPeerId(machineId);
29599
30587
  disposeLoroGuard(peerId);
@@ -29770,7 +30758,7 @@ function createPeerManager(config) {
29770
30758
  return;
29771
30759
  }
29772
30760
  default:
29773
- assertNever3(route);
30761
+ assertNever4(route);
29774
30762
  }
29775
30763
  } catch (err3) {
29776
30764
  config.log.warn(
@@ -29960,7 +30948,8 @@ function createPeerManager(config) {
29960
30948
  generationId,
29961
30949
  createdAt: now,
29962
30950
  lastState: "new",
29963
- lastStateAt: now
30951
+ lastStateAt: now,
30952
+ offerSdp: offer.sdp
29964
30953
  };
29965
30954
  if (pendingCreates.get(targetMachineId) !== promise) {
29966
30955
  disposeLoroGuard(machineIdToPeerId(targetMachineId));
@@ -30007,44 +30996,44 @@ function createPeerManager(config) {
30007
30996
  async handleAnswer(fromMachineId, answer, generationId) {
30008
30997
  const entry = await resolveLiveEntry(fromMachineId, "answer", generationId);
30009
30998
  if (!entry) return;
30010
- if (entry.generationId !== generationId) {
30011
- config.logAdapter?.({
30012
- event: "webrtc_answer_stale",
30013
- fromMachineId,
30014
- currentGenerationId: entry.generationId,
30015
- staleGenerationId: generationId
30016
- });
30017
- config.log.debug(
30018
- {
30019
- fromMachineId,
30020
- currentGenerationId: entry.generationId,
30021
- staleGenerationId: generationId
30022
- },
30023
- "Dropping stale WebRTC answer"
30024
- );
30025
- return;
30999
+ const decision = decideStaleSignal(
31000
+ { generationId: entry.generationId, lastState: entry.lastState },
31001
+ { kind: "answer", generationId }
31002
+ );
31003
+ switch (decision.action) {
31004
+ case "apply":
31005
+ break;
31006
+ case "reoffer":
31007
+ if (reemitOfferForDesync(fromMachineId, entry, "answer")) return;
31008
+ logStaleAnswer(fromMachineId, entry.generationId, generationId);
31009
+ return;
31010
+ case "drop":
31011
+ logStaleAnswer(fromMachineId, entry.generationId, generationId);
31012
+ return;
31013
+ default:
31014
+ return assertNever4(decision);
30026
31015
  }
30027
31016
  await entry.pc.setRemoteDescription(answer);
30028
31017
  },
30029
31018
  async handleIce(fromMachineId, candidate, generationId) {
30030
31019
  const entry = await resolveLiveEntry(fromMachineId, "ice", generationId);
30031
31020
  if (!entry) return;
30032
- if (entry.generationId !== generationId) {
30033
- config.logAdapter?.({
30034
- event: "webrtc_ice_candidate_stale",
30035
- fromMachineId,
30036
- currentGenerationId: entry.generationId,
30037
- staleGenerationId: generationId
30038
- });
30039
- config.log.debug(
30040
- {
30041
- fromMachineId,
30042
- currentGenerationId: entry.generationId,
30043
- staleGenerationId: generationId
30044
- },
30045
- "Dropping stale ICE candidate"
30046
- );
30047
- return;
31021
+ const decision = decideStaleSignal(
31022
+ { generationId: entry.generationId, lastState: entry.lastState },
31023
+ { kind: "ice", generationId }
31024
+ );
31025
+ switch (decision.action) {
31026
+ case "apply":
31027
+ break;
31028
+ case "reoffer":
31029
+ if (reemitOfferForDesync(fromMachineId, entry, "ice")) return;
31030
+ logStaleIce(fromMachineId, entry.generationId, generationId);
31031
+ return;
31032
+ case "drop":
31033
+ logStaleIce(fromMachineId, entry.generationId, generationId);
31034
+ return;
31035
+ default:
31036
+ return assertNever4(decision);
30048
31037
  }
30049
31038
  try {
30050
31039
  await entry.pc.addIceCandidate(candidate);
@@ -30428,7 +31417,7 @@ function createLocalDirectPeer(config) {
30428
31417
  return;
30429
31418
  }
30430
31419
  default:
30431
- assertNever4(route);
31420
+ assertNever5(route);
30432
31421
  }
30433
31422
  }
30434
31423
  function handleOpen(label) {
@@ -30534,7 +31523,7 @@ function createLocalDirectPeer(config) {
30534
31523
  );
30535
31524
  return;
30536
31525
  default:
30537
- return assertNever4(frame);
31526
+ return assertNever5(frame);
30538
31527
  }
30539
31528
  },
30540
31529
  dispose(reason) {
@@ -30555,7 +31544,7 @@ function createLocalDirectPeer(config) {
30555
31544
  }
30556
31545
  };
30557
31546
  }
30558
- function assertNever4(x2) {
31547
+ function assertNever5(x2) {
30559
31548
  throw new Error(`Unhandled local-direct route: ${JSON.stringify(x2)}`);
30560
31549
  }
30561
31550
 
@@ -30932,8 +31921,8 @@ function onConnection(ws, deps, peers) {
30932
31921
  }
30933
31922
 
30934
31923
  // src/services/local-direct/local-direct-token.ts
30935
- import { chmod as chmod2, mkdir as mkdir14, rename as rename12, rm as rm8, writeFile as writeFile14 } from "fs/promises";
30936
- import { dirname as dirname16, join as join35 } from "path";
31924
+ import { chmod as chmod2, mkdir as mkdir15, rename as rename12, rm as rm8, writeFile as writeFile14 } from "fs/promises";
31925
+ import { dirname as dirname17, join as join36 } from "path";
30937
31926
  var ADVERTISEMENT_FILE = "local-direct.json";
30938
31927
  var ADVERTISEMENT_MODE = 384;
30939
31928
  var ADVERTISEMENT_DIR_MODE = 448;
@@ -30942,12 +31931,12 @@ function generateLocalDirectToken() {
30942
31931
  return nanoid(TOKEN_LENGTH);
30943
31932
  }
30944
31933
  function advertisementPath(shipyardHome) {
30945
- return join35(shipyardHome, "data", ADVERTISEMENT_FILE);
31934
+ return join36(shipyardHome, "data", ADVERTISEMENT_FILE);
30946
31935
  }
30947
31936
  async function writeAdvertisement(shipyardHome, ad) {
30948
31937
  const target = advertisementPath(shipyardHome);
30949
- const dir = dirname16(target);
30950
- await mkdir14(dir, { recursive: true, mode: ADVERTISEMENT_DIR_MODE });
31938
+ const dir = dirname17(target);
31939
+ await mkdir15(dir, { recursive: true, mode: ADVERTISEMENT_DIR_MODE });
30951
31940
  try {
30952
31941
  await chmod2(dir, ADVERTISEMENT_DIR_MODE);
30953
31942
  } catch {
@@ -31208,8 +32197,8 @@ async function setupPluginEventWiring(deps) {
31208
32197
  // src/services/plugins/plugin-file-watcher.ts
31209
32198
  import { exec as execCb2 } from "child_process";
31210
32199
  import { existsSync as existsSync5 } from "fs";
31211
- import { readdir as readdir14, readFile as readFile20, stat as stat11 } from "fs/promises";
31212
- import { basename as basename7, dirname as dirname17, join as join36 } from "path";
32200
+ import { readdir as readdir14, readFile as readFile21, stat as stat11 } from "fs/promises";
32201
+ import { basename as basename7, dirname as dirname18, join as join37 } from "path";
31213
32202
  import { pathToFileURL as pathToFileURL2 } from "url";
31214
32203
  import { promisify as promisify7 } from "util";
31215
32204
 
@@ -31344,7 +32333,7 @@ var PluginFileWatcher = class {
31344
32333
  }
31345
32334
  const loaded = [];
31346
32335
  for (const entry of entries) {
31347
- const pluginDir = join36(dir, entry);
32336
+ const pluginDir = join37(dir, entry);
31348
32337
  let stats;
31349
32338
  try {
31350
32339
  stats = await stat11(pluginDir);
@@ -31359,10 +32348,10 @@ var PluginFileWatcher = class {
31359
32348
  this.#reconcile(loaded);
31360
32349
  }
31361
32350
  async #loadPlugin(id, pluginDir) {
31362
- const manifestPath = join36(pluginDir, "plugin.json");
32351
+ const manifestPath = join37(pluginDir, "plugin.json");
31363
32352
  let manifestRaw;
31364
32353
  try {
31365
- manifestRaw = await readFile20(manifestPath, "utf-8");
32354
+ manifestRaw = await readFile21(manifestPath, "utf-8");
31366
32355
  } catch {
31367
32356
  this.#config.log({ event: "plugin_manifest_missing", pluginId: id, pluginDir });
31368
32357
  return null;
@@ -31396,9 +32385,9 @@ var PluginFileWatcher = class {
31396
32385
  return null;
31397
32386
  }
31398
32387
  let template = "";
31399
- const templatePath = join36(pluginDir, "template.html");
32388
+ const templatePath = join37(pluginDir, "template.html");
31400
32389
  try {
31401
- template = await readFile20(templatePath, "utf-8");
32390
+ template = await readFile21(templatePath, "utf-8");
31402
32391
  } catch {
31403
32392
  }
31404
32393
  await this.#loadAndRegisterHandler(id, pluginDir, manifest.title, manifest);
@@ -31411,7 +32400,7 @@ var PluginFileWatcher = class {
31411
32400
  };
31412
32401
  }
31413
32402
  async #loadAndRegisterHandler(id, pluginDir, title, manifest) {
31414
- const handlerPath = join36(pluginDir, "handler.mjs");
32403
+ const handlerPath = join37(pluginDir, "handler.mjs");
31415
32404
  const loaded = await this.#resolveHandler(id, handlerPath);
31416
32405
  const { handlerFn, provideResourcesFn } = loaded;
31417
32406
  if (manifest.provideResources && provideResourcesFn && this.#config.resourceResolver) {
@@ -31575,7 +32564,7 @@ async function findNativeDependencyMarker(pluginDir) {
31575
32564
  }
31576
32565
  async function scanNativeDependencyDir(dir, stack) {
31577
32566
  for (const entry of await readNativeScanEntries(dir)) {
31578
- const fullPath = join36(dir, entry.name);
32567
+ const fullPath = join37(dir, entry.name);
31579
32568
  if (isNativeDependencyMarker(entry, dir)) return fullPath;
31580
32569
  if (shouldDescendForNativeScan(entry)) stack.push(fullPath);
31581
32570
  }
@@ -31595,12 +32584,12 @@ function shouldDescendForNativeScan(entry) {
31595
32584
  return entry.isDirectory() && entry.name !== ".git" && entry.name !== NATIVE_DEP_MARKER_DIR;
31596
32585
  }
31597
32586
  function isNodeBuildReleaseDir(dir) {
31598
- return basename7(dir) === "Release" && basename7(dirname17(dir)) === "build";
32587
+ return basename7(dir) === "Release" && basename7(dirname18(dir)) === "build";
31599
32588
  }
31600
32589
 
31601
32590
  // src/services/port-detection.ts
31602
32591
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
31603
- import { dirname as dirname18, join as join37 } from "path";
32592
+ import { dirname as dirname19, join as join38 } from "path";
31604
32593
  var LSOF_TIMEOUT_MS = 8e3;
31605
32594
  var LSOF_TTL_MS = 5e3;
31606
32595
  var DEFAULT_POLL_INTERVAL_MS = 5e3;
@@ -31783,7 +32772,7 @@ function hasStringName(val) {
31783
32772
  function resolveProjectRoot(cwd) {
31784
32773
  let dir = cwd;
31785
32774
  while (true) {
31786
- const candidate = join37(dir, "package.json");
32775
+ const candidate = join38(dir, "package.json");
31787
32776
  if (existsSync6(candidate)) {
31788
32777
  let packageName;
31789
32778
  try {
@@ -31796,7 +32785,7 @@ function resolveProjectRoot(cwd) {
31796
32785
  }
31797
32786
  return { projectRoot: dir, packageName };
31798
32787
  }
31799
- const parent = dirname18(dir);
32788
+ const parent = dirname19(dir);
31800
32789
  if (parent === dir) {
31801
32790
  return {};
31802
32791
  }
@@ -31907,7 +32896,7 @@ import {
31907
32896
  unlinkSync as unlinkSync2,
31908
32897
  writeFileSync as writeFileSync2
31909
32898
  } from "fs";
31910
- import { join as join38 } from "path";
32899
+ import { join as join39 } from "path";
31911
32900
  function isEnoent6(err3) {
31912
32901
  return err3 instanceof Error && Reflect.get(err3, "code") === "ENOENT";
31913
32902
  }
@@ -31917,7 +32906,7 @@ function atomicWriteSync(filePath, content) {
31917
32906
  renameSync(tmpPath, filePath);
31918
32907
  }
31919
32908
  function recordFilePath2(rootDir, taskId, elementId) {
31920
- return join38(rootDir, taskId, `${elementId}.json`);
32909
+ return join39(rootDir, taskId, `${elementId}.json`);
31921
32910
  }
31922
32911
  function parseRecord2(filePath, logger2) {
31923
32912
  let raw;
@@ -31957,7 +32946,7 @@ function createPreviewStateStore(opts) {
31957
32946
  return taskMap;
31958
32947
  }
31959
32948
  function scanTaskDir(taskId) {
31960
- const taskPath = join38(rootDir, taskId);
32949
+ const taskPath = join39(rootDir, taskId);
31961
32950
  let files;
31962
32951
  try {
31963
32952
  files = readdirSync4(taskPath);
@@ -31966,7 +32955,7 @@ function createPreviewStateStore(opts) {
31966
32955
  }
31967
32956
  for (const file of files) {
31968
32957
  if (!file.endsWith(".json")) continue;
31969
- const record = parseRecord2(join38(taskPath, file), logger2);
32958
+ const record = parseRecord2(join39(taskPath, file), logger2);
31970
32959
  if (record) getTaskMap(taskId).set(record.elementId, record);
31971
32960
  }
31972
32961
  }
@@ -31992,7 +32981,7 @@ function createPreviewStateStore(opts) {
31992
32981
  function writeToDisk(taskId, state) {
31993
32982
  const filePath = recordFilePath2(rootDir, taskId, state.elementId);
31994
32983
  try {
31995
- mkdirSync3(join38(rootDir, taskId), { recursive: true });
32984
+ mkdirSync3(join39(rootDir, taskId), { recursive: true });
31996
32985
  atomicWriteSync(filePath, JSON.stringify(state));
31997
32986
  } catch (err3) {
31998
32987
  logger2.warn(
@@ -33362,633 +34351,172 @@ function handleAssetChannel(dc, assetStore, deliverableStore, allowedTaskId, log
33362
34351
  }).catch((err3) => {
33363
34352
  log({
33364
34353
  event: "asset_upload_failed",
33365
- requestId,
33366
- error: err3 instanceof Error ? err3.message : String(err3)
33367
- });
33368
- rawSend(
33369
- JSON.stringify({
33370
- type: "upload-error",
33371
- requestId,
33372
- error: err3 instanceof Error ? err3.message : "Upload failed"
33373
- })
33374
- );
33375
- });
33376
- }
33377
- async function handleDownloadRequest(requestId, assetId) {
33378
- try {
33379
- const { data, meta } = await assetStore.read(assetId);
33380
- rawSend(
33381
- JSON.stringify({
33382
- type: "download-head",
33383
- requestId,
33384
- mimeType: meta.mimeType,
33385
- totalBytes: data.byteLength
33386
- })
33387
- );
33388
- const shell = createDownloadShell(requestId);
33389
- downloadShells.set(requestId, shell);
33390
- await shell.initialize();
33391
- await shell.setConnected(true);
33392
- const bytes = new Uint8Array(data);
33393
- let chunkIndex = 0;
33394
- let yieldCounter = 0;
33395
- for (let offset = 0; offset < bytes.byteLength; offset += ASSET_CHUNK_SIZE) {
33396
- const end = Math.min(offset + ASSET_CHUNK_SIZE, bytes.byteLength);
33397
- const chunk = bytes.subarray(offset, end);
33398
- await shell.send({
33399
- kind: "download-chunk",
33400
- requestId,
33401
- chunkIndex,
33402
- data: encodeChunk(chunk)
33403
- });
33404
- chunkIndex++;
33405
- yieldCounter++;
33406
- if (yieldCounter % YIELD_EVERY_CHUNKS === 0) {
33407
- await new Promise((r) => setTimeout(r, 0));
33408
- }
33409
- }
33410
- rawSend(JSON.stringify({ type: "download-done", requestId }));
33411
- log({ event: "asset_download_complete", requestId, assetId, size: data.byteLength });
33412
- setTimeout(() => downloadShells.delete(requestId), UPLOAD_TIMEOUT_MS);
33413
- } catch (err3) {
33414
- downloadShells.delete(requestId);
33415
- const { event, reason } = classifyDownloadError(assetId, err3);
33416
- log({ event, requestId, assetId, error: reason });
33417
- rawSend(
33418
- JSON.stringify({
33419
- type: "download-error",
33420
- requestId,
33421
- error: err3 instanceof Error ? err3.message : "Download failed"
33422
- })
33423
- );
33424
- }
33425
- }
33426
- async function handleDeliverableFileRequest(requestId, taskId, deliverableId) {
33427
- const resolved = await resolveDeliverableFile(deliverableStore, taskId, deliverableId);
33428
- if (resolved.kind === "error") {
33429
- log({
33430
- event: resolved.event,
33431
- requestId,
33432
- taskId,
33433
- deliverableId,
33434
- ...resolved.logExtras
33435
- });
33436
- rawSend(JSON.stringify({ type: "download-error", requestId, error: "" }));
33437
- return;
33438
- }
33439
- if (allowedTaskId !== null && resolved.recordTaskId !== allowedTaskId) {
33440
- log({
33441
- event: "deliverable_file_task_scope_mismatch",
33442
- level: "warn",
33443
- requestId,
33444
- allowedTaskId,
33445
- requestedTaskId: taskId
33446
- });
33447
- rawSend(JSON.stringify({ type: "download-error", requestId, error: "" }));
33448
- return;
33449
- }
33450
- const { filePath, mimeType } = resolved;
33451
- try {
33452
- const data = await fsReadFile(filePath);
33453
- rawSend(
33454
- JSON.stringify({
33455
- type: "download-head",
33456
- requestId,
33457
- mimeType,
33458
- totalBytes: data.byteLength
33459
- })
33460
- );
33461
- const shell = createDownloadShell(requestId);
33462
- downloadShells.set(requestId, shell);
33463
- await shell.initialize();
33464
- await shell.setConnected(true);
33465
- const bytes = new Uint8Array(data);
33466
- let chunkIndex = 0;
33467
- let yieldCounter = 0;
33468
- for (let offset = 0; offset < bytes.byteLength; offset += ASSET_CHUNK_SIZE) {
33469
- const end = Math.min(offset + ASSET_CHUNK_SIZE, bytes.byteLength);
33470
- const chunk = bytes.subarray(offset, end);
33471
- await shell.send({
33472
- kind: "download-chunk",
33473
- requestId,
33474
- chunkIndex,
33475
- data: encodeChunk(chunk)
33476
- });
33477
- chunkIndex++;
33478
- yieldCounter++;
33479
- if (yieldCounter % YIELD_EVERY_CHUNKS === 0) {
33480
- await new Promise((r) => setTimeout(r, 0));
33481
- }
33482
- }
33483
- rawSend(JSON.stringify({ type: "download-done", requestId }));
33484
- log({
33485
- event: "deliverable_file_download_complete",
33486
- requestId,
33487
- deliverableId,
33488
- size: data.byteLength
33489
- });
33490
- setTimeout(() => downloadShells.delete(requestId), UPLOAD_TIMEOUT_MS);
33491
- } catch (err3) {
33492
- downloadShells.delete(requestId);
33493
- const reason = err3 instanceof Error ? err3.message : String(err3);
33494
- log({
33495
- event: "deliverable_file_download_failed",
33496
- level: "warn",
33497
- requestId,
33498
- deliverableId,
33499
- error: reason
33500
- });
33501
- rawSend(JSON.stringify({ type: "download-error", requestId, error: "" }));
33502
- }
33503
- }
33504
- dc.onmessage = (event) => {
33505
- if (typeof event.data === "string") {
33506
- const data = event.data;
33507
- receiveQueue = receiveQueue.then(() => handleTextMessage(data)).catch((err3) => {
33508
- log({
33509
- event: "asset_channel_handler_error",
33510
- error: err3 instanceof Error ? err3.message : String(err3)
33511
- });
33512
- });
33513
- }
33514
- };
33515
- dc.onclose = () => {
33516
- for (const upload of pendingUploads.values()) clearTimeout(upload.timer);
33517
- pendingUploads.clear();
33518
- downloadShells.clear();
33519
- guardedText.dispose();
33520
- receiveQueue = Promise.resolve();
33521
- };
33522
- return {
33523
- dispose() {
33524
- dc.onmessage = null;
33525
- dc.onclose = null;
33526
- for (const upload of pendingUploads.values()) clearTimeout(upload.timer);
33527
- pendingUploads.clear();
33528
- downloadShells.clear();
33529
- guardedText.dispose();
33530
- receiveQueue = Promise.resolve();
33531
- }
33532
- };
33533
- }
33534
-
33535
- // src/services/collab/collab-repo-manager.ts
33536
- var BRIDGE_EPHEMERAL_HOPS = 1;
33537
- var DAEMON_IDENTITY = { name: "shipyard-daemon", type: "service" };
33538
- function restoreEphemeralHops(message) {
33539
- switch (message.type) {
33540
- case "channel/ephemeral":
33541
- if (message.hopsRemaining >= BRIDGE_EPHEMERAL_HOPS) return message;
33542
- return { ...message, hopsRemaining: BRIDGE_EPHEMERAL_HOPS };
33543
- case "channel/batch": {
33544
- let changed = false;
33545
- const nextMessages = message.messages.map((inner) => {
33546
- if (inner.type !== "channel/ephemeral") return inner;
33547
- if (inner.hopsRemaining >= BRIDGE_EPHEMERAL_HOPS) return inner;
33548
- changed = true;
33549
- return { ...inner, hopsRemaining: BRIDGE_EPHEMERAL_HOPS };
33550
- });
33551
- if (!changed) return message;
33552
- return { ...message, messages: nextMessages };
33553
- }
33554
- case "channel/establish-request":
33555
- case "channel/establish-response":
33556
- case "channel/sync-request":
33557
- case "channel/sync-response":
33558
- case "channel/update":
33559
- case "channel/directory-request":
33560
- case "channel/directory-response":
33561
- case "channel/new-doc":
33562
- case "channel/delete-request":
33563
- case "channel/delete-response":
33564
- return message;
33565
- default: {
33566
- const _exhaustive = message;
33567
- void _exhaustive;
33568
- return message;
33569
- }
33570
- }
33571
- }
33572
- var HopRestoringBridgeAdapter = class extends BridgeAdapter {
33573
- generate(context) {
33574
- const channel = super.generate(context);
33575
- const originalSend = channel.send;
33576
- return {
33577
- ...channel,
33578
- send: (msg) => originalSend(restoreEphemeralHops(msg))
33579
- };
33580
- }
33581
- };
33582
- function planCollabRoomResources(taskId, roomId, epochs) {
33583
- return {
33584
- docIds: [buildPlanDocId(taskId, epochs.plan), buildCanvasDocId(taskId, epochs.canvas)],
33585
- personalBridgeType: `bridge-personal-${roomId}`,
33586
- collabBridgeType: `bridge-collab-${roomId}`
33587
- };
33588
- }
33589
- function buildCollabRepoPermissions(taskId, peerRoleRegistry) {
33590
- return {
33591
- visibility(doc, peer) {
33592
- if (peer.channelKind === "storage") return true;
33593
- if (peer.peerType === "service") return true;
33594
- const parsed = parseDocumentId(doc.id);
33595
- if (!parsed) return false;
33596
- if (parsed.key !== taskId) return false;
33597
- const entry = peerRoleRegistry.getEntry(peer.peerId);
33598
- if (!entry) return false;
33599
- return isCollabPrefixVisible(entry.role, parsed.prefix);
33600
- },
33601
- mutability(doc, peer) {
33602
- if (peer.channelKind === "storage") return true;
33603
- if (peer.peerType === "service") return true;
33604
- const parsed = parseDocumentId(doc.id);
33605
- if (!parsed) return false;
33606
- if (parsed.key !== taskId) return false;
33607
- const entry = peerRoleRegistry.getEntry(peer.peerId);
33608
- if (!entry) return false;
33609
- return isCollabPrefixMutable(entry.role, parsed.prefix);
33610
- },
33611
- creation(_docId, _peer) {
33612
- return false;
33613
- },
33614
- deletion(_doc, _peer) {
33615
- return false;
33616
- }
33617
- };
33618
- }
33619
- function createCollabRepo(personalRepo, taskId, roomId, epochs, peerRoleRegistry) {
33620
- const resources = planCollabRoomResources(taskId, roomId, epochs);
33621
- const bridge = new Bridge();
33622
- const personalBridgeAdapter = new HopRestoringBridgeAdapter({
33623
- adapterType: resources.personalBridgeType,
33624
- bridge
33625
- });
33626
- const collabBridgeAdapter = new HopRestoringBridgeAdapter({
33627
- adapterType: resources.collabBridgeType,
33628
- bridge
33629
- });
33630
- const collabWebrtcAdapter = new WebRtcDataChannelAdapter();
33631
- const collabRepo = new Repo({
33632
- identity: { ...DAEMON_IDENTITY, name: `collab-room-${roomId}` },
33633
- adapters: [collabBridgeAdapter, collabWebrtcAdapter],
33634
- permissions: buildCollabRepoPermissions(taskId, peerRoleRegistry)
33635
- });
33636
- personalRepo.addAdapter(personalBridgeAdapter);
33637
- return {
33638
- repo: collabRepo,
33639
- webrtcAdapter: collabWebrtcAdapter,
33640
- async destroy() {
33641
- personalRepo.removeAdapter(personalBridgeAdapter.adapterId);
33642
- await collabRepo.shutdown();
33643
- }
33644
- };
33645
- }
33646
-
33647
- // src/services/collab/collab-room-manager.ts
33648
- function assertNever5(x2) {
33649
- throw new Error(`Unhandled message type: ${JSON.stringify(x2)}`);
33650
- }
33651
- function namespacePeerId(roomId, remoteUserId) {
33652
- return `collab:${roomId}:${remoteUserId}`;
33653
- }
33654
- function stripCollabRoomPrefix(roomId, namespacedId) {
33655
- const roomPrefix = `collab:${roomId}:`;
33656
- if (!namespacedId.startsWith(roomPrefix)) {
33657
- throw new Error(
33658
- `Expected collab-namespaced peer ID with prefix "${roomPrefix}", got "${namespacedId}"`
33659
- );
33660
- }
33661
- const browserMarker = ":browser:";
33662
- const browserIdx = namespacedId.lastIndexOf(browserMarker);
33663
- if (browserIdx === -1) {
33664
- throw new Error(`Expected collab peer ID to contain "${browserMarker}", got "${namespacedId}"`);
33665
- }
33666
- return namespacedId.slice(browserIdx + browserMarker.length);
33667
- }
33668
- function bumpAndNotify(room, deps) {
33669
- room.generation += 1;
33670
- deps.onRoomStateChanged?.(room.taskId);
33671
- }
33672
- function handleParticipantsList(room, participants, deps) {
33673
- if (!room.myUserId) return;
33674
- room.participants = participants.map((p2) => ({
33675
- userId: p2.userId,
33676
- username: p2.username,
33677
- avatarUrl: p2.avatarUrl,
33678
- role: p2.role,
33679
- connectionId: p2.connectionId,
33680
- isHub: p2.isHub
33681
- }));
33682
- for (const p2 of participants) {
33683
- room.connectionIdByUser.set(p2.userId, p2.connectionId);
33684
- }
33685
- bumpAndNotify(room, deps);
33686
- for (const participant of participants) {
33687
- if (participant.userId === room.myUserId) continue;
33688
- if (room.knownPeers.has(participant.userId)) continue;
33689
- registerCollabParticipant(
33690
- room,
33691
- participant.userId,
33692
- participant.role,
33693
- participant.username,
33694
- deps
33695
- );
33696
- initiateOfferToParticipant(room, participant.userId, deps);
33697
- }
33698
- }
33699
- function handleParticipantJoined(room, participant, deps) {
33700
- if (!room.myUserId) return;
33701
- if (participant.userId === room.myUserId) return;
33702
- room.connectionIdByUser.set(participant.userId, participant.connectionId);
33703
- if (room.knownPeers.has(participant.userId)) return;
33704
- room.participants = [
33705
- ...room.participants,
33706
- {
33707
- userId: participant.userId,
33708
- username: participant.username,
33709
- avatarUrl: participant.avatarUrl,
33710
- role: participant.role,
33711
- connectionId: participant.connectionId,
33712
- isHub: participant.isHub
33713
- }
33714
- ];
33715
- bumpAndNotify(room, deps);
33716
- registerCollabParticipant(room, participant.userId, participant.role, participant.username, deps);
33717
- initiateOfferToParticipant(room, participant.userId, deps);
33718
- }
33719
- function handleParticipantLeft(room, msg, deps) {
33720
- const { userId, connectionId } = msg;
33721
- const tracked = room.connectionIdByUser.get(userId);
33722
- if (tracked !== void 0 && tracked !== connectionId) {
33723
- deps.log({
33724
- event: "participant_left_stale_dropped",
33725
- roomId: room.roomId,
33726
- userId,
33727
- incomingConnectionId: connectionId,
33728
- trackedConnectionId: tracked
33729
- });
33730
- return;
33731
- }
33732
- room.knownPeers.delete(userId);
33733
- room.participants = room.participants.filter((p2) => p2.userId !== userId);
33734
- room.connectionIdByUser.delete(userId);
33735
- bumpAndNotify(room, deps);
33736
- const peerId = namespacePeerId(room.roomId, userId);
33737
- deps.peerRoleRegistry.unregisterPeer(peerId);
33738
- room.peerManager.closePeer(peerId);
33739
- deps.log({ event: "collab_participant_left", roomId: room.roomId, userId });
33740
- }
33741
- function handleWebrtcSignaling(room, type, targetUserId, generationId, payload, deps) {
33742
- const peerId = namespacePeerId(room.roomId, targetUserId);
33743
- const actions = {
33744
- // eslint-disable-next-line no-restricted-syntax -- SDP is opaque z.unknown() from signaling
33745
- offer: () => room.peerManager.handleOffer(peerId, payload, generationId),
33746
- // eslint-disable-next-line no-restricted-syntax -- SDP is opaque z.unknown() from signaling
33747
- answer: () => room.peerManager.handleAnswer(peerId, payload, generationId),
33748
- // eslint-disable-next-line no-restricted-syntax -- ICE candidate is opaque z.unknown() from signaling
33749
- ice: () => room.peerManager.handleIce(peerId, payload, generationId)
33750
- };
33751
- actions[type]().catch((err3) => {
33752
- deps.log({
33753
- event: `collab_${type}_failed`,
33754
- roomId: room.roomId,
33755
- generationId,
33756
- error: String(err3)
33757
- });
33758
- });
33759
- }
33760
- function handleCollabRoomMessage(room, msg, deps) {
33761
- switch (msg.type) {
33762
- case "authenticated":
33763
- room.myUserId = msg.userId;
33764
- deps.log({ event: "collab_authenticated", roomId: room.roomId, userId: msg.userId });
33765
- break;
33766
- case "participants-list":
33767
- handleParticipantsList(room, msg.participants, deps);
33768
- break;
33769
- case "participant-joined":
33770
- handleParticipantJoined(room, msg.participant, deps);
33771
- break;
33772
- case "participant-left":
33773
- handleParticipantLeft(room, { userId: msg.userId, connectionId: msg.connectionId }, deps);
33774
- break;
33775
- case "webrtc-offer":
33776
- handleWebrtcSignaling(room, "offer", msg.targetUserId, msg.generationId, msg.offer, deps);
33777
- break;
33778
- case "webrtc-answer":
33779
- handleWebrtcSignaling(room, "answer", msg.targetUserId, msg.generationId, msg.answer, deps);
33780
- break;
33781
- case "webrtc-ice":
33782
- handleWebrtcSignaling(room, "ice", msg.targetUserId, msg.generationId, msg.candidate, deps);
33783
- break;
33784
- case "ice-servers":
33785
- room.peerManager.updateIceServers(msg.iceServers);
33786
- deps.log({ event: "collab_ice_servers", roomId: room.roomId, count: msg.iceServers.length });
33787
- break;
33788
- case "error":
33789
- deps.log({ event: "collab_room_error", roomId: room.roomId, message: msg.message });
33790
- break;
33791
- default:
33792
- assertNever5(msg);
33793
- }
33794
- }
33795
- function registerCollabParticipant(room, userId, role, displayName, deps) {
33796
- const peerId = namespacePeerId(room.roomId, userId);
33797
- deps.peerRoleRegistry.registerCollabPeer(peerId, role, userId, displayName, room.taskId);
33798
- }
33799
- function initiateOfferToParticipant(room, remoteUserId, deps) {
33800
- if (!room.myUserId) return;
33801
- room.knownPeers.add(remoteUserId);
33802
- deps.log({ event: "collab_initiating_offer", roomId: room.roomId, target: remoteUserId });
33803
- const peerId = namespacePeerId(room.roomId, remoteUserId);
33804
- room.peerManager.initiateOffer(peerId).catch((err3) => {
33805
- deps.log({ event: "collab_offer_initiate_failed", roomId: room.roomId, error: String(err3) });
33806
- });
33807
- }
33808
- function createCollabRoomManager(deps) {
33809
- const rooms = /* @__PURE__ */ new Map();
33810
- async function leave(roomId) {
33811
- const room = rooms.get(roomId);
33812
- if (!room) return;
33813
- deps.log({ event: "collab_leaving", roomId });
33814
- clearTimeout(room.expiryTimer);
33815
- room.unsubMessage();
33816
- room.unsubState();
33817
- for (const userId of room.knownPeers) {
33818
- deps.peerRoleRegistry.unregisterPeer(namespacePeerId(roomId, userId));
33819
- }
33820
- const destroyPromise = room.collabRepoHandle?.destroy();
33821
- room.peerManager.destroy();
33822
- room.connection.disconnect();
33823
- const leftTaskId = room.taskId;
33824
- rooms.delete(roomId);
33825
- deps.onRoomStateChanged?.(leftTaskId);
33826
- if (destroyPromise) {
33827
- await destroyPromise.catch((err3) => {
33828
- deps.log({
33829
- event: "collab_repo_destroy_failed",
33830
- roomId,
33831
- error: err3 instanceof Error ? err3.message : String(err3)
33832
- });
33833
- });
33834
- }
33835
- }
33836
- return {
33837
- joinRoom(config) {
33838
- const { roomId, taskId, token, expiresAt, signalingBaseUrl, userToken, machineId } = config;
33839
- const existing = rooms.get(roomId);
33840
- if (existing) {
33841
- const existingState = existing.connection.getState();
33842
- if (existing.taskId === taskId && (existingState === "connected" || existingState === "connecting")) {
33843
- deps.log({ event: "collab_join_dedup", roomId, state: existingState });
33844
- return {
33845
- roomId,
33846
- taskId,
33847
- async destroy() {
33848
- await leave(roomId);
33849
- }
33850
- };
33851
- }
33852
- deps.log({ event: "collab_replacing", roomId });
33853
- leave(roomId).catch((err3) => {
33854
- deps.log({
33855
- event: "collab_replace_leave_failed",
33856
- roomId,
33857
- error: err3 instanceof Error ? err3.message : String(err3)
33858
- });
33859
- });
33860
- }
33861
- const wsUrl = new URL(signalingBaseUrl);
33862
- wsUrl.pathname = ROUTES.WS_COLLAB.replace(":roomId", roomId);
33863
- wsUrl.searchParams.set("token", token);
33864
- wsUrl.searchParams.set("userToken", userToken);
33865
- wsUrl.searchParams.set("clientType", "agent");
33866
- wsUrl.searchParams.set("machineId", machineId);
33867
- const connection = new CollabRoomConnection({
33868
- url: wsUrl.toString(),
33869
- maxRetries: -1,
33870
- initialDelayMs: 1e3,
33871
- maxDelayMs: 3e4,
33872
- backoffMultiplier: 2
34354
+ requestId,
34355
+ error: err3 instanceof Error ? err3.message : String(err3)
33873
34356
  });
33874
- const collabRepoHandle = createCollabRepo(
33875
- deps.personalRepo,
33876
- taskId,
33877
- roomId,
33878
- { plan: deps.planEpoch, canvas: deps.canvasEpoch },
33879
- deps.peerRoleRegistry
34357
+ rawSend(
34358
+ JSON.stringify({
34359
+ type: "upload-error",
34360
+ requestId,
34361
+ error: err3 instanceof Error ? err3.message : "Upload failed"
34362
+ })
33880
34363
  );
33881
- const peerManager = deps.createPeerManagerForRoom(
33882
- roomId,
33883
- connection,
33884
- taskId,
33885
- collabRepoHandle.webrtcAdapter
34364
+ });
34365
+ }
34366
+ async function handleDownloadRequest(requestId, assetId) {
34367
+ try {
34368
+ const { data, meta } = await assetStore.read(assetId);
34369
+ rawSend(
34370
+ JSON.stringify({
34371
+ type: "download-head",
34372
+ requestId,
34373
+ mimeType: meta.mimeType,
34374
+ totalBytes: data.byteLength
34375
+ })
33886
34376
  );
33887
- const handle = {
33888
- roomId,
33889
- taskId,
33890
- async destroy() {
33891
- await leave(roomId);
33892
- }
33893
- };
33894
- const delayMs = expiresAt - Date.now();
33895
- if (delayMs <= 0 || !Number.isFinite(delayMs)) {
33896
- deps.log({ event: "collab_expired", roomId, expiresAt });
33897
- peerManager.destroy();
33898
- collabRepoHandle.destroy().catch((err3) => {
33899
- deps.log({
33900
- event: "collab_repo_destroy_failed",
33901
- roomId,
33902
- error: err3 instanceof Error ? err3.message : String(err3)
33903
- });
34377
+ const shell = createDownloadShell(requestId);
34378
+ downloadShells.set(requestId, shell);
34379
+ await shell.initialize();
34380
+ await shell.setConnected(true);
34381
+ const bytes = new Uint8Array(data);
34382
+ let chunkIndex = 0;
34383
+ let yieldCounter = 0;
34384
+ for (let offset = 0; offset < bytes.byteLength; offset += ASSET_CHUNK_SIZE) {
34385
+ const end = Math.min(offset + ASSET_CHUNK_SIZE, bytes.byteLength);
34386
+ const chunk = bytes.subarray(offset, end);
34387
+ await shell.send({
34388
+ kind: "download-chunk",
34389
+ requestId,
34390
+ chunkIndex,
34391
+ data: encodeChunk(chunk)
33904
34392
  });
33905
- return handle;
34393
+ chunkIndex++;
34394
+ yieldCounter++;
34395
+ if (yieldCounter % YIELD_EVERY_CHUNKS === 0) {
34396
+ await new Promise((r) => setTimeout(r, 0));
34397
+ }
33906
34398
  }
33907
- const safeDelayMs = Math.min(delayMs, 2147483647);
33908
- const room = {
33909
- roomId,
34399
+ rawSend(JSON.stringify({ type: "download-done", requestId }));
34400
+ log({ event: "asset_download_complete", requestId, assetId, size: data.byteLength });
34401
+ setTimeout(() => downloadShells.delete(requestId), UPLOAD_TIMEOUT_MS);
34402
+ } catch (err3) {
34403
+ downloadShells.delete(requestId);
34404
+ const { event, reason } = classifyDownloadError(assetId, err3);
34405
+ log({ event, requestId, assetId, error: reason });
34406
+ rawSend(
34407
+ JSON.stringify({
34408
+ type: "download-error",
34409
+ requestId,
34410
+ error: err3 instanceof Error ? err3.message : "Download failed"
34411
+ })
34412
+ );
34413
+ }
34414
+ }
34415
+ async function handleDeliverableFileRequest(requestId, taskId, deliverableId) {
34416
+ const resolved = await resolveDeliverableFile(deliverableStore, taskId, deliverableId);
34417
+ if (resolved.kind === "error") {
34418
+ log({
34419
+ event: resolved.event,
34420
+ requestId,
33910
34421
  taskId,
33911
- connection,
33912
- peerManager,
33913
- collabRepoHandle,
33914
- myUserId: null,
33915
- knownPeers: /* @__PURE__ */ new Set(),
33916
- participants: [],
33917
- connectionIdByUser: /* @__PURE__ */ new Map(),
33918
- expiresAt,
33919
- generation: 0,
33920
- expiryTimer: setTimeout(() => {
33921
- deps.log({ event: "collab_token_expired", roomId });
33922
- leave(roomId).catch((err3) => {
33923
- deps.log({
33924
- event: "collab_expiry_leave_failed",
33925
- roomId,
33926
- error: err3 instanceof Error ? err3.message : String(err3)
33927
- });
33928
- });
33929
- }, safeDelayMs),
33930
- unsubMessage: () => {
33931
- },
33932
- unsubState: () => {
33933
- }
33934
- };
33935
- rooms.set(roomId, room);
33936
- room.unsubMessage = connection.onMessage((msg) => {
33937
- handleCollabRoomMessage(room, msg, deps);
34422
+ deliverableId,
34423
+ ...resolved.logExtras
33938
34424
  });
33939
- room.unsubState = connection.onStateChange((state) => {
33940
- deps.log({ event: "collab_state_change", roomId, state });
33941
- if (state === "disconnected" || state === "error") {
33942
- room.peerManager.destroy();
33943
- room.peerManager = deps.createPeerManagerForRoom(
33944
- roomId,
33945
- connection,
33946
- taskId,
33947
- room.collabRepoHandle?.webrtcAdapter ?? collabRepoHandle.webrtcAdapter
33948
- );
33949
- room.myUserId = null;
33950
- room.knownPeers.clear();
33951
- room.connectionIdByUser.clear();
33952
- }
34425
+ rawSend(JSON.stringify({ type: "download-error", requestId, error: "" }));
34426
+ return;
34427
+ }
34428
+ if (allowedTaskId !== null && resolved.recordTaskId !== allowedTaskId) {
34429
+ log({
34430
+ event: "deliverable_file_task_scope_mismatch",
34431
+ level: "warn",
34432
+ requestId,
34433
+ allowedTaskId,
34434
+ requestedTaskId: taskId
33953
34435
  });
33954
- connection.connect();
33955
- deps.log({ event: "collab_joining", roomId, taskId });
33956
- bumpAndNotify(room, deps);
33957
- return handle;
33958
- },
33959
- async leaveRoom(roomId) {
33960
- await leave(roomId);
33961
- },
33962
- getParticipantsForTask(taskId) {
33963
- for (const room of rooms.values()) {
33964
- if (room.taskId === taskId) {
33965
- return room.participants.filter((p2) => p2.userId !== room.myUserId).map((p2) => ({ name: p2.username, role: p2.role }));
34436
+ rawSend(JSON.stringify({ type: "download-error", requestId, error: "" }));
34437
+ return;
34438
+ }
34439
+ const { filePath, mimeType } = resolved;
34440
+ try {
34441
+ const data = await fsReadFile(filePath);
34442
+ rawSend(
34443
+ JSON.stringify({
34444
+ type: "download-head",
34445
+ requestId,
34446
+ mimeType,
34447
+ totalBytes: data.byteLength
34448
+ })
34449
+ );
34450
+ const shell = createDownloadShell(requestId);
34451
+ downloadShells.set(requestId, shell);
34452
+ await shell.initialize();
34453
+ await shell.setConnected(true);
34454
+ const bytes = new Uint8Array(data);
34455
+ let chunkIndex = 0;
34456
+ let yieldCounter = 0;
34457
+ for (let offset = 0; offset < bytes.byteLength; offset += ASSET_CHUNK_SIZE) {
34458
+ const end = Math.min(offset + ASSET_CHUNK_SIZE, bytes.byteLength);
34459
+ const chunk = bytes.subarray(offset, end);
34460
+ await shell.send({
34461
+ kind: "download-chunk",
34462
+ requestId,
34463
+ chunkIndex,
34464
+ data: encodeChunk(chunk)
34465
+ });
34466
+ chunkIndex++;
34467
+ yieldCounter++;
34468
+ if (yieldCounter % YIELD_EVERY_CHUNKS === 0) {
34469
+ await new Promise((r) => setTimeout(r, 0));
33966
34470
  }
33967
34471
  }
33968
- return [];
33969
- },
33970
- listActiveTaskIds() {
33971
- const ids = [];
33972
- for (const room of rooms.values()) ids.push(room.taskId);
33973
- return ids;
33974
- },
33975
- getStateForTask(taskId) {
33976
- for (const room of rooms.values()) {
33977
- if (room.taskId !== taskId) continue;
33978
- const owner = room.participants.find((p2) => p2.role === "owner");
33979
- return {
33980
- roomId: room.roomId,
33981
- taskId: room.taskId,
33982
- ownerId: owner?.userId ?? "",
33983
- participants: room.participants,
33984
- expiresAt: room.expiresAt,
33985
- generation: room.generation
33986
- };
33987
- }
33988
- return null;
33989
- },
33990
- async destroy() {
33991
- await Promise.all([...rooms.keys()].map((roomId) => leave(roomId)));
34472
+ rawSend(JSON.stringify({ type: "download-done", requestId }));
34473
+ log({
34474
+ event: "deliverable_file_download_complete",
34475
+ requestId,
34476
+ deliverableId,
34477
+ size: data.byteLength
34478
+ });
34479
+ setTimeout(() => downloadShells.delete(requestId), UPLOAD_TIMEOUT_MS);
34480
+ } catch (err3) {
34481
+ downloadShells.delete(requestId);
34482
+ const reason = err3 instanceof Error ? err3.message : String(err3);
34483
+ log({
34484
+ event: "deliverable_file_download_failed",
34485
+ level: "warn",
34486
+ requestId,
34487
+ deliverableId,
34488
+ error: reason
34489
+ });
34490
+ rawSend(JSON.stringify({ type: "download-error", requestId, error: "" }));
34491
+ }
34492
+ }
34493
+ dc.onmessage = (event) => {
34494
+ if (typeof event.data === "string") {
34495
+ const data = event.data;
34496
+ receiveQueue = receiveQueue.then(() => handleTextMessage(data)).catch((err3) => {
34497
+ log({
34498
+ event: "asset_channel_handler_error",
34499
+ error: err3 instanceof Error ? err3.message : String(err3)
34500
+ });
34501
+ });
34502
+ }
34503
+ };
34504
+ dc.onclose = () => {
34505
+ for (const upload of pendingUploads.values()) clearTimeout(upload.timer);
34506
+ pendingUploads.clear();
34507
+ downloadShells.clear();
34508
+ guardedText.dispose();
34509
+ receiveQueue = Promise.resolve();
34510
+ };
34511
+ return {
34512
+ dispose() {
34513
+ dc.onmessage = null;
34514
+ dc.onclose = null;
34515
+ for (const upload of pendingUploads.values()) clearTimeout(upload.timer);
34516
+ pendingUploads.clear();
34517
+ downloadShells.clear();
34518
+ guardedText.dispose();
34519
+ receiveQueue = Promise.resolve();
33992
34520
  }
33993
34521
  };
33994
34522
  }
@@ -34333,19 +34861,19 @@ function wireThreadErrorFallback(daemon, dc, taskId, threadId, channelId, log) {
34333
34861
  }
34334
34862
 
34335
34863
  // src/services/terminal-handler.ts
34336
- import { join as join39 } from "path";
34864
+ import { join as join40 } from "path";
34337
34865
 
34338
34866
  // src/shared/pty-manager.ts
34339
34867
  import { accessSync, chmodSync, constants as constants2, createWriteStream } from "fs";
34340
34868
  import { rename as rename13, unlink as unlink8 } from "fs/promises";
34341
34869
  import { createRequire as createRequire2 } from "module";
34342
- import { dirname as dirname19, resolve as resolve7 } from "path";
34870
+ import { dirname as dirname20, resolve as resolve7 } from "path";
34343
34871
  import * as pty from "node-pty";
34344
34872
  function ensureSpawnHelperExecutable() {
34345
34873
  if (globalThis.process.platform === "win32") return;
34346
34874
  try {
34347
34875
  const req = createRequire2(import.meta.url);
34348
- const nodePtyDir = dirname19(req.resolve("node-pty/package.json"));
34876
+ const nodePtyDir = dirname20(req.resolve("node-pty/package.json"));
34349
34877
  const spawnHelper = resolve7(
34350
34878
  nodePtyDir,
34351
34879
  "prebuilds",
@@ -34614,7 +35142,7 @@ function createPtyManager() {
34614
35142
  // src/services/terminal-handler.ts
34615
35143
  var MAX_PTYS_DEFAULT = 20;
34616
35144
  function terminalLogPathFor(dir, terminalId) {
34617
- return join39(dir, `${terminalId}.log`);
35145
+ return join40(dir, `${terminalId}.log`);
34618
35146
  }
34619
35147
  function handleTerminalChannel(taskId, terminalId, cwd, send, log, deps) {
34620
35148
  const maxPtys = deps.maxPtys ?? MAX_PTYS_DEFAULT;
@@ -34814,13 +35342,15 @@ function buildCollabRoomManager(deps) {
34814
35342
  fileWatcherPool,
34815
35343
  terminalPtys,
34816
35344
  terminalLogsDir,
34817
- housekeepingBarrier
35345
+ housekeepingBarrier,
35346
+ collabHostingStore
34818
35347
  } = deps;
34819
35348
  let managerRef = null;
34820
35349
  const emitCollabRoomState = (taskId) => {
34821
35350
  const mgr2 = managerRef;
34822
35351
  if (!mgr2) return;
34823
35352
  const state = mgr2.getStateForTask(taskId);
35353
+ if (!shouldBroadcastRoomState(state)) return;
34824
35354
  daemon.taskManager.broadcastControl({
34825
35355
  type: "collab_room_state",
34826
35356
  taskId,
@@ -35216,6 +35746,7 @@ function buildCollabRoomManager(deps) {
35216
35746
  onRoomStateChanged: (taskId) => {
35217
35747
  emitCollabRoomState(taskId);
35218
35748
  },
35749
+ collabHostingStore,
35219
35750
  log: logAdapter
35220
35751
  });
35221
35752
  managerRef = mgr;
@@ -35225,7 +35756,7 @@ function buildCollabRoomManager(deps) {
35225
35756
  // src/services/serve-factory.ts
35226
35757
  import { randomUUID as randomUUID25 } from "crypto";
35227
35758
  import { mkdir as mkdir36 } from "fs/promises";
35228
- import { join as join78 } from "path";
35759
+ import { join as join79 } from "path";
35229
35760
 
35230
35761
  // src/shared/capabilities/cursor-boot-auth.ts
35231
35762
  async function mergeCursorAuthFromVault(caps, detect, log) {
@@ -35266,12 +35797,12 @@ async function mergeCursorAuthFromVault(caps, detect, log) {
35266
35797
 
35267
35798
  // src/shared/capabilities/cursor-hook-shim-path.ts
35268
35799
  import { statSync as statSync4 } from "fs";
35269
- import { join as join40 } from "path";
35800
+ import { join as join41 } from "path";
35270
35801
  import { fileURLToPath as fileURLToPath2 } from "url";
35271
35802
  function getCursorHookShimPath() {
35272
35803
  const resourcesPath = Reflect.get(process, "resourcesPath");
35273
35804
  if (typeof resourcesPath === "string" && resourcesPath.length > 0) {
35274
- return join40(resourcesPath, "daemon", "cursor-hook-shim.js");
35805
+ return join41(resourcesPath, "daemon", "cursor-hook-shim.js");
35275
35806
  }
35276
35807
  return fileURLToPath2(new URL("./cursor-hook-shim.js", import.meta.url));
35277
35808
  }
@@ -35504,9 +36035,9 @@ function resolveRuntimeAuthIdentity(runtimeAuth, runtimeId) {
35504
36035
  }
35505
36036
 
35506
36037
  // src/shared/capabilities/spinner-verbs-reader.ts
35507
- import { readFile as readFile21 } from "fs/promises";
36038
+ import { readFile as readFile22 } from "fs/promises";
35508
36039
  import { homedir as homedir6 } from "os";
35509
- import { join as join41 } from "path";
36040
+ import { join as join42 } from "path";
35510
36041
  var ClaudeSpinnerVerbsSchema = external_exports.object({
35511
36042
  mode: external_exports.enum(["append", "replace"]),
35512
36043
  verbs: external_exports.array(external_exports.string())
@@ -35531,12 +36062,12 @@ function toPersistedVerbs(resolved, defaults) {
35531
36062
  return resolved.every((v2, i) => v2 === defaults[i]) ? [] : [...resolved];
35532
36063
  }
35533
36064
  async function readSpinnerVerbs() {
35534
- const claudeDir = join41(homedir6(), ".claude");
35535
- const paths = [join41(claudeDir, "settings.json"), join41(claudeDir, "settings.local.json")];
36065
+ const claudeDir = join42(homedir6(), ".claude");
36066
+ const paths = [join42(claudeDir, "settings.json"), join42(claudeDir, "settings.local.json")];
35536
36067
  const parsed = await Promise.all(
35537
36068
  paths.map(async (p2) => {
35538
36069
  try {
35539
- const raw = await readFile21(p2, "utf-8");
36070
+ const raw = await readFile22(p2, "utf-8");
35540
36071
  const json = JSON.parse(raw);
35541
36072
  if (json === null || typeof json !== "object") return null;
35542
36073
  const field2 = Reflect.get(json, "spinnerVerbs");
@@ -35573,8 +36104,8 @@ async function reimportSpinnerVerbs(store, log) {
35573
36104
  }
35574
36105
 
35575
36106
  // src/shared/mcp/oauth-state-store.ts
35576
- import { readFile as readFile22 } from "fs/promises";
35577
- import { join as join42 } from "path";
36107
+ import { readFile as readFile23 } from "fs/promises";
36108
+ import { join as join43 } from "path";
35578
36109
  import {
35579
36110
  OAuthClientInformationFullSchema as OAuthClientInformationFullSchema2,
35580
36111
  OAuthClientInformationSchema as OAuthClientInformationSchema2,
@@ -35620,7 +36151,7 @@ var MCPOAuthStateStore = class {
35620
36151
  this.#shipyardHome = shipyardHome;
35621
36152
  }
35622
36153
  #filePath() {
35623
- return join42(this.#shipyardHome, STATE_FILE);
36154
+ return join43(this.#shipyardHome, STATE_FILE);
35624
36155
  }
35625
36156
  #lockPath() {
35626
36157
  return `${this.#filePath()}.lock`;
@@ -35628,7 +36159,7 @@ var MCPOAuthStateStore = class {
35628
36159
  /** Always reads the state map from disk (never the cache). */
35629
36160
  async #readFreshMap() {
35630
36161
  try {
35631
- const raw = await readFile22(this.#filePath(), "utf-8");
36162
+ const raw = await readFile23(this.#filePath(), "utf-8");
35632
36163
  const parsed = StateMapSchema.safeParse(JSON.parse(raw));
35633
36164
  return parsed.success ? parsed.data : {};
35634
36165
  } catch {
@@ -35718,12 +36249,12 @@ function getShipyardSubagentCatalog() {
35718
36249
  }
35719
36250
 
35720
36251
  // src/services/bootstrap/rehydrate.ts
35721
- import { readFile as readFile24, rename as rename15, writeFile as writeFile16 } from "fs/promises";
35722
- import { join as join44 } from "path";
36252
+ import { readFile as readFile25, rename as rename15, writeFile as writeFile16 } from "fs/promises";
36253
+ import { join as join45 } from "path";
35723
36254
 
35724
36255
  // src/services/collab/collab-queue-persistence.ts
35725
- import { appendFile as appendFile2, mkdir as mkdir15, readdir as readdir15, readFile as readFile23, rename as rename14, rm as rm9, writeFile as writeFile15 } from "fs/promises";
35726
- import { join as join43 } from "path";
36256
+ import { appendFile as appendFile2, mkdir as mkdir16, readdir as readdir15, readFile as readFile24, rename as rename14, rm as rm9, writeFile as writeFile15 } from "fs/promises";
36257
+ import { join as join44 } from "path";
35727
36258
  var PersistedQueueEntrySchema = external_exports.object({
35728
36259
  content: external_exports.array(ContentBlockSchema),
35729
36260
  settings: DaemonSettingsSchema.optional(),
@@ -35747,12 +36278,12 @@ function parsePersistedLine(line) {
35747
36278
  };
35748
36279
  }
35749
36280
  function buildCollabQueuePersistence(dataDir) {
35750
- const queuesDir = join43(dataDir, "collab-queues");
36281
+ const queuesDir = join44(dataDir, "collab-queues");
35751
36282
  function queuePath(queueKey) {
35752
- return join43(queuesDir, `${queueKey}.jsonl`);
36283
+ return join44(queuesDir, `${queueKey}.jsonl`);
35753
36284
  }
35754
36285
  async function ensureDir() {
35755
- await mkdir15(queuesDir, { recursive: true });
36286
+ await mkdir16(queuesDir, { recursive: true });
35756
36287
  }
35757
36288
  const writeChain = /* @__PURE__ */ new Map();
35758
36289
  async function chained(queueKey, work) {
@@ -35777,7 +36308,7 @@ function buildCollabQueuePersistence(dataDir) {
35777
36308
  async read(queueKey) {
35778
36309
  let raw;
35779
36310
  try {
35780
- raw = await readFile23(queuePath(queueKey), "utf-8");
36311
+ raw = await readFile24(queuePath(queueKey), "utf-8");
35781
36312
  } catch (err3) {
35782
36313
  if (isEnoent(err3)) return [];
35783
36314
  throw err3;
@@ -35842,7 +36373,7 @@ function buildCollabQueuePersistence(dataDir) {
35842
36373
  }
35843
36374
  for (const name of names) {
35844
36375
  if (!name.includes(".tmp-")) continue;
35845
- await rm9(join43(queuesDir, name), { force: true }).catch(() => {
36376
+ await rm9(join44(queuesDir, name), { force: true }).catch(() => {
35846
36377
  });
35847
36378
  }
35848
36379
  }
@@ -36104,11 +36635,11 @@ async function readRawTasksEnvelope(tasksPath) {
36104
36635
  let raw;
36105
36636
  let fromMigrated = false;
36106
36637
  try {
36107
- raw = await readFile24(tasksPath, "utf-8");
36638
+ raw = await readFile25(tasksPath, "utf-8");
36108
36639
  } catch (err3) {
36109
36640
  if (!isEnoent(err3)) throw err3;
36110
36641
  try {
36111
- raw = await readFile24(`${tasksPath}.migrated`, "utf-8");
36642
+ raw = await readFile25(`${tasksPath}.migrated`, "utf-8");
36112
36643
  fromMigrated = true;
36113
36644
  } catch (err22) {
36114
36645
  if (isEnoent(err22)) return null;
@@ -36161,7 +36692,7 @@ async function stripPinnedFieldFromTasksFile(tasksPath, envelope, records) {
36161
36692
  }
36162
36693
  }
36163
36694
  async function migratePinnedToFavoriteTasks(dataDir, userSettingsStore, log) {
36164
- const tasksPath = join44(dataDir, "tasks.json");
36695
+ const tasksPath = join45(dataDir, "tasks.json");
36165
36696
  const parsed = await readRawTasksEnvelope(tasksPath);
36166
36697
  if (!parsed) return;
36167
36698
  const { records, envelope, fromMigrated } = parsed;
@@ -36276,8 +36807,8 @@ async function sweepStaleTasks(taskStateStore, taskManager, log) {
36276
36807
  }
36277
36808
 
36278
36809
  // src/services/bootstrap/update-status.ts
36279
- import { readFile as readFile25, unlink as unlink9 } from "fs/promises";
36280
- import { join as join45 } from "path";
36810
+ import { readFile as readFile26, unlink as unlink9 } from "fs/promises";
36811
+ import { join as join46 } from "path";
36281
36812
  var UPDATE_STATUS_FILENAME = "update-status.json";
36282
36813
  var RAW_LOG_MAX_CHARS = 500;
36283
36814
  var UpdateStatusSchema = external_exports.object({
@@ -36290,10 +36821,10 @@ var UpdateStatusSchema = external_exports.object({
36290
36821
  rolledBack: external_exports.boolean()
36291
36822
  });
36292
36823
  async function readAndClearUpdateStatus(shipyardHome, log) {
36293
- const path5 = join45(shipyardHome, UPDATE_STATUS_FILENAME);
36824
+ const path5 = join46(shipyardHome, UPDATE_STATUS_FILENAME);
36294
36825
  let raw;
36295
36826
  try {
36296
- raw = await readFile25(path5, "utf-8");
36827
+ raw = await readFile26(path5, "utf-8");
36297
36828
  } catch (err3) {
36298
36829
  if (isEnoent(err3)) return null;
36299
36830
  log.warn({ err: err3, path: path5 }, "failed to read update-status.json");
@@ -36377,11 +36908,11 @@ function wireUpdateStatusForwarding(report, sink2, onMessage, log) {
36377
36908
 
36378
36909
  // src/services/bootstrap/updates-cleanup.ts
36379
36910
  import { readdir as readdir16, rm as rm10, stat as stat13, unlink as unlink11 } from "fs/promises";
36380
- import { join as join47 } from "path";
36911
+ import { join as join48 } from "path";
36381
36912
 
36382
36913
  // src/services/bootstrap/self-update-lock.ts
36383
- import { mkdir as mkdir16, readFile as readFile26, stat as stat12, unlink as unlink10, writeFile as writeFile17 } from "fs/promises";
36384
- import { dirname as dirname20, join as join46 } from "path";
36914
+ import { mkdir as mkdir17, readFile as readFile27, stat as stat12, unlink as unlink10, writeFile as writeFile17 } from "fs/promises";
36915
+ import { dirname as dirname21, join as join47 } from "path";
36385
36916
  var LOCK_FILENAME = ".lock";
36386
36917
  var STALE_LOCK_MS = 10 * 60 * 1e3;
36387
36918
  var LockFileSchema = external_exports.object({
@@ -36389,7 +36920,7 @@ var LockFileSchema = external_exports.object({
36389
36920
  startedAt: external_exports.number()
36390
36921
  });
36391
36922
  function lockPath(shipyardHome) {
36392
- return join46(shipyardHome, "updates", LOCK_FILENAME);
36923
+ return join47(shipyardHome, "updates", LOCK_FILENAME);
36393
36924
  }
36394
36925
  function isStaleLock(lock, now, isProcessAlive2) {
36395
36926
  const age = now - lock.startedAt;
@@ -36401,7 +36932,7 @@ async function readLockFile(shipyardHome) {
36401
36932
  const path5 = lockPath(shipyardHome);
36402
36933
  let raw;
36403
36934
  try {
36404
- raw = await readFile26(path5, "utf-8");
36935
+ raw = await readFile27(path5, "utf-8");
36405
36936
  } catch {
36406
36937
  return null;
36407
36938
  }
@@ -36418,7 +36949,7 @@ async function readLockFileWithOutcome(shipyardHome) {
36418
36949
  const path5 = lockPath(shipyardHome);
36419
36950
  let raw;
36420
36951
  try {
36421
- raw = await readFile26(path5, "utf-8");
36952
+ raw = await readFile27(path5, "utf-8");
36422
36953
  } catch (err3) {
36423
36954
  if (isEnoent(err3)) return { kind: "absent" };
36424
36955
  return { kind: "read-error", err: err3 };
@@ -36436,7 +36967,7 @@ async function readLockFileWithOutcome(shipyardHome) {
36436
36967
  async function unlinkStaleLock(path5, expected) {
36437
36968
  let current;
36438
36969
  try {
36439
- const raw = await readFile26(path5, "utf-8");
36970
+ const raw = await readFile27(path5, "utf-8");
36440
36971
  const parsed = LockFileSchema.safeParse(JSON.parse(raw));
36441
36972
  current = parsed.success ? parsed.data : null;
36442
36973
  } catch (err3) {
@@ -36473,7 +37004,7 @@ async function resolveEexist(shipyardHome, path5, ownerPid, now, isProcessAlive2
36473
37004
  }
36474
37005
  async function tryAcquireLockExclusive(shipyardHome, pid, now, isProcessAlive2) {
36475
37006
  const path5 = lockPath(shipyardHome);
36476
- await mkdir16(dirname20(path5), { recursive: true });
37007
+ await mkdir17(dirname21(path5), { recursive: true });
36477
37008
  const body = JSON.stringify({ pid, startedAt: now() });
36478
37009
  while (true) {
36479
37010
  try {
@@ -36512,7 +37043,7 @@ async function cleanupUpdatesDir(shipyardHome, deps = {}) {
36512
37043
  const now = deps.now ?? Date.now;
36513
37044
  const isProcessAlive2 = deps.isProcessAlive ?? defaultIsProcessAlive;
36514
37045
  const log = deps.log;
36515
- const updatesDir = join47(shipyardHome, "updates");
37046
+ const updatesDir = join48(shipyardHome, "updates");
36516
37047
  const empty = {
36517
37048
  tarballsKept: [],
36518
37049
  tarballsDeleted: [],
@@ -36636,7 +37167,7 @@ async function sortByMtimeDesc(baseDir, names, log) {
36636
37167
  const rows = [];
36637
37168
  for (const name of names) {
36638
37169
  try {
36639
- const s2 = await stat13(join47(baseDir, name));
37170
+ const s2 = await stat13(join48(baseDir, name));
36640
37171
  rows.push({ name, mtimeMs: s2.mtimeMs });
36641
37172
  } catch (err3) {
36642
37173
  log?.info({ err: err3, entry: name }, "updates cleanup: stat failed, skipping entry");
@@ -36649,7 +37180,7 @@ async function filterOlderThan(baseDir, names, nowMs, thresholdMs, log) {
36649
37180
  const out = [];
36650
37181
  for (const name of names) {
36651
37182
  try {
36652
- const s2 = await stat13(join47(baseDir, name));
37183
+ const s2 = await stat13(join48(baseDir, name));
36653
37184
  if (nowMs - s2.mtimeMs > thresholdMs) {
36654
37185
  out.push(name);
36655
37186
  }
@@ -36663,7 +37194,7 @@ async function deleteFiles(baseDir, names, log) {
36663
37194
  const deleted = [];
36664
37195
  for (const name of names) {
36665
37196
  try {
36666
- await unlink11(join47(baseDir, name));
37197
+ await unlink11(join48(baseDir, name));
36667
37198
  deleted.push(name);
36668
37199
  } catch (err3) {
36669
37200
  if (isEnoent(err3)) {
@@ -36679,7 +37210,7 @@ async function deleteDirs(baseDir, names, log) {
36679
37210
  const deleted = [];
36680
37211
  for (const name of names) {
36681
37212
  try {
36682
- await rm10(join47(baseDir, name), { recursive: true, force: true });
37213
+ await rm10(join48(baseDir, name), { recursive: true, force: true });
36683
37214
  deleted.push(name);
36684
37215
  } catch (err3) {
36685
37216
  log?.info({ err: err3, entry: name }, "updates cleanup: rm -rf failed");
@@ -36688,7 +37219,7 @@ async function deleteDirs(baseDir, names, log) {
36688
37219
  return deleted;
36689
37220
  }
36690
37221
  async function maybeClearLock(shipyardHome, nowMs, isProcessAlive2, log) {
36691
- const lockFilePath = join47(shipyardHome, "updates", LOCK_FILENAME);
37222
+ const lockFilePath = join48(shipyardHome, "updates", LOCK_FILENAME);
36692
37223
  const outcome = await readLockFileWithOutcome(shipyardHome);
36693
37224
  switch (outcome.kind) {
36694
37225
  case "absent":
@@ -37992,8 +38523,8 @@ function wireDevServersAndTerminalsResolvers(deps) {
37992
38523
  }
37993
38524
 
37994
38525
  // src/services/file-resource-resolver.ts
37995
- import { readFile as readFile27, stat as stat14 } from "fs/promises";
37996
- import { basename as basename8, join as join48, normalize as normalize4, sep as sep2 } from "path";
38526
+ import { readFile as readFile28, stat as stat14 } from "fs/promises";
38527
+ import { basename as basename8, join as join49, normalize as normalize4, sep as sep2 } from "path";
37997
38528
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
37998
38529
  var MIME_BY_EXT2 = {
37999
38530
  ".ts": "text/typescript",
@@ -38062,7 +38593,7 @@ function createFileResourceResolver(deps) {
38062
38593
  const { taskId, relativePath } = parseFileUri2(uri);
38063
38594
  const cwd = deps.getTaskCwd(taskId) ?? deps.workspaceRoot;
38064
38595
  const normalizedCwd = normalize4(cwd);
38065
- const absolutePath = normalize4(join48(cwd, relativePath));
38596
+ const absolutePath = normalize4(join49(cwd, relativePath));
38066
38597
  const cwdPrefix = normalizedCwd.endsWith(sep2) ? normalizedCwd : `${normalizedCwd}${sep2}`;
38067
38598
  if (!absolutePath.startsWith(cwdPrefix) && absolutePath !== normalizedCwd) {
38068
38599
  throw new Error(`Path traversal rejected: ${relativePath}`);
@@ -38080,14 +38611,14 @@ function createFileResourceResolver(deps) {
38080
38611
  };
38081
38612
  }
38082
38613
  if (isImageExtension(absolutePath)) {
38083
- const buffer2 = await readFile27(absolutePath);
38614
+ const buffer2 = await readFile28(absolutePath);
38084
38615
  return {
38085
38616
  uri,
38086
38617
  mimeType: guessMimeType(absolutePath),
38087
38618
  blob: buffer2.toString("base64")
38088
38619
  };
38089
38620
  }
38090
- const buffer = await readFile27(absolutePath);
38621
+ const buffer = await readFile28(absolutePath);
38091
38622
  if (looksLikeBinary(buffer)) {
38092
38623
  return {
38093
38624
  uri,
@@ -38106,8 +38637,8 @@ function createFileResourceResolver(deps) {
38106
38637
 
38107
38638
  // src/services/git-checkpoint.ts
38108
38639
  import { execFile as execFileCb2 } from "child_process";
38109
- import { readFile as readFile28, stat as stat15, unlink as unlink12, writeFile as writeFile18 } from "fs/promises";
38110
- import { join as join49 } from "path";
38640
+ import { readFile as readFile29, stat as stat15, unlink as unlink12, writeFile as writeFile18 } from "fs/promises";
38641
+ import { join as join50 } from "path";
38111
38642
  import { promisify as promisify8 } from "util";
38112
38643
  var execFile9 = promisify8(execFileCb2);
38113
38644
  var NOT_A_GIT_REPO = "Not a git repository";
@@ -38119,10 +38650,10 @@ async function resolveGitPaths(repoDir, taskId) {
38119
38650
  try {
38120
38651
  const { stdout } = await execFile9("git", ["rev-parse", "--git-dir"], { cwd: repoDir });
38121
38652
  const gitDir = stdout.trim();
38122
- const absoluteGitDir = gitDir.startsWith("/") ? gitDir : join49(repoDir, gitDir);
38653
+ const absoluteGitDir = gitDir.startsWith("/") ? gitDir : join50(repoDir, gitDir);
38123
38654
  return {
38124
38655
  gitDir: absoluteGitDir,
38125
- shadowIndex: join49(absoluteGitDir, `shipyard-index-${taskId}`)
38656
+ shadowIndex: join50(absoluteGitDir, `shipyard-index-${taskId}`)
38126
38657
  };
38127
38658
  } catch {
38128
38659
  return null;
@@ -38217,7 +38748,7 @@ async function rewindToCheckpointImpl(repoDir, taskId, turnNo) {
38217
38748
  const untrackedFiles = untrackedRaw.trim().split("\n").filter((f2) => f2.length > 0);
38218
38749
  for (const file of untrackedFiles) {
38219
38750
  try {
38220
- const fullPath = join49(repoDir, file);
38751
+ const fullPath = join50(repoDir, file);
38221
38752
  const s2 = await stat15(fullPath);
38222
38753
  if (s2.isFile()) {
38223
38754
  await unlink12(fullPath);
@@ -38484,7 +39015,7 @@ async function revertSingleFile(repoDir, file, restoreCommit, mode) {
38484
39015
  const isDeletedByAgent = file.status === "deleted";
38485
39016
  const shouldDelete = mode === "revert" && isAddedByAgent || mode === "unrevert" && isDeletedByAgent;
38486
39017
  if (shouldDelete) {
38487
- await unlink12(join49(repoDir, file.path)).catch((err3) => {
39018
+ await unlink12(join50(repoDir, file.path)).catch((err3) => {
38488
39019
  if (!isEnoent(err3)) throw err3;
38489
39020
  });
38490
39021
  await execFile9("git", ["reset", "HEAD", "--", file.path], { cwd: repoDir }).catch(() => {
@@ -38496,7 +39027,7 @@ async function revertSingleFile(repoDir, file, restoreCommit, mode) {
38496
39027
  maxBuffer: 10 * 1024 * 1024,
38497
39028
  encoding: "buffer"
38498
39029
  });
38499
- await writeFile18(join49(repoDir, file.path), stdout);
39030
+ await writeFile18(join50(repoDir, file.path), stdout);
38500
39031
  await execFile9("git", ["reset", "HEAD", "--", file.path], { cwd: repoDir }).catch(() => {
38501
39032
  });
38502
39033
  return true;
@@ -38589,7 +39120,7 @@ async function diffCheckpointToWorkingTreeImpl(repoDir, taskId, fromTurnNo, scop
38589
39120
  if (!line || statusMap.has(line)) continue;
38590
39121
  let ins = 0;
38591
39122
  try {
38592
- ins = countLines(await readFile28(join49(repoDir, line), "utf-8"));
39123
+ ins = countLines(await readFile29(join50(repoDir, line), "utf-8"));
38593
39124
  } catch {
38594
39125
  }
38595
39126
  entries.push({ path: line, status: "added", insertions: ins, deletions: 0 });
@@ -38609,7 +39140,7 @@ async function getFileVsWorkingTreeImpl(repoDir, taskId, fromTurnNo, filePath) {
38609
39140
  if (from === "error") return null;
38610
39141
  let modifiedContent;
38611
39142
  try {
38612
- modifiedContent = await readFile28(join49(repoDir, filePath), "utf-8");
39143
+ modifiedContent = await readFile29(join50(repoDir, filePath), "utf-8");
38613
39144
  } catch {
38614
39145
  modifiedContent = "";
38615
39146
  }
@@ -38706,8 +39237,8 @@ function unregisterSubprocessEpoch(taskId, registry = subprocessEpochs) {
38706
39237
 
38707
39238
  // src/services/harness/deliverable-server.ts
38708
39239
  import { randomUUID as randomUUID12 } from "crypto";
38709
- import { copyFile, rm as fsRm, stat as fsStat3, mkdir as mkdir17 } from "fs/promises";
38710
- import { basename as basename9, extname as extname2, join as join50, resolve as resolve8, sep as sep3 } from "path";
39240
+ import { copyFile, rm as fsRm, stat as fsStat3, mkdir as mkdir18 } from "fs/promises";
39241
+ import { basename as basename9, extname as extname2, join as join51, resolve as resolve8, sep as sep3 } from "path";
38711
39242
  var TOOL_DESCRIPTION2 = [
38712
39243
  "Register a proof-of-work deliverable for this task \u2014 proof a reviewer cannot get from the diff alone.",
38713
39244
  "",
@@ -38771,7 +39302,7 @@ var RegisterDeliverableInput = {
38771
39302
  };
38772
39303
  var COPY_MAX_BYTES = 50 * 1024 * 1024;
38773
39304
  function isInternedFile(dataDir, filePath) {
38774
- const internBase = resolve8(join50(dataDir, "deliverable-files")) + sep3;
39305
+ const internBase = resolve8(join51(dataDir, "deliverable-files")) + sep3;
38775
39306
  return resolve8(filePath).startsWith(internBase);
38776
39307
  }
38777
39308
  async function internDeliverableFile(deliverableId, taskId, filePath, storedSizeBytes, dataDir, prevInternedPath) {
@@ -38795,10 +39326,10 @@ async function internDeliverableFile(deliverableId, taskId, filePath, storedSize
38795
39326
  };
38796
39327
  }
38797
39328
  const ext = extname2(filePath);
38798
- const destDir = join50(dataDir, "deliverable-files", taskId);
38799
- const destPath = join50(destDir, `${deliverableId}${ext}`);
39329
+ const destDir = join51(dataDir, "deliverable-files", taskId);
39330
+ const destPath = join51(destDir, `${deliverableId}${ext}`);
38800
39331
  try {
38801
- await mkdir17(destDir, { recursive: true });
39332
+ await mkdir18(destDir, { recursive: true });
38802
39333
  await copyFile(filePath, destPath);
38803
39334
  } catch (err3) {
38804
39335
  return {
@@ -39221,8 +39752,8 @@ function createDeliverableTools(ctx) {
39221
39752
  }
39222
39753
 
39223
39754
  // src/services/harness/plugin-server.ts
39224
- import { mkdir as mkdir18, writeFile as writeFile19 } from "fs/promises";
39225
- import { join as join51 } from "path";
39755
+ import { mkdir as mkdir19, writeFile as writeFile19 } from "fs/promises";
39756
+ import { join as join52 } from "path";
39226
39757
 
39227
39758
  // src/services/harness/sandbox-docs.ts
39228
39759
  var SANDBOX_HTML_RULES = `## HTML Rules
@@ -39851,14 +40382,14 @@ ${addendum}`;
39851
40382
  events: input.events,
39852
40383
  provideResources: input.provideResources
39853
40384
  });
39854
- const pluginDir = join51(ctx.pluginsDir, input.pluginId);
39855
- await mkdir18(pluginDir, { recursive: true });
39856
- await writeFile19(join51(pluginDir, "template.html"), input.template, "utf-8");
40385
+ const pluginDir = join52(ctx.pluginsDir, input.pluginId);
40386
+ await mkdir19(pluginDir, { recursive: true });
40387
+ await writeFile19(join52(pluginDir, "template.html"), input.template, "utf-8");
39857
40388
  if (input.handler) {
39858
- await writeFile19(join51(pluginDir, "handler.mjs"), input.handler, "utf-8");
40389
+ await writeFile19(join52(pluginDir, "handler.mjs"), input.handler, "utf-8");
39859
40390
  }
39860
40391
  await writeFile19(
39861
- join51(pluginDir, "plugin.json"),
40392
+ join52(pluginDir, "plugin.json"),
39862
40393
  JSON.stringify(manifest, null, 2),
39863
40394
  "utf-8"
39864
40395
  );
@@ -40595,7 +41126,7 @@ function errorResult(message) {
40595
41126
  }
40596
41127
 
40597
41128
  // src/services/harness/visualize-server.ts
40598
- import { readFile as readFile29 } from "fs/promises";
41129
+ import { readFile as readFile30 } from "fs/promises";
40599
41130
  import * as net from "net";
40600
41131
 
40601
41132
  // src/services/harness/html-validator.ts
@@ -49358,7 +49889,7 @@ async function resolveContent(inlineContent, filePath) {
49358
49889
  }
49359
49890
  if (filePath) {
49360
49891
  try {
49361
- return { ok: true, content: await readFile29(filePath, "utf-8") };
49892
+ return { ok: true, content: await readFile30(filePath, "utf-8") };
49362
49893
  } catch (err3) {
49363
49894
  const msg = err3 instanceof Error ? err3.message : String(err3);
49364
49895
  return { ok: false, error: `Failed to read file: ${msg}` };
@@ -49440,7 +49971,7 @@ function textResult3(text, isError) {
49440
49971
  }
49441
49972
  async function handlePresentInline(filePath, vizType) {
49442
49973
  try {
49443
- const content = await readFile29(filePath, "utf-8");
49974
+ const content = await readFile30(filePath, "utf-8");
49444
49975
  return {
49445
49976
  content: [
49446
49977
  { type: "text", text: content },
@@ -49480,7 +50011,7 @@ async function refreshCanvasPresentationFromFile(vizWatcher, slug) {
49480
50011
  if (!viz) return { ok: false, title: slug, reason: "no_canvas_element" };
49481
50012
  if (!viz.canvasElementId) return { ok: false, title: viz.title, reason: "no_canvas_element" };
49482
50013
  try {
49483
- const content = await readFile29(viz.filePath, "utf-8");
50014
+ const content = await readFile30(viz.filePath, "utf-8");
49484
50015
  const effects = vizWatcher.registry.refreshCanvasPresentation(slug, content);
49485
50016
  await vizWatcher.executeEffects(effects);
49486
50017
  return { ok: true, title: viz.title };
@@ -49567,7 +50098,7 @@ Fix the content and try again.`
49567
50098
  await ctx.vizWatcher.executeEffects([
49568
50099
  { type: "write_file", filePath: existingFilePath, content }
49569
50100
  ]);
49570
- const persisted = await readFile29(existingFilePath, "utf-8").catch((err3) => {
50101
+ const persisted = await readFile30(existingFilePath, "utf-8").catch((err3) => {
49571
50102
  ctx.log({
49572
50103
  event: "viz_update_reconcile_read_failed",
49573
50104
  filePath: existingFilePath,
@@ -50085,7 +50616,7 @@ import { pathToFileURL as pathToFileURL3 } from "url";
50085
50616
  // src/services/lsp/language-server-registry.ts
50086
50617
  import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
50087
50618
  import { createRequire as createRequire3 } from "module";
50088
- import { dirname as dirname21, isAbsolute as isAbsolute4, join as join52 } from "path";
50619
+ import { dirname as dirname22, isAbsolute as isAbsolute4, join as join53 } from "path";
50089
50620
  function decideTypescriptServer(d) {
50090
50621
  if (d.hasAngular || d.hasNest || d.hasTsserverPlugins) return "vtsls";
50091
50622
  return "tsgo";
@@ -50175,11 +50706,11 @@ function isRecord7(value) {
50175
50706
  return typeof value === "object" && value !== null;
50176
50707
  }
50177
50708
  function gatherTsDetection(cwd, deps) {
50178
- const pkg = deps.readJsonFile(join52(cwd, "package.json"));
50709
+ const pkg = deps.readJsonFile(join53(cwd, "package.json"));
50179
50710
  const depNames = isRecord7(pkg) ? [...keysOf(pkg.dependencies), ...keysOf(pkg.devDependencies)] : [];
50180
- const hasAngular = depNames.includes("@angular/core") || deps.fileExists(join52(cwd, "angular.json"));
50181
- const hasNest = depNames.includes("@nestjs/core") || deps.fileExists(join52(cwd, "nest-cli.json"));
50182
- const tsconfig = deps.readJsonFile(join52(cwd, "tsconfig.json"));
50711
+ const hasAngular = depNames.includes("@angular/core") || deps.fileExists(join53(cwd, "angular.json"));
50712
+ const hasNest = depNames.includes("@nestjs/core") || deps.fileExists(join53(cwd, "nest-cli.json"));
50713
+ const tsconfig = deps.readJsonFile(join53(cwd, "tsconfig.json"));
50183
50714
  const compilerOptions = isRecord7(tsconfig) ? tsconfig.compilerOptions : void 0;
50184
50715
  const plugins2 = isRecord7(compilerOptions) ? compilerOptions.plugins : void 0;
50185
50716
  const hasTsserverPlugins = Array.isArray(plugins2) && plugins2.length > 0;
@@ -50194,11 +50725,11 @@ function detectPythonVenv(cwd, deps) {
50194
50725
  if (envVenv && isAbsolute4(envVenv) && deps.fileExists(envVenv)) {
50195
50726
  candidates.push(envVenv);
50196
50727
  }
50197
- candidates.push(join52(cwd, ".venv"), join52(cwd, "venv"));
50728
+ candidates.push(join53(cwd, ".venv"), join53(cwd, "venv"));
50198
50729
  const isWin = deps.platform === "win32";
50199
50730
  for (const root of candidates) {
50200
- const python = isWin ? join52(root, "Scripts", "python.exe") : join52(root, "bin", "python");
50201
- const python3 = isWin ? python : join52(root, "bin", "python3");
50731
+ const python = isWin ? join53(root, "Scripts", "python.exe") : join53(root, "bin", "python");
50732
+ const python3 = isWin ? python : join53(root, "bin", "python3");
50202
50733
  if (deps.fileExists(python) || deps.fileExists(python3)) return root;
50203
50734
  }
50204
50735
  return null;
@@ -50231,8 +50762,8 @@ function resolveTsgoBinaryPath(req) {
50231
50762
  `Unable to resolve ${platformPkg}: ${err3 instanceof Error ? err3.message : String(err3)}`
50232
50763
  );
50233
50764
  }
50234
- const exeDir = join52(dirname21(pkgJsonPath), "lib");
50235
- const exe = process.platform === "win32" ? join52(exeDir, "tsgo.exe") : join52(exeDir, "tsgo");
50765
+ const exeDir = join53(dirname22(pkgJsonPath), "lib");
50766
+ const exe = process.platform === "win32" ? join53(exeDir, "tsgo.exe") : join53(exeDir, "tsgo");
50236
50767
  if (!existsSync7(exe)) {
50237
50768
  throw new Error(`tsgo native binary not found: ${exe}`);
50238
50769
  }
@@ -50245,7 +50776,7 @@ function resolveBinViaPackageJson(pkg, binName, req) {
50245
50776
  const named = isRecord7(binField) ? binField[binName] : void 0;
50246
50777
  const bin = typeof binField === "string" ? binField : typeof named === "string" ? named : void 0;
50247
50778
  if (!bin) throw new Error(`package '${pkg}' has no bin entry '${binName}'`);
50248
- return join52(dirname21(pkgJsonPath), bin);
50779
+ return join53(dirname22(pkgJsonPath), bin);
50249
50780
  }
50250
50781
  function errorMessage3(err3) {
50251
50782
  return err3 instanceof Error ? err3.message : String(err3);
@@ -51400,6 +51931,7 @@ function assertNever6(x2, context) {
51400
51931
 
51401
51932
  // src/services/mcp/server-fsm.ts
51402
51933
  var CONNECT_TIMEOUT_MS = 3e4;
51934
+ var COLD_CONNECT_TIMEOUT_MS = 12e4;
51403
51935
  function snapshotToStatusEntry(snap) {
51404
51936
  const entry = {
51405
51937
  name: snap.name,
@@ -51427,7 +51959,22 @@ function initialSnapshot(name, startEnabled) {
51427
51959
  config: null,
51428
51960
  toolCount: 0,
51429
51961
  serverInfo: null,
51430
- error: null
51962
+ error: null,
51963
+ connectAttemptCount: startEnabled ? 1 : 0
51964
+ };
51965
+ }
51966
+ function connectTimeoutMsForAttempt(attemptCount) {
51967
+ return attemptCount <= 1 ? COLD_CONNECT_TIMEOUT_MS : CONNECT_TIMEOUT_MS;
51968
+ }
51969
+ function formatConnectTimeoutError(ms) {
51970
+ return `Connect timeout (${Math.round(ms / 1e3)}s)`;
51971
+ }
51972
+ function enterConnecting(snap) {
51973
+ return {
51974
+ ...snap,
51975
+ state: "connecting",
51976
+ error: null,
51977
+ connectAttemptCount: snap.connectAttemptCount + 1
51431
51978
  };
51432
51979
  }
51433
51980
  function illegal(state, eventType) {
@@ -51452,17 +51999,16 @@ function transition3(snap, event) {
51452
51999
  function fromDisabled(snap, event) {
51453
52000
  switch (event.type) {
51454
52001
  case "enable": {
51455
- const next = {
51456
- ...snap,
51457
- state: "connecting",
51458
- config: event.config,
51459
- error: null
51460
- };
52002
+ const next = { ...enterConnecting(snap), config: event.config };
51461
52003
  return {
51462
52004
  snapshot: next,
51463
52005
  effects: [
51464
52006
  { type: "OpenTransport", name: snap.name, config: event.config },
51465
- { type: "StartConnectTimer", name: snap.name, ms: CONNECT_TIMEOUT_MS },
52007
+ {
52008
+ type: "StartConnectTimer",
52009
+ name: snap.name,
52010
+ ms: connectTimeoutMsForAttempt(next.connectAttemptCount)
52011
+ },
51466
52012
  { type: "BroadcastStatus", entry: snapshotToStatusEntry(next) }
51467
52013
  ]
51468
52014
  };
@@ -51555,7 +52101,7 @@ function fromConnecting(snap, event) {
51555
52101
  const next = {
51556
52102
  ...snap,
51557
52103
  state: "failed",
51558
- error: "Connect timeout (30s)"
52104
+ error: formatConnectTimeoutError(event.ms)
51559
52105
  };
51560
52106
  return {
51561
52107
  snapshot: next,
@@ -51667,16 +52213,16 @@ function fromNeedsAuth(snap, event) {
51667
52213
  if (snap.config === null) {
51668
52214
  throw new Error(`MCP FSM: reauth_completed for "${snap.name}" without retained config`);
51669
52215
  }
51670
- const next = {
51671
- ...snap,
51672
- state: "connecting",
51673
- error: null
51674
- };
52216
+ const next = enterConnecting(snap);
51675
52217
  return {
51676
52218
  snapshot: next,
51677
52219
  effects: [
51678
52220
  { type: "OpenTransport", name: snap.name, config: snap.config },
51679
- { type: "StartConnectTimer", name: snap.name, ms: CONNECT_TIMEOUT_MS },
52221
+ {
52222
+ type: "StartConnectTimer",
52223
+ name: snap.name,
52224
+ ms: connectTimeoutMsForAttempt(next.connectAttemptCount)
52225
+ },
51680
52226
  { type: "EmitReauthCompleted", name: snap.name },
51681
52227
  { type: "BroadcastStatus", entry: snapshotToStatusEntry(next) }
51682
52228
  ]
@@ -51715,17 +52261,16 @@ function fromFailed(snap, event) {
51715
52261
  };
51716
52262
  }
51717
52263
  case "enable": {
51718
- const next = {
51719
- ...snap,
51720
- state: "connecting",
51721
- config: event.config,
51722
- error: null
51723
- };
52264
+ const next = { ...enterConnecting(snap), config: event.config };
51724
52265
  return {
51725
52266
  snapshot: next,
51726
52267
  effects: [
51727
52268
  { type: "OpenTransport", name: snap.name, config: event.config },
51728
- { type: "StartConnectTimer", name: snap.name, ms: CONNECT_TIMEOUT_MS },
52269
+ {
52270
+ type: "StartConnectTimer",
52271
+ name: snap.name,
52272
+ ms: connectTimeoutMsForAttempt(next.connectAttemptCount)
52273
+ },
51729
52274
  { type: "BroadcastStatus", entry: snapshotToStatusEntry(next) }
51730
52275
  ]
51731
52276
  };
@@ -52118,7 +52663,7 @@ var ConnectTimer = class {
52118
52663
  const set = this.#deps.setTimeoutFn ?? setTimeout;
52119
52664
  const handle = set(() => {
52120
52665
  this.#handles.delete(name);
52121
- this.#deps.onTimeout(name);
52666
+ this.#deps.onTimeout(name, ms);
52122
52667
  }, ms);
52123
52668
  if (hasUnref(handle)) handle.unref();
52124
52669
  this.#handles.set(name, handle);
@@ -52182,7 +52727,6 @@ var BOOTSTRAP_OPEN_JITTER_BASE_MS = 100;
52182
52727
  var BOOTSTRAP_OPEN_JITTER_MS = 500;
52183
52728
  var OPEN_TRANSPORT_BURST_WINDOW_MS = 200;
52184
52729
  var STDIO_NO_AGENT_SENTINEL_MS = 5 * 6e4;
52185
- var CONNECT_TIMEOUT_MS2 = 3e4;
52186
52730
  var McpCoordinator = class {
52187
52731
  #deps;
52188
52732
  #registry;
@@ -52225,8 +52769,8 @@ var McpCoordinator = class {
52225
52769
  } : void 0;
52226
52770
  this.#dedup = new TerminalReconnectDedup(deps.log, gatedSendUserActionRequired);
52227
52771
  this.#timer = new ConnectTimer({
52228
- onTimeout: (name) => {
52229
- this.#registry.dispatch(name, { type: "timeout" });
52772
+ onTimeout: (name, ms) => {
52773
+ this.#registry.dispatch(name, { type: "timeout", ms });
52230
52774
  },
52231
52775
  ...deps.setTimeoutFn ? { setTimeoutFn: deps.setTimeoutFn } : {},
52232
52776
  ...deps.clearTimeoutFn ? { clearTimeoutFn: deps.clearTimeoutFn } : {}
@@ -52765,7 +53309,7 @@ var McpCoordinator = class {
52765
53309
  }
52766
53310
  for (const snap of this.#snapshots()) {
52767
53311
  if (snap.state === "connecting" && snap.config?.transport === "stdio") {
52768
- this.#timer.start(snap.name, CONNECT_TIMEOUT_MS2);
53312
+ this.#timer.start(snap.name, CONNECT_TIMEOUT_MS);
52769
53313
  }
52770
53314
  }
52771
53315
  for (const snap of this.#snapshots()) {
@@ -53109,9 +53653,9 @@ import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
53109
53653
  // src/shared/mcp/codex-credentials.ts
53110
53654
  import { execFile as execFileCb3 } from "child_process";
53111
53655
  import { createHash as createHash3 } from "crypto";
53112
- import { readFile as readFile30 } from "fs/promises";
53656
+ import { readFile as readFile31 } from "fs/promises";
53113
53657
  import { homedir as homedir7 } from "os";
53114
- import { join as join53 } from "path";
53658
+ import { join as join54 } from "path";
53115
53659
  import { promisify as promisify9 } from "util";
53116
53660
  var execFile10 = promisify9(execFileCb3);
53117
53661
  var CodexCredentialsEntrySchema = external_exports.object({
@@ -53172,10 +53716,10 @@ async function readKeychainStore() {
53172
53716
  return null;
53173
53717
  }
53174
53718
  }
53175
- async function readCodexMcpCredentials(serverName, serverUrl, codexHome = join53(homedir7(), ".codex")) {
53719
+ async function readCodexMcpCredentials(serverName, serverUrl, codexHome = join54(homedir7(), ".codex")) {
53176
53720
  if (!serverUrl) return null;
53177
53721
  try {
53178
- const raw = await readFile30(join53(codexHome, FALLBACK_FILENAME), "utf-8");
53722
+ const raw = await readFile31(join54(codexHome, FALLBACK_FILENAME), "utf-8");
53179
53723
  const fileResult = CodexCredentialsFileSchema.safeParse(JSON.parse(raw));
53180
53724
  if (fileResult.success) {
53181
53725
  const entry = findEntry2(fileResult.data, serverName, serverUrl);
@@ -55676,9 +56220,9 @@ function startPlanDocPersistenceGapReporter(deps) {
55676
56220
  }
55677
56221
 
55678
56222
  // src/services/metrics/stall-profiler.ts
55679
- import { mkdir as mkdir19, readdir as readdir18, unlink as unlink13, writeFile as writeFile20 } from "fs/promises";
56223
+ import { mkdir as mkdir20, readdir as readdir18, unlink as unlink13, writeFile as writeFile20 } from "fs/promises";
55680
56224
  import { Session as Session2 } from "inspector/promises";
55681
- import { join as join54 } from "path";
56225
+ import { join as join55 } from "path";
55682
56226
  import { monitorEventLoopDelay as monitorEventLoopDelay3 } from "perf_hooks";
55683
56227
  function hasProfile2(value) {
55684
56228
  return typeof value === "object" && value !== null && "profile" in value;
@@ -55788,8 +56332,8 @@ var StallProfiler = class {
55788
56332
  const isoTs = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
55789
56333
  const stallRounded = Math.round(stallMs);
55790
56334
  const filename = `stall-${isoTs}-${stallRounded}ms.cpuprofile`;
55791
- const profilePath = join54(this.#outDir, filename);
55792
- await mkdir19(this.#outDir, { recursive: true });
56335
+ const profilePath = join55(this.#outDir, filename);
56336
+ await mkdir20(this.#outDir, { recursive: true });
55793
56337
  await writeFile20(profilePath, JSON.stringify(stopResult.profile));
55794
56338
  this.#log({
55795
56339
  event: "stall_profile_captured",
@@ -55822,7 +56366,7 @@ var StallProfiler = class {
55822
56366
  const entries = await readdir18(this.#outDir);
55823
56367
  const toDelete = planProfileEviction(entries, this.#maxProfiles, STALL_PROFILE_PREFIX);
55824
56368
  if (toDelete.length === 0) return;
55825
- const results = await Promise.allSettled(toDelete.map((f2) => unlink13(join54(this.#outDir, f2))));
56369
+ const results = await Promise.allSettled(toDelete.map((f2) => unlink13(join55(this.#outDir, f2))));
55826
56370
  const failures = results.filter((r) => r.status === "rejected").length;
55827
56371
  if (failures > 0) {
55828
56372
  this.#log({
@@ -66359,12 +66903,12 @@ function handleTaskStoreBroadcast(deps, event) {
66359
66903
  }
66360
66904
 
66361
66905
  // src/services/serve-factory/viz-preview.ts
66362
- import { join as join57 } from "path";
66906
+ import { join as join58 } from "path";
66363
66907
 
66364
66908
  // src/services/harness/visualization-file-watcher.ts
66365
66909
  import { randomUUID as randomUUID16 } from "crypto";
66366
- import { mkdir as mkdir20, readFile as readFile31, rename as rename16, writeFile as writeFile21 } from "fs/promises";
66367
- import { basename as basename10, dirname as dirname22, join as join55 } from "path";
66910
+ import { mkdir as mkdir21, readFile as readFile32, rename as rename16, writeFile as writeFile21 } from "fs/promises";
66911
+ import { basename as basename10, dirname as dirname23, join as join56 } from "path";
66368
66912
  var PREVIEW_DEFAULT_W = 1200;
66369
66913
  var PREVIEW_DEFAULT_H = 800;
66370
66914
  function previewDataToLoroValue(data) {
@@ -66417,7 +66961,7 @@ function mergedPreviewData(data, state) {
66417
66961
  }
66418
66962
  var DEBOUNCE_MS3 = 200;
66419
66963
  async function atomicWrite2(filePath, content) {
66420
- await mkdir20(dirname22(filePath), { recursive: true });
66964
+ await mkdir21(dirname23(filePath), { recursive: true });
66421
66965
  const tmpPath = `${filePath}.${randomUUID16()}.tmp`;
66422
66966
  await writeFile21(tmpPath, content, "utf-8");
66423
66967
  await rename16(tmpPath, filePath);
@@ -66513,7 +67057,7 @@ var VisualizationFileWatcher = class {
66513
67057
  const byTask = /* @__PURE__ */ new Map();
66514
67058
  for (const viz of allViz) {
66515
67059
  try {
66516
- const content = await readFile31(viz.filePath, "utf-8");
67060
+ const content = await readFile32(viz.filePath, "utf-8");
66517
67061
  let items = byTask.get(viz.taskId);
66518
67062
  if (!items) {
66519
67063
  items = [];
@@ -66576,7 +67120,7 @@ var VisualizationFileWatcher = class {
66576
67120
  }
66577
67121
  case "persist_registry":
66578
67122
  await atomicWrite2(
66579
- join55(this.#deps.vizDir, effect.taskId, "registry.json"),
67123
+ join56(this.#deps.vizDir, effect.taskId, "registry.json"),
66580
67124
  JSON.stringify(effect.data, null, 2)
66581
67125
  );
66582
67126
  break;
@@ -66588,7 +67132,7 @@ var VisualizationFileWatcher = class {
66588
67132
  if (this.#disposed) return;
66589
67133
  if (this.#watchedFiles.has(filePath)) return;
66590
67134
  this.#watchedFiles.add(filePath);
66591
- const dirPath = dirname22(filePath);
67135
+ const dirPath = dirname23(filePath);
66592
67136
  const existing = this.#dirWatchers.get(dirPath);
66593
67137
  if (existing) {
66594
67138
  existing.refCount += 1;
@@ -66602,7 +67146,7 @@ var VisualizationFileWatcher = class {
66602
67146
  const findWatchedFile = (eventPath) => {
66603
67147
  const eventBase = basename10(eventPath);
66604
67148
  for (const wf of this.#watchedFiles) {
66605
- if (dirname22(wf) === dirPath && basename10(wf) === eventBase) return wf;
67149
+ if (dirname23(wf) === dirPath && basename10(wf) === eventBase) return wf;
66606
67150
  }
66607
67151
  return null;
66608
67152
  };
@@ -66655,7 +67199,7 @@ var VisualizationFileWatcher = class {
66655
67199
  }
66656
67200
  #stopFileWatch(filePath) {
66657
67201
  if (!this.#watchedFiles.delete(filePath)) return;
66658
- const dirPath = dirname22(filePath);
67202
+ const dirPath = dirname23(filePath);
66659
67203
  const entry = this.#dirWatchers.get(dirPath);
66660
67204
  if (entry) {
66661
67205
  entry.refCount -= 1;
@@ -66777,7 +67321,7 @@ var VisualizationFileWatcher = class {
66777
67321
  let content = effect.content;
66778
67322
  if (!content && effect.filePath) {
66779
67323
  try {
66780
- content = await readFile31(effect.filePath, "utf-8");
67324
+ content = await readFile32(effect.filePath, "utf-8");
66781
67325
  } catch {
66782
67326
  content = "";
66783
67327
  }
@@ -66823,7 +67367,7 @@ var VisualizationFileWatcher = class {
66823
67367
  async #handleFileChange(filePath) {
66824
67368
  let content;
66825
67369
  try {
66826
- content = await readFile31(filePath, "utf-8");
67370
+ content = await readFile32(filePath, "utf-8");
66827
67371
  } catch {
66828
67372
  return;
66829
67373
  }
@@ -66874,7 +67418,7 @@ var VisualizationFileWatcher = class {
66874
67418
 
66875
67419
  // src/services/harness/visualization-registry.ts
66876
67420
  import { createHash as createHash5 } from "crypto";
66877
- import { join as join56 } from "path";
67421
+ import { join as join57 } from "path";
66878
67422
  function hashContent(content) {
66879
67423
  return createHash5("sha256").update(content).digest("hex");
66880
67424
  }
@@ -66888,7 +67432,7 @@ var VisualizationRegistry = class {
66888
67432
  return null;
66889
67433
  }
66890
67434
  const ext = vizFileExtension(vizType);
66891
- const filePath = join56(vizDir, taskId, `${slug}${ext}`);
67435
+ const filePath = join57(vizDir, taskId, `${slug}${ext}`);
66892
67436
  const contentHash3 = hashContent(content);
66893
67437
  const viz = {
66894
67438
  slug,
@@ -67039,7 +67583,7 @@ var VisualizationRegistry = class {
67039
67583
  // src/services/serve-factory/viz-preview.ts
67040
67584
  function createVizPreviewRegistry(deps) {
67041
67585
  const { canvasRepo } = deps;
67042
- const vizDir = join57(deps.dataDir, "visualizations");
67586
+ const vizDir = join58(deps.dataDir, "visualizations");
67043
67587
  const vizWatchers = /* @__PURE__ */ new Map();
67044
67588
  function getOrCreateVizWatcher(taskId) {
67045
67589
  const existing = vizWatchers.get(taskId);
@@ -67138,7 +67682,7 @@ function createVizPreviewRegistry(deps) {
67138
67682
  import { createHmac, timingSafeEqual as timingSafeEqual3 } from "crypto";
67139
67683
  import { promises as fs } from "fs";
67140
67684
  import { createServer as createServer5 } from "net";
67141
- import { dirname as dirname23 } from "path";
67685
+ import { dirname as dirname24 } from "path";
67142
67686
  var NONCE_TTL_MS = 12e4;
67143
67687
  var NONCE_CACHE_MAX = 1e5;
67144
67688
  var CursorHookSocketServer = class {
@@ -67162,7 +67706,7 @@ var CursorHookSocketServer = class {
67162
67706
  if (this.#hmacSecret.length === 0) {
67163
67707
  throw new Error(`cursor-hook-socket: empty HMAC keyfile at ${this.#opts.hmacKeyfile}`);
67164
67708
  }
67165
- await fs.mkdir(dirname23(this.#opts.socketPath), { recursive: true, mode: 448 });
67709
+ await fs.mkdir(dirname24(this.#opts.socketPath), { recursive: true, mode: 448 });
67166
67710
  await fs.rm(this.#opts.socketPath, { force: true });
67167
67711
  const server = createServer5((conn) => {
67168
67712
  this.#handleConnection(conn);
@@ -70347,7 +70891,7 @@ function createCursorVaultKeyCache() {
70347
70891
  }
70348
70892
 
70349
70893
  // src/services/session/skills/cursor-skill-body-resolver.ts
70350
- import { readFile as readFile32 } from "fs/promises";
70894
+ import { readFile as readFile33 } from "fs/promises";
70351
70895
  function skillKey(name, namespace) {
70352
70896
  return namespace ? `${namespace}:${name}` : name;
70353
70897
  }
@@ -70400,7 +70944,7 @@ function collectInvocations(blocks) {
70400
70944
  return out;
70401
70945
  }
70402
70946
  async function readUtf8(path5) {
70403
- return readFile32(path5, "utf-8");
70947
+ return readFile33(path5, "utf-8");
70404
70948
  }
70405
70949
  async function resolveCursorSkillBodiesFromTrustedSkills(blocks, skills, log, readSkillFile = readUtf8) {
70406
70950
  const invocations = collectInvocations(blocks);
@@ -70513,7 +71057,7 @@ function buildCreationSkillDefaults(entries) {
70513
71057
 
70514
71058
  // src/services/skills/skill-resolution-service.ts
70515
71059
  import { mkdir as mkdir23, readdir as readdir19, readFile as readFile34, writeFile as writeFile23 } from "fs/promises";
70516
- import { basename as basename11, dirname as dirname26, join as join61, relative as relative8, resolve as resolve11, sep as sep6 } from "path";
71060
+ import { basename as basename11, dirname as dirname26, join as join62, relative as relative8, resolve as resolve11, sep as sep6 } from "path";
70517
71061
 
70518
71062
  // src/services/skills/bundle-schema.ts
70519
71063
  var SKILL_BUNDLE_VERSION = 2;
@@ -70641,196 +71185,10 @@ function migrateSkillBundle(raw) {
70641
71185
 
70642
71186
  // src/services/skills/bundle-store.ts
70643
71187
  import { createHash as createHash9 } from "crypto";
70644
- import { join as join59 } from "path";
70645
-
70646
- // src/services/storage/json-document-store.ts
70647
- import { mkdir as mkdir21, readFile as readFile33 } from "fs/promises";
70648
- import { dirname as dirname24 } from "path";
70649
- var WRITE_DEBOUNCE_MS = 50;
70650
- function applyMutations(records, mutations) {
70651
- for (const [id, mutation] of mutations) {
70652
- if (mutation.kind === "delete") {
70653
- delete records[id];
70654
- } else {
70655
- records[id] = mutation.data;
70656
- }
70657
- }
70658
- }
70659
- function resolveWaiters(waiters) {
70660
- for (const waiter of waiters) waiter.resolve();
70661
- }
70662
- function rejectWaiters(waiters, error) {
70663
- for (const waiter of waiters) waiter.reject(error);
70664
- }
70665
- function buildJsonDocumentStore(opts) {
70666
- const { filePath, recordSchema, currentVersion, migrate, storeName, docType } = opts;
70667
- let cache2 = null;
70668
- const listeners = /* @__PURE__ */ new Set();
70669
- let writeQueue = Promise.resolve();
70670
- const lockPath2 = `${filePath}.lock`;
70671
- let pendingMutations = /* @__PURE__ */ new Map();
70672
- let pendingFlushTimer = null;
70673
- let pendingFlushWaiters = [];
70674
- function notify(event) {
70675
- for (const listener of listeners) {
70676
- try {
70677
- listener(event);
70678
- } catch {
70679
- }
70680
- }
70681
- }
70682
- async function ensureDir() {
70683
- await mkdir21(dirname24(filePath), { recursive: true });
70684
- }
70685
- async function readFromDisk() {
70686
- try {
70687
- const raw = await readFile33(filePath, "utf-8");
70688
- return migrate(JSON.parse(raw));
70689
- } catch (err3) {
70690
- if (isEnoent(err3)) {
70691
- return { schemaVersion: currentVersion, records: {} };
70692
- }
70693
- if (isCorruptionError(err3)) {
70694
- const event = await quarantineCorruptFile({
70695
- path: filePath,
70696
- storeName,
70697
- docType,
70698
- error: err3,
70699
- defaultsApplied: true,
70700
- logger: opts.logger
70701
- });
70702
- opts.onCorrupt?.(event);
70703
- return { schemaVersion: currentVersion, records: {} };
70704
- }
70705
- throw err3;
70706
- }
70707
- }
70708
- async function readStore() {
70709
- if (cache2) return cache2;
70710
- cache2 = await readFromDisk();
70711
- return cache2;
70712
- }
70713
- async function flushPendingWrite() {
70714
- pendingFlushTimer = null;
70715
- const waiters = pendingFlushWaiters;
70716
- pendingFlushWaiters = [];
70717
- const mutations = pendingMutations;
70718
- pendingMutations = /* @__PURE__ */ new Map();
70719
- if (mutations.size === 0) {
70720
- resolveWaiters(waiters);
70721
- return;
70722
- }
70723
- try {
70724
- await commitMutations(mutations);
70725
- resolveWaiters(waiters);
70726
- } catch (err3) {
70727
- requeueMutations(mutations);
70728
- if (pendingMutations.size > 0 && pendingFlushTimer === null) {
70729
- pendingFlushTimer = setTimeout(() => {
70730
- void flushPendingWrite();
70731
- }, WRITE_DEBOUNCE_MS);
70732
- }
70733
- rejectWaiters(waiters, err3);
70734
- }
70735
- }
70736
- async function commitMutations(mutations) {
70737
- await withFileLock(lockPath2, async () => {
70738
- const disk = await readFromDisk();
70739
- applyMutations(disk.records, mutations);
70740
- await ensureDir();
70741
- await atomicWriteFile(filePath, JSON.stringify(disk));
70742
- applyMutations(disk.records, pendingMutations);
70743
- cache2 = disk;
70744
- });
70745
- }
70746
- function requeueMutations(mutations) {
70747
- for (const [id, mutation] of mutations) {
70748
- if (!pendingMutations.has(id)) pendingMutations.set(id, mutation);
70749
- }
70750
- }
70751
- function scheduleFlush() {
70752
- return new Promise((resolve12, reject) => {
70753
- pendingFlushWaiters.push({ resolve: resolve12, reject });
70754
- if (pendingFlushTimer) return;
70755
- pendingFlushTimer = setTimeout(() => {
70756
- void flushPendingWrite();
70757
- }, WRITE_DEBOUNCE_MS);
70758
- });
70759
- }
70760
- function enqueueWrite(op) {
70761
- let resultFlush;
70762
- const next = writeQueue.then(
70763
- async () => {
70764
- const out = await op();
70765
- resultFlush = out?.flush;
70766
- },
70767
- async () => {
70768
- const out = await op();
70769
- resultFlush = out?.flush;
70770
- }
70771
- );
70772
- writeQueue = next;
70773
- return next.then(() => resultFlush);
70774
- }
70775
- return {
70776
- async get(id) {
70777
- const store = await readStore();
70778
- const record = store.records[id];
70779
- return record ?? null;
70780
- },
70781
- async set(id, data) {
70782
- await enqueueWrite(async () => {
70783
- const store = await readStore();
70784
- store.records[id] = data;
70785
- pendingMutations.set(id, { kind: "set", data });
70786
- const flush = scheduleFlush();
70787
- notify({ kind: "set", id, data });
70788
- return { flush };
70789
- });
70790
- },
70791
- async update(id, fn) {
70792
- await enqueueWrite(async () => {
70793
- const store = await readStore();
70794
- const existing = store.records[id];
70795
- if (!existing) return void 0;
70796
- const parsed = recordSchema.parse(existing);
70797
- const updated = fn(parsed);
70798
- if (updated === parsed) return void 0;
70799
- store.records[id] = updated;
70800
- pendingMutations.set(id, { kind: "set", data: updated });
70801
- const flush = scheduleFlush();
70802
- notify({ kind: "set", id, data: updated });
70803
- return { flush };
70804
- });
70805
- },
70806
- async delete(id) {
70807
- await enqueueWrite(async () => {
70808
- const store = await readStore();
70809
- if (!(id in store.records)) return void 0;
70810
- delete store.records[id];
70811
- pendingMutations.set(id, { kind: "delete" });
70812
- const flush = scheduleFlush();
70813
- notify({ kind: "delete", id });
70814
- return { flush };
70815
- });
70816
- },
70817
- async list() {
70818
- const store = await readStore();
70819
- return { ...store.records };
70820
- },
70821
- subscribe(listener) {
70822
- listeners.add(listener);
70823
- return () => {
70824
- listeners.delete(listener);
70825
- };
70826
- }
70827
- };
70828
- }
70829
-
70830
- // src/services/skills/bundle-store.ts
71188
+ import { join as join60 } from "path";
70831
71189
  var BUNDLE_RECORD_KEY = "bundle";
70832
71190
  function defaultSkillBundleDir(shipyardHome) {
70833
- return join59(shipyardHome, "state", "skill-bundles");
71191
+ return join60(shipyardHome, "state", "skill-bundles");
70834
71192
  }
70835
71193
  function skillBundleSlug(ownerRepo) {
70836
71194
  return ownerRepo.replace(/[^A-Za-z0-9._-]/g, (ch) => {
@@ -70852,7 +71210,7 @@ function buildProjectSkillBundleStore(baseDir) {
70852
71210
  const store = buildJsonDocumentStore({
70853
71211
  storeName: `skill-bundle:${slug}`,
70854
71212
  docType: "generic",
70855
- filePath: join59(baseDir, `${slug}.json`),
71213
+ filePath: join60(baseDir, `${slug}.json`),
70856
71214
  recordSchema: SkillBundleRecordSchema,
70857
71215
  currentVersion: SKILL_BUNDLE_VERSION,
70858
71216
  migrate: migrateSkillBundle
@@ -71244,9 +71602,9 @@ function nativeSkillIdentifier(skill) {
71244
71602
  // src/services/skills/skill-library-cache.ts
71245
71603
  import { createHash as createHash11 } from "crypto";
71246
71604
  import { mkdir as mkdir22, writeFile as writeFile22 } from "fs/promises";
71247
- import { join as join60 } from "path";
71605
+ import { join as join61 } from "path";
71248
71606
  function defaultSkillLibraryCacheDir(shipyardHome) {
71249
- return join60(shipyardHome, "cache", "library-skills");
71607
+ return join61(shipyardHome, "cache", "library-skills");
71250
71608
  }
71251
71609
  function skillCacheSlug(name, contentHash3) {
71252
71610
  const safeName = name.replace(/[^A-Za-z0-9._-]/g, "-");
@@ -71254,9 +71612,9 @@ function skillCacheSlug(name, contentHash3) {
71254
71612
  return `${safeName}-${hash}`;
71255
71613
  }
71256
71614
  async function materializeLibrarySkill(input) {
71257
- const dir = join60(input.baseDir, skillCacheSlug(input.name, input.contentHash));
71615
+ const dir = join61(input.baseDir, skillCacheSlug(input.name, input.contentHash));
71258
71616
  await mkdir22(dir, { recursive: true });
71259
- const skillMdPath = join60(dir, "SKILL.md");
71617
+ const skillMdPath = join61(dir, "SKILL.md");
71260
71618
  await writeFile22(skillMdPath, input.body, "utf-8");
71261
71619
  return { skillMdPath, dir };
71262
71620
  }
@@ -71515,7 +71873,7 @@ function buildSkillResolutionService(deps) {
71515
71873
  for (const file of files) {
71516
71874
  const fileSlug = basename11(file, ".json");
71517
71875
  if (!file.endsWith(".json") || fileSlug === currentSlug) continue;
71518
- const bundle = await readBundleFile(join61(dir, file));
71876
+ const bundle = await readBundleFile(join62(dir, file));
71519
71877
  if (bundle !== null) bundlesBySlug.set(fileSlug, bundle);
71520
71878
  }
71521
71879
  return bundlesBySlug;
@@ -71879,14 +72237,14 @@ function buildSkillResolutionService(deps) {
71879
72237
  if (body === null || body.length === 0) {
71880
72238
  return { ok: false, error: `No body supplied and no library skill named "${args.name}"` };
71881
72239
  }
71882
- const skillsRoot = join61(repoRoot, ".claude", "skills");
72240
+ const skillsRoot = join62(repoRoot, ".claude", "skills");
71883
72241
  const skillDir = resolve11(skillsRoot, args.name);
71884
72242
  const rel = relative8(skillsRoot, skillDir);
71885
72243
  if (rel.startsWith("..") || rel.includes(`..${sep6}`) || resolve11(skillsRoot, rel) !== skillDir) {
71886
72244
  return { ok: false, error: "Invalid skill path" };
71887
72245
  }
71888
72246
  await mkdir23(skillDir, { recursive: true });
71889
- await writeFile23(join61(skillDir, "SKILL.md"), body, "utf-8");
72247
+ await writeFile23(join62(skillDir, "SKILL.md"), body, "utf-8");
71890
72248
  return { ok: true, name: args.name };
71891
72249
  }
71892
72250
  async function libraryBodyByName(cwd, name) {
@@ -71916,7 +72274,7 @@ function buildSkillResolutionService(deps) {
71916
72274
 
71917
72275
  // src/services/stack-detection.ts
71918
72276
  import { access as access5 } from "fs/promises";
71919
- import { join as join62 } from "path";
72277
+ import { join as join63 } from "path";
71920
72278
  var STACK_TIMEOUT_MS = TIMEOUT_MS;
71921
72279
  async function defaultIsCommandAvailable(command) {
71922
72280
  try {
@@ -71958,7 +72316,7 @@ async function detectStack(cwd, currentBranch, deps = {}) {
71958
72316
  async function detectGraphite(cwd, currentBranch, isCommandAvailable, fileExists2, exec, getDefaultBranch2) {
71959
72317
  const gtAvailable = await isCommandAvailable("gt");
71960
72318
  if (!gtAvailable) return null;
71961
- const markerPresent = await fileExists2(join62(cwd, ".git", ".graphite_repo_config"));
72319
+ const markerPresent = await fileExists2(join63(cwd, ".git", ".graphite_repo_config"));
71962
72320
  if (!markerPresent) return null;
71963
72321
  const ancestor = await exec("gt", ["parent"], cwd).then((s2) => normalizeBranchName(s2)).catch(() => null);
71964
72322
  const childrenRaw = await exec("gt", ["children"], cwd).then((s2) => s2).catch(() => "");
@@ -71971,7 +72329,7 @@ async function detectGraphite(cwd, currentBranch, isCommandAvailable, fileExists
71971
72329
  return { ancestor: resolvedAncestor, descendants };
71972
72330
  }
71973
72331
  async function detectJujutsu(cwd, fileExists2, exec) {
71974
- const jjPresent = await fileExists2(join62(cwd, ".jj"));
72332
+ const jjPresent = await fileExists2(join63(cwd, ".jj"));
71975
72333
  if (!jjPresent) return null;
71976
72334
  const ancestorRaw = await exec(
71977
72335
  "jj",
@@ -72020,7 +72378,7 @@ function parseJjLines(raw) {
72020
72378
 
72021
72379
  // src/services/storage/annotation-store.ts
72022
72380
  import { mkdir as mkdir24, readFile as readFile35 } from "fs/promises";
72023
- import { dirname as dirname27, join as join63 } from "path";
72381
+ import { dirname as dirname27, join as join64 } from "path";
72024
72382
  var LegacyBaseFields = external_exports.object({
72025
72383
  commentId: external_exports.string(),
72026
72384
  body: external_exports.string(),
@@ -72047,7 +72405,7 @@ var LegacyPlanStoreSchema = external_exports.object({
72047
72405
  versions: external_exports.array(PlanVersionZodSchema)
72048
72406
  });
72049
72407
  function buildAnnotationStore(dataDir) {
72050
- const baseDir = join63(dataDir, "annotations");
72408
+ const baseDir = join64(dataDir, "annotations");
72051
72409
  const cache2 = /* @__PURE__ */ new Map();
72052
72410
  const listeners = /* @__PURE__ */ new Set();
72053
72411
  const writeQueues = /* @__PURE__ */ new Map();
@@ -72060,7 +72418,7 @@ function buildAnnotationStore(dataDir) {
72060
72418
  }
72061
72419
  }
72062
72420
  function filePath(taskId) {
72063
- return join63(baseDir, `${taskId}.json`);
72421
+ return join64(baseDir, `${taskId}.json`);
72064
72422
  }
72065
72423
  function emptyData() {
72066
72424
  return {
@@ -72125,9 +72483,9 @@ function buildAnnotationStore(dataDir) {
72125
72483
  }
72126
72484
  async function migrateLegacyFiles(taskId) {
72127
72485
  const [diffRaw, previewRaw, planRaw] = await Promise.all([
72128
- readLegacyFile(join63(dataDir, "diff-review", `${taskId}.json`)),
72129
- readLegacyFile(join63(dataDir, "preview-annotations", `${taskId}.json`)),
72130
- readLegacyFile(join63(dataDir, "plan-comments", `${taskId}.json`))
72486
+ readLegacyFile(join64(dataDir, "diff-review", `${taskId}.json`)),
72487
+ readLegacyFile(join64(dataDir, "preview-annotations", `${taskId}.json`)),
72488
+ readLegacyFile(join64(dataDir, "plan-comments", `${taskId}.json`))
72131
72489
  ]);
72132
72490
  if (diffRaw === null && previewRaw === null && planRaw === null) {
72133
72491
  return null;
@@ -72333,7 +72691,7 @@ function buildAnnotationStore(dataDir) {
72333
72691
 
72334
72692
  // src/services/storage/asset-store.ts
72335
72693
  import { mkdir as mkdir25, readFile as readFile36, rename as rename17, writeFile as writeFile24 } from "fs/promises";
72336
- import { join as join64 } from "path";
72694
+ import { join as join65 } from "path";
72337
72695
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
72338
72696
  function isValidAssetId(id) {
72339
72697
  return UUID_RE.test(id);
@@ -72359,10 +72717,10 @@ function buildAssetStore(assetsDir) {
72359
72717
  }
72360
72718
  }
72361
72719
  function binaryPath(assetId) {
72362
- return join64(assetsDir, `${assetId}.bin`);
72720
+ return join65(assetsDir, `${assetId}.bin`);
72363
72721
  }
72364
72722
  function metaPath(assetId) {
72365
- return join64(assetsDir, `${assetId}.meta.json`);
72723
+ return join65(assetsDir, `${assetId}.meta.json`);
72366
72724
  }
72367
72725
  function parseMeta(raw) {
72368
72726
  return AssetMetadataSchema.parse(JSON.parse(raw));
@@ -72386,8 +72744,8 @@ function buildAssetStore(assetsDir) {
72386
72744
  size: data.byteLength,
72387
72745
  createdAt: Date.now()
72388
72746
  };
72389
- const tmpBin = join64(assetsDir, `${assetId}.bin.tmp`);
72390
- const tmpMeta = join64(assetsDir, `${assetId}.meta.tmp`);
72747
+ const tmpBin = join65(assetsDir, `${assetId}.bin.tmp`);
72748
+ const tmpMeta = join65(assetsDir, `${assetId}.meta.tmp`);
72391
72749
  await writeFile24(tmpBin, data);
72392
72750
  await writeFile24(tmpMeta, JSON.stringify(metadata), "utf-8");
72393
72751
  await rename17(tmpBin, binaryPath(assetId));
@@ -72612,7 +72970,7 @@ function buildCredentialsVaultStore(filePath, corruptionLogger) {
72612
72970
 
72613
72971
  // src/services/storage/deliverable-store.ts
72614
72972
  import { mkdir as mkdir27, readFile as readFile38 } from "fs/promises";
72615
- import { dirname as dirname29, join as join65 } from "path";
72973
+ import { dirname as dirname29, join as join66 } from "path";
72616
72974
  var DELIVERABLE_STORE_VERSION = 1;
72617
72975
  var DeliverableStoreSchema = external_exports.object({
72618
72976
  schemaVersion: external_exports.number(),
@@ -72626,7 +72984,7 @@ function migrateDeliverableStore(raw) {
72626
72984
  return { schemaVersion: DELIVERABLE_STORE_VERSION, records: [] };
72627
72985
  }
72628
72986
  function buildDeliverableStore(dataDir) {
72629
- const baseDir = join65(dataDir, "deliverables");
72987
+ const baseDir = join66(dataDir, "deliverables");
72630
72988
  const cache2 = /* @__PURE__ */ new Map();
72631
72989
  const listeners = /* @__PURE__ */ new Set();
72632
72990
  const writeQueues = /* @__PURE__ */ new Map();
@@ -72639,7 +72997,7 @@ function buildDeliverableStore(dataDir) {
72639
72997
  }
72640
72998
  }
72641
72999
  function filePath(taskId) {
72642
- return join65(baseDir, `${taskId}.json`);
73000
+ return join66(baseDir, `${taskId}.json`);
72643
73001
  }
72644
73002
  function emptyData() {
72645
73003
  return { schemaVersion: DELIVERABLE_STORE_VERSION, records: [] };
@@ -72779,7 +73137,7 @@ function buildDeliverableStore(dataDir) {
72779
73137
 
72780
73138
  // src/services/storage/jsonl-conversation-store.ts
72781
73139
  import { appendFile as appendFile3, mkdir as mkdir28, open, readFile as readFile39, stat as stat17 } from "fs/promises";
72782
- import { join as join66 } from "path";
73140
+ import { join as join67 } from "path";
72783
73141
  var StoredMessageSchema = MessageSchema.omit({ seqNo: true, channelId: true });
72784
73142
  function logPerf(entry) {
72785
73143
  try {
@@ -72789,12 +73147,12 @@ function logPerf(entry) {
72789
73147
  }
72790
73148
  }
72791
73149
  function buildJsonlConversationStore(dataDir, opts = {}) {
72792
- const channelsDir = join66(dataDir, "channels");
73150
+ const channelsDir = join67(dataDir, "channels");
72793
73151
  const seqCounters = /* @__PURE__ */ new Map();
72794
73152
  const channelQueues = /* @__PURE__ */ new Map();
72795
73153
  const readCache = /* @__PURE__ */ new Map();
72796
73154
  function channelPath(channelId) {
72797
- return join66(channelsDir, `${channelId}.jsonl`);
73155
+ return join67(channelsDir, `${channelId}.jsonl`);
72798
73156
  }
72799
73157
  async function ensureDir() {
72800
73158
  await mkdir28(channelsDir, { recursive: true });
@@ -73566,7 +73924,7 @@ function buildProjectsStore(filePath) {
73566
73924
 
73567
73925
  // src/services/storage/rate-limit-store.ts
73568
73926
  import { mkdir as mkdir30, readFile as readFile41 } from "fs/promises";
73569
- import { dirname as dirname31, join as join67 } from "path";
73927
+ import { dirname as dirname31, join as join68 } from "path";
73570
73928
  var RATE_LIMIT_STORE_VERSION = 4;
73571
73929
  var RateLimitRecordSchema = external_exports.object({
73572
73930
  info: RateLimitInfoSchema,
@@ -73605,7 +73963,7 @@ function isWindowKey(x2) {
73605
73963
  return x2 !== void 0 && WINDOW_KEYS.has(x2);
73606
73964
  }
73607
73965
  async function buildRateLimitStore(dataDir, opts) {
73608
- const filePath = join67(dataDir, "rate-limits.json");
73966
+ const filePath = join68(dataDir, "rate-limits.json");
73609
73967
  const lockPath2 = `${filePath}.lock`;
73610
73968
  const initial = await loadStoreFile(filePath, opts.logger);
73611
73969
  const records = { ...initial.records };
@@ -73858,12 +74216,12 @@ async function atomicWrite4(filePath, data) {
73858
74216
  }
73859
74217
 
73860
74218
  // src/services/storage/schedule-store.ts
73861
- import { join as join68 } from "path";
74219
+ import { join as join69 } from "path";
73862
74220
  function buildScheduleStore(dataDir) {
73863
74221
  const store = buildJsonDocumentStore({
73864
74222
  storeName: "schedules",
73865
74223
  docType: "schedule",
73866
- filePath: join68(dataDir, "schedules.json"),
74224
+ filePath: join69(dataDir, "schedules.json"),
73867
74225
  recordSchema: ScheduleRecordSchema,
73868
74226
  currentVersion: SCHEDULE_STORE_VERSION,
73869
74227
  migrate(raw) {
@@ -73902,7 +74260,7 @@ function buildScheduleStore(dataDir) {
73902
74260
 
73903
74261
  // src/services/storage/session-persistence.ts
73904
74262
  import { mkdir as mkdir31, readdir as readdir20, readFile as readFile42, rm as rm11 } from "fs/promises";
73905
- import { join as join69 } from "path";
74263
+ import { join as join70 } from "path";
73906
74264
  var PersistedSessionSchema = external_exports.object({
73907
74265
  sessionId: external_exports.string(),
73908
74266
  channelId: external_exports.string(),
@@ -73959,9 +74317,9 @@ async function loadOneSessionFile(originalPath, corruptionLogger) {
73959
74317
  }
73960
74318
  }
73961
74319
  function buildSessionPersistence(dataDir, corruptionLogger) {
73962
- const channelsDir = join69(dataDir, "channels");
74320
+ const channelsDir = join70(dataDir, "channels");
73963
74321
  function sessionPath(channelId) {
73964
- return join69(channelsDir, `${channelId}.session.json`);
74322
+ return join70(channelsDir, `${channelId}.session.json`);
73965
74323
  }
73966
74324
  async function ensureDir() {
73967
74325
  await mkdir31(channelsDir, { recursive: true });
@@ -74019,7 +74377,7 @@ function buildSessionPersistence(dataDir, corruptionLogger) {
74019
74377
  if (i >= sessionEntries.length) return;
74020
74378
  const entry = sessionEntries[i];
74021
74379
  if (!entry) return;
74022
- results[i] = await loadOneSessionFile(join69(channelsDir, entry), corruptionLogger);
74380
+ results[i] = await loadOneSessionFile(join70(channelsDir, entry), corruptionLogger);
74023
74381
  }
74024
74382
  }
74025
74383
  const workers = Array.from(
@@ -74033,12 +74391,12 @@ function buildSessionPersistence(dataDir, corruptionLogger) {
74033
74391
  }
74034
74392
 
74035
74393
  // src/services/storage/template-store.ts
74036
- import { join as join70 } from "path";
74394
+ import { join as join71 } from "path";
74037
74395
  function buildTemplateStore(dataDir) {
74038
74396
  const store = buildJsonDocumentStore({
74039
74397
  storeName: "templates",
74040
74398
  docType: "template",
74041
- filePath: join70(dataDir, "templates.json"),
74399
+ filePath: join71(dataDir, "templates.json"),
74042
74400
  recordSchema: TaskTemplateRecordSchema,
74043
74401
  currentVersion: TEMPLATE_STORE_VERSION,
74044
74402
  migrate(raw) {
@@ -79770,7 +80128,7 @@ import { randomUUID as randomUUID21 } from "crypto";
79770
80128
  import { existsSync as existsSync10, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
79771
80129
  import { mkdir as mkdir33, readFile as readFile45, writeFile as writeFile26 } from "fs/promises";
79772
80130
  import { homedir as homedir10 } from "os";
79773
- import { dirname as dirname34, join as join72 } from "path";
80131
+ import { dirname as dirname34, join as join73 } from "path";
79774
80132
 
79775
80133
  // src/services/plan-backfill/plan-lazy-rebuild.ts
79776
80134
  var SETTLE_BUDGET_MS = 2e3;
@@ -80540,8 +80898,8 @@ function greedyLCS(a, b2) {
80540
80898
  // src/services/plan/plan-file-watcher.ts
80541
80899
  import { existsSync as existsSync9 } from "fs";
80542
80900
  import { homedir as homedir9 } from "os";
80543
- import { join as join71 } from "path";
80544
- var DEFAULT_PLANS_DIR = join71(homedir9(), ".claude", "plans");
80901
+ import { join as join72 } from "path";
80902
+ var DEFAULT_PLANS_DIR = join72(homedir9(), ".claude", "plans");
80545
80903
  var PLAN_WATCH_TIMEOUT_MS = 1e4;
80546
80904
  var PLAN_WATCH_DEBOUNCE_MS = 250;
80547
80905
  function createPlanFileWatcher(deps) {
@@ -81079,7 +81437,7 @@ var PlanHandler = class {
81079
81437
  * Called when a Write tool result mentions a file in ~/.claude/plans/.
81080
81438
  */
81081
81439
  trackCreatedFile(filePath) {
81082
- const plansDir = join72(homedir10(), ".claude", "plans");
81440
+ const plansDir = join73(homedir10(), ".claude", "plans");
81083
81441
  if (filePath.startsWith(plansDir) && filePath.endsWith(".md")) {
81084
81442
  this.#createdPlanFiles.add(filePath);
81085
81443
  this.#deps.log({
@@ -81111,7 +81469,7 @@ var PlanHandler = class {
81111
81469
  }
81112
81470
  async #runHandleCodexPlanReady(content) {
81113
81471
  if (this.#deps.isDisposed()) return;
81114
- const filePath = join72(getShipyardHome(), "plans", `${this.#deps.taskId}.md`);
81472
+ const filePath = join73(getShipyardHome(), "plans", `${this.#deps.taskId}.md`);
81115
81473
  try {
81116
81474
  await mkdir33(dirname34(filePath), { recursive: true });
81117
81475
  await writeFile26(filePath, content, "utf-8");
@@ -81590,12 +81948,12 @@ var PlanHandler = class {
81590
81948
  }).filter((f2) => f2 !== null).sort((a, b2) => b2.mtime - a.mtime)[0];
81591
81949
  return newest?.path ?? null;
81592
81950
  }
81593
- const plansDir = join72(homedir10(), ".claude", "plans");
81951
+ const plansDir = join73(homedir10(), ".claude", "plans");
81594
81952
  if (!existsSync10(plansDir)) return null;
81595
81953
  try {
81596
81954
  const files = readdirSync5(plansDir).filter((f2) => f2.endsWith(".md")).map((f2) => ({
81597
- path: join72(plansDir, f2),
81598
- mtime: statSync5(join72(plansDir, f2)).mtimeMs
81955
+ path: join73(plansDir, f2),
81956
+ mtime: statSync5(join73(plansDir, f2)).mtimeMs
81599
81957
  })).sort((a, b2) => b2.mtime - a.mtime);
81600
81958
  const recent = files[0];
81601
81959
  if (recent && Date.now() - recent.mtime < 3e4) return recent.path;
@@ -83822,7 +84180,7 @@ var RewindCheckpointHandler = class {
83822
84180
 
83823
84181
  // src/services/task/side-thread-registry.ts
83824
84182
  import { mkdir as mkdir34, readFile as readFile46, rename as rename19, writeFile as writeFile27 } from "fs/promises";
83825
- import { dirname as dirname35, join as join73 } from "path";
84183
+ import { dirname as dirname35, join as join74 } from "path";
83826
84184
  var ThreadFileSchema = external_exports.object({
83827
84185
  threads: external_exports.record(external_exports.string(), ThreadMetadataSchema)
83828
84186
  });
@@ -84198,7 +84556,7 @@ var SideThreadRegistry = class {
84198
84556
  }
84199
84557
  /** Persistence */
84200
84558
  #filePath() {
84201
- return join73(this.#deps.dataDir, "threads", `${this.#deps.taskId}.json`);
84559
+ return join74(this.#deps.dataDir, "threads", `${this.#deps.taskId}.json`);
84202
84560
  }
84203
84561
  async #ensureLoaded() {
84204
84562
  if (this.#loadedFromDisk.has(this.#deps.taskId)) return;
@@ -87115,7 +87473,7 @@ async function resolveTaskListPrepend(ctx) {
87115
87473
  import { existsSync as existsSync11 } from "fs";
87116
87474
  import { readdir as readdir21, readFile as readFile47 } from "fs/promises";
87117
87475
  import { homedir as homedir11 } from "os";
87118
- import { basename as basename13, dirname as dirname36, join as join74 } from "path";
87476
+ import { basename as basename13, dirname as dirname36, join as join75 } from "path";
87119
87477
  var VALID_STATUSES = /* @__PURE__ */ new Set(["pending", "in_progress", "completed"]);
87120
87478
  var IDLE_DETACH_MS = 5 * 60 * 1e3;
87121
87479
  var IDLE_SWEEP_INTERVAL_MS = 6e4;
@@ -87131,7 +87489,7 @@ function isCCTaskFile(value) {
87131
87489
  }
87132
87490
  function createCCTaskFileWatcher(listId, log) {
87133
87491
  const sanitizedId = sanitize(listId);
87134
- const dir = join74(homedir11(), ".claude", "tasks", sanitizedId);
87492
+ const dir = join75(homedir11(), ".claude", "tasks", sanitizedId);
87135
87493
  const targetDirName = basename13(dir);
87136
87494
  const parentDir = dirname36(dir);
87137
87495
  const watchState = {
@@ -87167,7 +87525,7 @@ function createCCTaskFileWatcher(listId, log) {
87167
87525
  return tasks;
87168
87526
  }
87169
87527
  async function readTask(taskId) {
87170
- const filePath = join74(dir, `${taskId}.json`);
87528
+ const filePath = join75(dir, `${taskId}.json`);
87171
87529
  try {
87172
87530
  const raw = await readFile47(filePath, "utf-8");
87173
87531
  const parsed = JSON.parse(raw);
@@ -87234,7 +87592,7 @@ function createCCTaskFileWatcher(listId, log) {
87234
87592
  watchState.parentSub = null;
87235
87593
  return;
87236
87594
  }
87237
- if (ev.path.endsWith(targetDirName) || ev.path === join74(parentDir, targetDirName)) {
87595
+ if (ev.path.endsWith(targetDirName) || ev.path === join75(parentDir, targetDirName)) {
87238
87596
  void ensureDirWatcher();
87239
87597
  break;
87240
87598
  }
@@ -87351,7 +87709,7 @@ function createCCTaskFileWatcher(listId, log) {
87351
87709
  // src/services/task/cc-task-file-writer.ts
87352
87710
  import { createHash as createHash13 } from "crypto";
87353
87711
  import { mkdir as mkdir35, readdir as readdir22, rename as rename20, unlink as unlink14, writeFile as writeFile28 } from "fs/promises";
87354
- import { join as join75 } from "path";
87712
+ import { join as join76 } from "path";
87355
87713
  function contentHash2(data) {
87356
87714
  return createHash13("sha256").update(data).digest("hex").slice(0, 16);
87357
87715
  }
@@ -87367,7 +87725,7 @@ async function writeTasks(dir, tasks, hashes) {
87367
87725
  const json = JSON.stringify(structuredTaskToCCFile(task), null, 2);
87368
87726
  const hash = contentHash2(json);
87369
87727
  if (hashes.get(id) === hash) continue;
87370
- await atomicWriteFile2(join75(dir, `${id}.json`), json);
87728
+ await atomicWriteFile2(join76(dir, `${id}.json`), json);
87371
87729
  hashes.set(id, hash);
87372
87730
  }
87373
87731
  return targetIds;
@@ -87383,7 +87741,7 @@ async function removeStaleFiles(dir, targetIds, hashes) {
87383
87741
  if (!entry.endsWith(".json")) continue;
87384
87742
  const id = entry.slice(0, -".json".length);
87385
87743
  if (!targetIds.has(id) && hashes.has(id)) {
87386
- await unlink14(join75(dir, entry)).catch(() => {
87744
+ await unlink14(join76(dir, entry)).catch(() => {
87387
87745
  });
87388
87746
  hashes.delete(id);
87389
87747
  }
@@ -88015,10 +88373,10 @@ var StructuredTaskTracker = class {
88015
88373
  import { unwatchFile, watchFile } from "fs";
88016
88374
  import { open as open2 } from "fs/promises";
88017
88375
  import { homedir as homedir12 } from "os";
88018
- import { join as join76 } from "path";
88376
+ import { join as join77 } from "path";
88019
88377
  function computeTranscriptPath(cwd, sessionId, taskId) {
88020
88378
  const cwdSlug = cwd.replace(/[^a-zA-Z0-9]/g, "-").slice(0, 200);
88021
- return join76(
88379
+ return join77(
88022
88380
  homedir12(),
88023
88381
  ".claude",
88024
88382
  "projects",
@@ -88733,7 +89091,7 @@ import { createHash as createHash14 } from "crypto";
88733
89091
  import { unlink as unlink15, writeFile as writeFile29 } from "fs/promises";
88734
89092
  import { createRequire as createRequire4 } from "module";
88735
89093
  import { tmpdir } from "os";
88736
- import { join as join77 } from "path";
89094
+ import { join as join78 } from "path";
88737
89095
  function spawnCollect(bin, args, opts) {
88738
89096
  return new Promise((resolve12, reject) => {
88739
89097
  const child = spawn12(bin, args, {
@@ -88791,7 +89149,7 @@ function resolveDaemonNodeModules(log) {
88791
89149
  } catch {
88792
89150
  }
88793
89151
  try {
88794
- const cwdRequire = createRequire4(join77(process.cwd(), "package.json"));
89152
+ const cwdRequire = createRequire4(join78(process.cwd(), "package.json"));
88795
89153
  const sdkPath = cwdRequire.resolve("@modelcontextprotocol/sdk/server/mcp.js");
88796
89154
  const idx = sdkPath.lastIndexOf("node_modules");
88797
89155
  if (idx >= 0) return sdkPath.slice(0, idx + "node_modules".length);
@@ -88801,7 +89159,7 @@ function resolveDaemonNodeModules(log) {
88801
89159
  error: err3 instanceof Error ? err3.message : String(err3)
88802
89160
  });
88803
89161
  }
88804
- return join77(process.cwd(), "node_modules");
89162
+ return join78(process.cwd(), "node_modules");
88805
89163
  }
88806
89164
  function planTokenCount(systemPrompt, metadata, cwd, model, mcpServers) {
88807
89165
  return {
@@ -89000,7 +89358,7 @@ async function serializeMcpConfig(servers, allToolNames, daemonNodeModules, rese
89000
89358
  if (isReservedMcpServerName(serverName, reservedServerNames)) continue;
89001
89359
  const script = buildStubServerScript(serverName, tools);
89002
89360
  if (!script) continue;
89003
- const stubPath = join77(tmpdir(), `shipyard-mcp-stub-${serverName}-${Date.now()}.cjs`);
89361
+ const stubPath = join78(tmpdir(), `shipyard-mcp-stub-${serverName}-${Date.now()}.cjs`);
89004
89362
  await writeFile29(stubPath, script, "utf-8");
89005
89363
  stubPaths.push(stubPath);
89006
89364
  mcpServers[serverName] = {
@@ -89026,7 +89384,7 @@ async function prepareTokenCountArgs(plan, log, reservedServerNames = SDK_RESERV
89026
89384
  reservedServerNames
89027
89385
  );
89028
89386
  if (!serialized.config) return { args, mcpConfigPath: null, stubPaths: serialized.stubPaths };
89029
- const mcpConfigPath = join77(tmpdir(), `shipyard-mcp-${Date.now()}.json`);
89387
+ const mcpConfigPath = join78(tmpdir(), `shipyard-mcp-${Date.now()}.json`);
89030
89388
  await writeFile29(mcpConfigPath, JSON.stringify(serialized.config), "utf-8");
89031
89389
  args.push("--mcp-config", mcpConfigPath);
89032
89390
  return { args, mcpConfigPath, stubPaths: serialized.stubPaths };
@@ -95122,7 +95480,7 @@ async function createDaemon(deps) {
95122
95480
  const annotationStore = buildAnnotationStore(deps.dataDir);
95123
95481
  const deliverableStore = buildDeliverableStore(deps.dataDir);
95124
95482
  const resourceRegistry = createResourceRegistry();
95125
- const assetStore = buildAssetStore(join78(deps.dataDir, "assets"));
95483
+ const assetStore = buildAssetStore(join79(deps.dataDir, "assets"));
95126
95484
  const shipyardResolver = createShipyardResolver();
95127
95485
  shipyardResolver.addSubResolver("asset", createAssetResolver(assetStore));
95128
95486
  const pluginResourceResolver = new PluginResourceResolver(deps.log);
@@ -95149,10 +95507,10 @@ async function createDaemon(deps) {
95149
95507
  workspaceRoot: deps.workspaceRoot,
95150
95508
  log: deps.log
95151
95509
  });
95152
- const userSettingsStore = buildUserSettingsStore(join78(deps.dataDir, "user-settings.json"));
95153
- const projectsStore = buildProjectsStore(join78(deps.dataDir, "projects.json"));
95510
+ const userSettingsStore = buildUserSettingsStore(join79(deps.dataDir, "user-settings.json"));
95511
+ const projectsStore = buildProjectsStore(join79(deps.dataDir, "projects.json"));
95154
95512
  const credentialsVaultStore = buildCredentialsVaultStore(
95155
- join78(deps.dataDir, "credentials-vault.json")
95513
+ join79(deps.dataDir, "credentials-vault.json")
95156
95514
  );
95157
95515
  const cursorVaultKeyRef = { current: null };
95158
95516
  let resolveCursorVaultKey;
@@ -95600,7 +95958,7 @@ async function createDaemon(deps) {
95600
95958
  taskId: args.taskId,
95601
95959
  agentSystem: args.agentSystem,
95602
95960
  environmentKey: args.cwd,
95603
- pluginsDir: join78(deps.shipyardHome, "plugins"),
95961
+ pluginsDir: join79(deps.shipyardHome, "plugins"),
95604
95962
  vizWatcher: getOrCreateVizWatcher(args.taskId).watcher,
95605
95963
  mode: "task",
95606
95964
  previewProxy: deps.previewProxy,
@@ -95738,7 +96096,7 @@ async function createDaemon(deps) {
95738
96096
  /** Claude in-process harness — Codex uses the HTTP path with 'codex' above. */
95739
96097
  agentSystem: AGENT_SYSTEM_CLAUDE_CODE,
95740
96098
  environmentKey: args.cwd,
95741
- pluginsDir: join78(deps.shipyardHome, "plugins"),
96099
+ pluginsDir: join79(deps.shipyardHome, "plugins"),
95742
96100
  vizWatcher: args.vizWatcher,
95743
96101
  mode: args.mode ?? "task",
95744
96102
  previewProxy: deps.previewProxy,
@@ -96772,7 +97130,7 @@ async function createDaemon(deps) {
96772
97130
  deps.metricsCollector
96773
97131
  );
96774
97132
  const planDocGapReporter = startPlanDocPersistenceGapReporter({
96775
- loroDir: join78(deps.dataDir, "loro"),
97133
+ loroDir: join79(deps.dataDir, "loro"),
96776
97134
  listTasks: () => taskStateStore.listTasks(),
96777
97135
  log: deps.log,
96778
97136
  metricsCollector: deps.metricsCollector
@@ -96788,7 +97146,7 @@ async function createDaemon(deps) {
96788
97146
  const stallProfilerConfig = getStallProfilerConfig();
96789
97147
  const stallProfiler = new StallProfiler({
96790
97148
  log: deps.log,
96791
- outDir: join78(deps.dataDir, "stall-profiles"),
97149
+ outDir: join79(deps.dataDir, "stall-profiles"),
96792
97150
  thresholdMs: stallProfilerConfig.thresholdMs,
96793
97151
  captureMs: stallProfilerConfig.captureMs,
96794
97152
  rateLimitMs: stallProfilerConfig.rateLimitMs
@@ -97426,7 +97784,7 @@ import { existsSync as existsSync13 } from "fs";
97426
97784
  import { execFile as execFile11, spawn as spawn13 } from "child_process";
97427
97785
  import { createHash as createHash15 } from "crypto";
97428
97786
  import { chmod as chmod3, mkdir as mkdir37, readFile as readFile48, rename as rename21, unlink as unlink16, writeFile as writeFile30 } from "fs/promises";
97429
- import { join as join79 } from "path";
97787
+ import { join as join80 } from "path";
97430
97788
 
97431
97789
  // src/services/bootstrap/self-update-installer-scripts.ts
97432
97790
  function buildPosixInstallerScript(params) {
@@ -97828,7 +98186,7 @@ async function downloadTarball(url, destPath, fetchFn) {
97828
98186
  throw new Error(`download failed: HTTP ${response.status} ${response.statusText}`);
97829
98187
  }
97830
98188
  const bytes = new Uint8Array(await response.arrayBuffer());
97831
- await mkdir37(join79(destPath, ".."), { recursive: true });
98189
+ await mkdir37(join80(destPath, ".."), { recursive: true });
97832
98190
  try {
97833
98191
  await writeFile30(tmpPath, bytes);
97834
98192
  await rename21(tmpPath, destPath);
@@ -97858,7 +98216,7 @@ async function verifyChecksum(path5, expectedHash) {
97858
98216
  }
97859
98217
  }
97860
98218
  async function stageInstallerScript(scriptPath, params) {
97861
- await mkdir37(join79(scriptPath, ".."), { recursive: true });
98219
+ await mkdir37(join80(scriptPath, ".."), { recursive: true });
97862
98220
  const body = process.platform === "win32" ? buildWindowsInstallerScript(params) : buildPosixInstallerScript(params);
97863
98221
  await writeFile30(scriptPath, body);
97864
98222
  await chmod3(scriptPath, 493);
@@ -97908,16 +98266,16 @@ function buildInstallerParams(state, deps) {
97908
98266
  targetVersion: state.resolved.version,
97909
98267
  previousVersion: deps.currentVersion,
97910
98268
  parentPid: deps.pid,
97911
- statusFilePath: join79(deps.shipyardHome, "update-status.json"),
97912
- pidFilePath: join79(deps.shipyardHome, "daemon.pid"),
97913
- logPath: join79(deps.shipyardHome, "updates", `install-${deps.pid}.log`),
97914
- snapshotPath: join79(deps.shipyardHome, "updates", `rollback-${deps.currentVersion}`),
98269
+ statusFilePath: join80(deps.shipyardHome, "update-status.json"),
98270
+ pidFilePath: join80(deps.shipyardHome, "daemon.pid"),
98271
+ logPath: join80(deps.shipyardHome, "updates", `install-${deps.pid}.log`),
98272
+ snapshotPath: join80(deps.shipyardHome, "updates", `rollback-${deps.currentVersion}`),
97915
98273
  npmBin: "npm"
97916
98274
  };
97917
98275
  }
97918
98276
  function tarballPathFor(shipyardHome, version, shasum) {
97919
98277
  const shaPrefix = shasum.slice(0, 12);
97920
- return join79(shipyardHome, "updates", `${version}-${shaPrefix}.tgz`);
98278
+ return join80(shipyardHome, "updates", `${version}-${shaPrefix}.tgz`);
97921
98279
  }
97922
98280
  async function runResolveStep(step, state, deps) {
97923
98281
  const resolver = deps.resolveVersion ?? defaultResolveVersion;
@@ -99005,7 +99363,7 @@ function buildLocalDirectChannelCallbacks(deps) {
99005
99363
 
99006
99364
  // src/services/skills-cache/codex-skills-cache.ts
99007
99365
  import { mkdir as mkdir38, readFile as readFile49, rename as rename22, writeFile as writeFile31 } from "fs/promises";
99008
- import { dirname as dirname38, join as join80 } from "path";
99366
+ import { dirname as dirname38, join as join81 } from "path";
99009
99367
  var CACHE_FILENAME = "codex-skills-cache.json";
99010
99368
  var SkillInfoCacheEntrySchema = external_exports.object({
99011
99369
  name: external_exports.string(),
@@ -99022,7 +99380,7 @@ var CodexSkillsCacheFileSchema = external_exports.object({
99022
99380
  skills: external_exports.array(SkillInfoCacheEntrySchema)
99023
99381
  });
99024
99382
  function defaultCodexSkillsCachePath(shipyardHome) {
99025
- return join80(shipyardHome, "state", CACHE_FILENAME);
99383
+ return join81(shipyardHome, "state", CACHE_FILENAME);
99026
99384
  }
99027
99385
  async function loadCodexSkillsCache(path5) {
99028
99386
  let raw;
@@ -99086,11 +99444,11 @@ async function saveCodexSkillsCache(path5, cache2) {
99086
99444
  // src/services/skills-mirror/manager.ts
99087
99445
  import { lstat, mkdir as mkdir40, readlink, stat as stat18, symlink, unlink as unlink18 } from "fs/promises";
99088
99446
  import { homedir as homedir14 } from "os";
99089
- import { dirname as dirname40, join as join82 } from "path";
99447
+ import { dirname as dirname40, join as join83 } from "path";
99090
99448
 
99091
99449
  // src/services/skills-mirror/manifest.ts
99092
99450
  import { mkdir as mkdir39, readFile as readFile50, rename as rename23, unlink as unlink17, writeFile as writeFile32 } from "fs/promises";
99093
- import { dirname as dirname39, join as join81 } from "path";
99451
+ import { dirname as dirname39, join as join82 } from "path";
99094
99452
  var MANIFEST_VERSION = 1;
99095
99453
  var ManifestEntrySchema = external_exports.object({
99096
99454
  /** Display name of the skill (e.g. `qa`, or `plugin:name` when namespaced). */
@@ -99110,7 +99468,7 @@ function emptyManifest() {
99110
99468
  return { version: MANIFEST_VERSION, entries: [] };
99111
99469
  }
99112
99470
  function defaultManifestPath(shipyardHome) {
99113
- return join81(shipyardHome, "state", "skill-mirror-manifest.json");
99471
+ return join82(shipyardHome, "state", "skill-mirror-manifest.json");
99114
99472
  }
99115
99473
  async function loadManifest(path5) {
99116
99474
  try {
@@ -99141,15 +99499,15 @@ async function deleteManifest(path5) {
99141
99499
  function defaultPaths() {
99142
99500
  const home = homedir14();
99143
99501
  return {
99144
- claudeSkillsDir: join82(home, ".claude", "skills"),
99145
- codexSkillsDir: join82(home, ".agents", "skills"),
99502
+ claudeSkillsDir: join83(home, ".claude", "skills"),
99503
+ codexSkillsDir: join83(home, ".agents", "skills"),
99146
99504
  manifestPath: defaultManifestPath(getShipyardHome())
99147
99505
  };
99148
99506
  }
99149
99507
  function plannedSymlinkPath(skill, paths) {
99150
99508
  const linkName = skill.namespace ? `${skill.namespace}:${skill.name}` : skill.name;
99151
- if (skill.sourceAgent === "claude-code") return join82(paths.codexSkillsDir, linkName);
99152
- if (skill.sourceAgent === "codex") return join82(paths.claudeSkillsDir, linkName);
99509
+ if (skill.sourceAgent === "claude-code") return join83(paths.codexSkillsDir, linkName);
99510
+ if (skill.sourceAgent === "codex") return join83(paths.claudeSkillsDir, linkName);
99153
99511
  return null;
99154
99512
  }
99155
99513
  function isPermissionError(err3) {
@@ -99362,7 +99720,7 @@ async function ensureMirror(skills, config, paths = defaultPaths()) {
99362
99720
 
99363
99721
  // src/services/skills-mirror/prefs.ts
99364
99722
  import { mkdir as mkdir41, readFile as readFile51, rename as rename24, writeFile as writeFile33 } from "fs/promises";
99365
- import { dirname as dirname41, join as join83 } from "path";
99723
+ import { dirname as dirname41, join as join84 } from "path";
99366
99724
  var SkillsMirrorPrefsSchema = external_exports.object({
99367
99725
  enabled: external_exports.boolean().default(false)
99368
99726
  });
@@ -99371,7 +99729,7 @@ function defaultSkillsMirrorPrefs() {
99371
99729
  return { enabled: false };
99372
99730
  }
99373
99731
  function prefsPath(dataDir) {
99374
- return join83(dataDir, FILENAME);
99732
+ return join84(dataDir, FILENAME);
99375
99733
  }
99376
99734
  async function loadSkillsMirrorPrefs(dataDir) {
99377
99735
  try {
@@ -99420,7 +99778,7 @@ async function reconcileSkillsMirror(args) {
99420
99778
  // src/services/storage/daemon-settings-store.ts
99421
99779
  import { createHash as createHash16 } from "crypto";
99422
99780
  import { mkdir as mkdir42, readFile as readFile52 } from "fs/promises";
99423
- import { join as join84 } from "path";
99781
+ import { join as join85 } from "path";
99424
99782
  var ProjectSettingsSchema = external_exports.object({
99425
99783
  disabledMcpServers: external_exports.array(external_exports.string()).optional(),
99426
99784
  previewMigrationV36Done: external_exports.boolean().default(false)
@@ -99429,9 +99787,9 @@ function hashProjectPath(projectPath) {
99429
99787
  return createHash16("sha256").update(projectPath).digest("hex").slice(0, 16);
99430
99788
  }
99431
99789
  function buildDaemonSettingsStore(dataDir) {
99432
- const settingsDir = join84(dataDir, "settings");
99790
+ const settingsDir = join85(dataDir, "settings");
99433
99791
  function settingsPath(projectPath) {
99434
- return join84(settingsDir, `${hashProjectPath(projectPath)}.json`);
99792
+ return join85(settingsDir, `${hashProjectPath(projectPath)}.json`);
99435
99793
  }
99436
99794
  async function ensureDir() {
99437
99795
  await mkdir42(settingsDir, { recursive: true });
@@ -99456,12 +99814,12 @@ function buildDaemonSettingsStore(dataDir) {
99456
99814
 
99457
99815
  // src/services/storage/plugin-config-store.ts
99458
99816
  import { mkdir as mkdir43, readFile as readFile53 } from "fs/promises";
99459
- import { join as join85 } from "path";
99817
+ import { join as join86 } from "path";
99460
99818
  function buildPluginConfigStore(pluginsDir) {
99461
99819
  const cache2 = /* @__PURE__ */ new Map();
99462
99820
  const writeQueues = /* @__PURE__ */ new Map();
99463
99821
  function configPath(pluginId) {
99464
- return join85(pluginsDir, pluginId, "config.json");
99822
+ return join86(pluginsDir, pluginId, "config.json");
99465
99823
  }
99466
99824
  async function readConfigFromDisk(pluginId) {
99467
99825
  const fp = configPath(pluginId);
@@ -99515,7 +99873,7 @@ function buildPluginConfigStore(pluginsDir) {
99515
99873
  const fresh = await readConfigFromDisk(pluginId);
99516
99874
  fresh[key] = value;
99517
99875
  cache2.set(pluginId, fresh);
99518
- await mkdir43(join85(pluginsDir, pluginId), { recursive: true });
99876
+ await mkdir43(join86(pluginsDir, pluginId), { recursive: true });
99519
99877
  await atomicWriteFile(configPath(pluginId), JSON.stringify(fresh, null, 2));
99520
99878
  })
99521
99879
  );
@@ -99761,7 +100119,7 @@ async function serve(options = {}) {
99761
100119
  }
99762
100120
  async function runServeBody(options, captureRefs) {
99763
100121
  const shipyardHome = options.shipyardHome ?? getShipyardHome();
99764
- const dataDir = join86(shipyardHome, options.isDev ? "data-dev" : "data");
100122
+ const dataDir = join87(shipyardHome, options.isDev ? "data-dev" : "data");
99765
100123
  const log = createChildLogger({ mode: "serve" });
99766
100124
  log.info(
99767
100125
  { event: "daemon_start", version: getDaemonVersion(), pid: process.pid },
@@ -99769,7 +100127,7 @@ async function runServeBody(options, captureRefs) {
99769
100127
  );
99770
100128
  const { workspaceRoot, unscopedWorkspace, unscopedReason } = await resolveWorkspaceScope();
99771
100129
  registerBuiltinPlugins();
99772
- const pluginConfigStore = buildPluginConfigStore(join86(dataDir, "plugins"));
100130
+ const pluginConfigStore = buildPluginConfigStore(join87(dataDir, "plugins"));
99773
100131
  await mkdir44(dataDir, { recursive: true });
99774
100132
  log.info(
99775
100133
  {
@@ -99862,9 +100220,9 @@ async function runServeBody(options, captureRefs) {
99862
100220
  await bootstrapPhase("pid_sweep_orphans", () => pidTracker.sweepOrphans(logAdapter));
99863
100221
  await bootstrapPhase(
99864
100222
  "legacy_epoch_prune",
99865
- () => pruneOldEpochData(join86(dataDir, "loro"), logAdapter)
100223
+ () => pruneOldEpochData(join87(dataDir, "loro"), logAdapter)
99866
100224
  );
99867
- const storage = new FileStorageAdapter(join86(dataDir, "loro"));
100225
+ const storage = new FileStorageAdapter(join87(dataDir, "loro"));
99868
100226
  const personalWebrtcAdapter = new WebRtcDataChannelAdapter();
99869
100227
  const peerRoleRegistry = createPeerRoleRegistry();
99870
100228
  const presencePoolRef = { pool: {} };
@@ -99973,14 +100331,14 @@ async function runServeBody(options, captureRefs) {
99973
100331
  current: []
99974
100332
  };
99975
100333
  const terminalPtys = /* @__PURE__ */ new Map();
99976
- const terminalLogsDir = join86(shipyardHome, "data", "terminals");
100334
+ const terminalLogsDir = join87(shipyardHome, "data", "terminals");
99977
100335
  await mkdir44(terminalLogsDir, { recursive: true, mode: 448 });
99978
100336
  const publishedArtifactStore = createPublishedArtifactStore({
99979
- rootDir: join86(dataDir, "published"),
100337
+ rootDir: join87(dataDir, "published"),
99980
100338
  log: logAdapter
99981
100339
  });
99982
100340
  const previewStateStore = createPreviewStateStore({
99983
- rootDir: join86(dataDir, "preview-state"),
100341
+ rootDir: join87(dataDir, "preview-state"),
99984
100342
  logger: log
99985
100343
  });
99986
100344
  let codexSkillsApplyScopedPatchRef = null;
@@ -100065,7 +100423,7 @@ async function runServeBody(options, captureRefs) {
100065
100423
  daemon.healthMetrics.start();
100066
100424
  publishedArtifactStore.setSendControlMessage((msg) => daemon.taskManager.broadcastControl(msg));
100067
100425
  previewStateStore.setSendControlMessage((msg) => daemon.taskManager.broadcastControl(msg));
100068
- const pluginsDir = join86(shipyardHome, "plugins");
100426
+ const pluginsDir = join87(shipyardHome, "plugins");
100069
100427
  await mkdir44(pluginsDir, { recursive: true });
100070
100428
  let loadedPlugins = [];
100071
100429
  const loadedPluginsRef = {
@@ -100261,6 +100619,7 @@ async function runServeBody(options, captureRefs) {
100261
100619
  }
100262
100620
  });
100263
100621
  const fileIOHandlers = /* @__PURE__ */ new Set();
100622
+ const collabHostingStore = buildCollabHostingStore(dataDir);
100264
100623
  const collabRoomManager = signalingHandle ? buildCollabRoomManager({
100265
100624
  peerRoleRegistry,
100266
100625
  repo,
@@ -100281,13 +100640,22 @@ async function runServeBody(options, captureRefs) {
100281
100640
  fileWatcherPool,
100282
100641
  terminalPtys,
100283
100642
  terminalLogsDir,
100284
- housekeepingBarrier
100643
+ housekeepingBarrier,
100644
+ collabHostingStore
100285
100645
  }) : null;
100286
100646
  if (collabRoomManager) {
100287
100647
  daemon.setCollabParticipantsProvider(
100288
100648
  (taskId) => collabRoomManager.getParticipantsForTask(taskId)
100289
100649
  );
100290
100650
  daemon.setCollabActiveTaskIdsProvider(() => collabRoomManager.listActiveTaskIds());
100651
+ startCollabRehydrate({
100652
+ collabRoomManager,
100653
+ store: collabHostingStore,
100654
+ signalingBaseUrl: auth3.signalingUrl,
100655
+ userToken: auth3.token,
100656
+ machineId: daemonPeerId,
100657
+ log: logAdapter
100658
+ });
100291
100659
  }
100292
100660
  const portDetectorRef = { current: null };
100293
100661
  const onAttachDataChannelFailed = createAttachFailureCanary(log, logAdapter);
@@ -100626,4 +100994,4 @@ export {
100626
100994
  decideWorkspaceScope,
100627
100995
  serve
100628
100996
  };
100629
- //# sourceMappingURL=serve-RW42I4NC.js.map
100997
+ //# sourceMappingURL=serve-25I4ML7R.js.map