@superblocksteam/vite-plugin-file-sync 2.0.156-next.3 → 2.0.156

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts +2 -9
  2. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
  3. package/dist/ai-service/agent/prompts/build-base-system-prompt.js +6 -34
  4. package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
  5. package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts +1 -2
  6. package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts.map +1 -1
  7. package/dist/ai-service/agent/prompts/build-security-scan-prompt.js +9 -52
  8. package/dist/ai-service/agent/prompts/build-security-scan-prompt.js.map +1 -1
  9. package/dist/ai-service/agent/tool-permission-delivery.d.ts +16 -25
  10. package/dist/ai-service/agent/tool-permission-delivery.d.ts.map +1 -1
  11. package/dist/ai-service/agent/tool-permission-delivery.js +38 -384
  12. package/dist/ai-service/agent/tool-permission-delivery.js.map +1 -1
  13. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts +35 -9
  14. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts.map +1 -1
  15. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js +98 -71
  16. package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js.map +1 -1
  17. package/dist/ai-service/agent/tools/apis/test-api.d.ts.map +1 -1
  18. package/dist/ai-service/agent/tools/apis/test-api.js +2 -3
  19. package/dist/ai-service/agent/tools/apis/test-api.js.map +1 -1
  20. package/dist/ai-service/agent/tools/app-environment/update-app-environment.d.ts +1 -1
  21. package/dist/ai-service/agent/tools/app-environment/update-app-environment.d.ts.map +1 -1
  22. package/dist/ai-service/agent/tools/app-environment/update-app-environment.js +1 -13
  23. package/dist/ai-service/agent/tools/app-environment/update-app-environment.js.map +1 -1
  24. package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts.map +1 -1
  25. package/dist/ai-service/agent/tools/build-capture-screenshot.js +3 -8
  26. package/dist/ai-service/agent/tools/build-capture-screenshot.js.map +1 -1
  27. package/dist/ai-service/agent/tools/build-navigate-preview.d.ts.map +1 -1
  28. package/dist/ai-service/agent/tools/build-navigate-preview.js +2 -4
  29. package/dist/ai-service/agent/tools/build-navigate-preview.js.map +1 -1
  30. package/dist/ai-service/agent/tools/build-reload-file.d.ts.map +1 -1
  31. package/dist/ai-service/agent/tools/build-reload-file.js +2 -4
  32. package/dist/ai-service/agent/tools/build-reload-file.js.map +1 -1
  33. package/dist/ai-service/agent/tools/get-logs.d.ts +2 -2
  34. package/dist/ai-service/agent/tools/integrations/delete-integration.d.ts.map +1 -1
  35. package/dist/ai-service/agent/tools/integrations/delete-integration.js +3 -6
  36. package/dist/ai-service/agent/tools/integrations/delete-integration.js.map +1 -1
  37. package/dist/ai-service/agent/tools/integrations/execute-request.d.ts +13 -13
  38. package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts +6 -18
  39. package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts.map +1 -1
  40. package/dist/ai-service/agent/tools/integrations/integration-setup.js +15 -41
  41. package/dist/ai-service/agent/tools/integrations/integration-setup.js.map +1 -1
  42. package/dist/ai-service/agent/tools/integrations/internal.d.ts.map +1 -1
  43. package/dist/ai-service/agent/tools/integrations/internal.js +2 -4
  44. package/dist/ai-service/agent/tools/integrations/internal.js.map +1 -1
  45. package/dist/ai-service/agent/tools/report-security-findings.d.ts +23 -23
  46. package/dist/ai-service/agent/tools.d.ts +2 -4
  47. package/dist/ai-service/agent/tools.d.ts.map +1 -1
  48. package/dist/ai-service/agent/tools.js +14 -21
  49. package/dist/ai-service/agent/tools.js.map +1 -1
  50. package/dist/ai-service/agent/tools2/tools/git.d.ts +2 -2
  51. package/dist/ai-service/index.d.ts +25 -26
  52. package/dist/ai-service/index.d.ts.map +1 -1
  53. package/dist/ai-service/index.js +121 -340
  54. package/dist/ai-service/index.js.map +1 -1
  55. package/dist/ai-service/integrations/store.d.ts +1 -2
  56. package/dist/ai-service/integrations/store.d.ts.map +1 -1
  57. package/dist/ai-service/integrations/store.js +2 -3
  58. package/dist/ai-service/integrations/store.js.map +1 -1
  59. package/dist/ai-service/judge/tools/playwright-action.d.ts +1 -1
  60. package/dist/ai-service/judge/tools/submit-feedback.d.ts +1 -1
  61. package/dist/ai-service/llm/client.d.ts +3 -9
  62. package/dist/ai-service/llm/client.d.ts.map +1 -1
  63. package/dist/ai-service/llm/client.js +3 -10
  64. package/dist/ai-service/llm/client.js.map +1 -1
  65. package/dist/ai-service/llm/context-v2/context-metrics.d.ts.map +1 -1
  66. package/dist/ai-service/llm/context-v2/context-metrics.js +0 -3
  67. package/dist/ai-service/llm/context-v2/context-metrics.js.map +1 -1
  68. package/dist/ai-service/llm/stream/observers/llmobs.d.ts +6 -6
  69. package/dist/ai-service/llm/stream/observers/llmobs.d.ts.map +1 -1
  70. package/dist/ai-service/llm/stream/observers/llmobs.js +9 -10
  71. package/dist/ai-service/llm/stream/observers/llmobs.js.map +1 -1
  72. package/dist/ai-service/llm/stream/orchestrator.d.ts +0 -4
  73. package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
  74. package/dist/ai-service/llm/stream/orchestrator.js +41 -19
  75. package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
  76. package/dist/ai-service/llm/stream/retry-engine.d.ts +1 -1
  77. package/dist/ai-service/llm/stream/retry-engine.d.ts.map +1 -1
  78. package/dist/ai-service/llm/stream/retry-engine.js +1 -26
  79. package/dist/ai-service/llm/stream/retry-engine.js.map +1 -1
  80. package/dist/ai-service/policy-agent-metrics.d.ts +0 -10
  81. package/dist/ai-service/policy-agent-metrics.d.ts.map +1 -1
  82. package/dist/ai-service/policy-agent-metrics.js +0 -33
  83. package/dist/ai-service/policy-agent-metrics.js.map +1 -1
  84. package/dist/ai-service/state-machine/clark-fsm.d.ts +1 -29
  85. package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
  86. package/dist/ai-service/state-machine/clark-fsm.js +1 -3
  87. package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
  88. package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
  89. package/dist/ai-service/state-machine/handlers/agent-planning.js +6 -7
  90. package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
  91. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
  92. package/dist/ai-service/state-machine/handlers/llm-generating.js +5 -18
  93. package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
  94. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.d.ts.map +1 -1
  95. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js +6 -8
  96. package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js.map +1 -1
  97. package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.d.ts +14 -0
  98. package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.d.ts.map +1 -1
  99. package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.js +36 -0
  100. package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.js.map +1 -1
  101. package/dist/ai-service/state-machine/helpers/pre-session-metrics.d.ts +0 -2
  102. package/dist/ai-service/state-machine/helpers/pre-session-metrics.d.ts.map +1 -1
  103. package/dist/ai-service/state-machine/helpers/pre-session-metrics.js +0 -3
  104. package/dist/ai-service/state-machine/helpers/pre-session-metrics.js.map +1 -1
  105. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts +0 -1
  106. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts.map +1 -1
  107. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js +27 -60
  108. package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js.map +1 -1
  109. package/dist/ai-service/state-machine/helpers/stable-peer.d.ts +1 -12
  110. package/dist/ai-service/state-machine/helpers/stable-peer.d.ts.map +1 -1
  111. package/dist/ai-service/state-machine/helpers/stable-peer.js +9 -42
  112. package/dist/ai-service/state-machine/helpers/stable-peer.js.map +1 -1
  113. package/dist/ai-service/state-machine/mocks.d.ts.map +1 -1
  114. package/dist/ai-service/state-machine/mocks.js +0 -6
  115. package/dist/ai-service/state-machine/mocks.js.map +1 -1
  116. package/dist/ai-service/util/rpc-timeout.d.ts +5 -12
  117. package/dist/ai-service/util/rpc-timeout.d.ts.map +1 -1
  118. package/dist/ai-service/util/rpc-timeout.js +5 -12
  119. package/dist/ai-service/util/rpc-timeout.js.map +1 -1
  120. package/dist/file-sync-vite-plugin.d.ts.map +1 -1
  121. package/dist/file-sync-vite-plugin.js +21 -8
  122. package/dist/file-sync-vite-plugin.js.map +1 -1
  123. package/dist/lock-service/activity-tracker.d.ts +5 -0
  124. package/dist/lock-service/activity-tracker.d.ts.map +1 -1
  125. package/dist/lock-service/activity-tracker.js +13 -0
  126. package/dist/lock-service/activity-tracker.js.map +1 -1
  127. package/dist/lock-service/index.d.ts +27 -308
  128. package/dist/lock-service/index.d.ts.map +1 -1
  129. package/dist/lock-service/index.js +156 -680
  130. package/dist/lock-service/index.js.map +1 -1
  131. package/dist/policy-gate-callback-mapper.d.ts +2 -3
  132. package/dist/policy-gate-callback-mapper.d.ts.map +1 -1
  133. package/dist/policy-gate-callback-mapper.js.map +1 -1
  134. package/dist/policy-gate-runner.d.ts +2 -4
  135. package/dist/policy-gate-runner.d.ts.map +1 -1
  136. package/dist/policy-gate-runner.js +8 -31
  137. package/dist/policy-gate-runner.js.map +1 -1
  138. package/dist/socket-manager.d.ts +6 -47
  139. package/dist/socket-manager.d.ts.map +1 -1
  140. package/dist/socket-manager.js +66 -283
  141. package/dist/socket-manager.js.map +1 -1
  142. package/dist/sync-service/index.d.ts.map +1 -1
  143. package/dist/sync-service/index.js +3 -3
  144. package/dist/sync-service/index.js.map +1 -1
  145. package/package.json +8 -8
  146. package/dist/ai-service/agent/tool-permission-delivery-metrics.d.ts +0 -3
  147. package/dist/ai-service/agent/tool-permission-delivery-metrics.d.ts.map +0 -1
  148. package/dist/ai-service/agent/tool-permission-delivery-metrics.js +0 -23
  149. package/dist/ai-service/agent/tool-permission-delivery-metrics.js.map +0 -1
  150. package/dist/ai-service/llm/stream/observers/step-token-usage.d.ts +0 -15
  151. package/dist/ai-service/llm/stream/observers/step-token-usage.d.ts.map +0 -1
  152. package/dist/ai-service/llm/stream/observers/step-token-usage.js +0 -88
  153. package/dist/ai-service/llm/stream/observers/step-token-usage.js.map +0 -1
  154. package/dist/ai-service/llm/stream/terminal-reason.d.ts +0 -6
  155. package/dist/ai-service/llm/stream/terminal-reason.d.ts.map +0 -1
  156. package/dist/ai-service/llm/stream/terminal-reason.js +0 -73
  157. package/dist/ai-service/llm/stream/terminal-reason.js.map +0 -1
  158. package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.d.ts +0 -28
  159. package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.d.ts.map +0 -1
  160. package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.js +0 -254
  161. package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.js.map +0 -1
  162. package/dist/ai-service/util/call-peer.d.ts +0 -11
  163. package/dist/ai-service/util/call-peer.d.ts.map +0 -1
  164. package/dist/ai-service/util/call-peer.js +0 -47
  165. package/dist/ai-service/util/call-peer.js.map +0 -1
  166. package/dist/ai-service/util/peer-liveness.d.ts +0 -23
  167. package/dist/ai-service/util/peer-liveness.d.ts.map +0 -1
  168. package/dist/ai-service/util/peer-liveness.js +0 -42
  169. package/dist/ai-service/util/peer-liveness.js.map +0 -1
  170. package/dist/scan-scope-merge.d.ts +0 -16
  171. package/dist/scan-scope-merge.d.ts.map +0 -1
  172. package/dist/scan-scope-merge.js +0 -90
  173. package/dist/scan-scope-merge.js.map +0 -1
  174. package/dist/test-setup.d.ts +0 -2
  175. package/dist/test-setup.d.ts.map +0 -1
  176. package/dist/test-setup.js +0 -21
  177. package/dist/test-setup.js.map +0 -1
@@ -6,7 +6,6 @@ import { createISocketClient, DEFAULT_NON_GIT_BRANCH, MAX_SOCKET_PAYLOAD_BYTES,
6
6
  import { uploadContextToBucketeer } from "./ai-service/context-upload.js";
7
7
  import { createSocketRecordingMiddleware } from "./ai-service/recording/recorders/socket-recorder.js";
8
8
  import { parseJwt } from "./ai-service/util/parse-jwt.js";
9
- import { PeerLiveness, } from "./ai-service/util/peer-liveness.js";
10
9
  import { clearGitAuthFailed, isGitAuthFailed, markGitAuthFailed, } from "./git-auth-state.js";
11
10
  import { GIT_LOG_FORMAT, parseGitLog } from "./git-log.js";
12
11
  import { GIT_OPERATION_BUSY_MESSAGE, } from "./git-operation-lock.js";
@@ -16,7 +15,6 @@ import { doesRemoteLiveBranchExist, ensureLiveBranchCheckedOut, pushLiveBranchWi
16
15
  import { ensureProviderWorkflowFiles } from "./git-service/provider-workflow-files.js";
17
16
  import { getSyncCountsAgainstDefaultBranch } from "./git-service/status-sync.js";
18
17
  import { getErrorMeta, getLogger } from "./util/logger.js";
19
- import { OperationQueue } from "./util/operation-queue.js";
20
18
  const SB_WORKTREE_SNAPSHOTS_DIR = join(tmpdir(), "sb-worktree-snapshots");
21
19
  /**
22
20
  * Pulls the JWT-derived `userId` out of an `Authorization: Bearer <jwt>`
@@ -129,28 +127,7 @@ export function disposeSocketManager() {
129
127
  _socketManager = undefined;
130
128
  }
131
129
  class SocketManager extends TracedEventEmitter {
132
- peerLiveness = new PeerLiveness();
133
- socketLiveness = new WeakMap();
134
- /**
135
- * Survives `editorSockets.delete` so a just-closed socket can still tell
136
- * {@link getSurvivingEditorClient} which peerId to prefer among leftovers.
137
- */
138
- editorPeerIdsBySocket = new WeakMap();
139
130
  peerIds = new Map();
140
- /**
141
- * Editor peers whose presence is registered with the lock service, and the
142
- * user each was registered for.
143
- *
144
- * Presence is per *peer*, not per socket: one tab reconnecting produces a new
145
- * socket under the same peer id. Remembering "did I register?" on the socket
146
- * therefore breaks the pair, because the socket that takes presence down need
147
- * not be the one that put it up — a replacement that never makes an
148
- * authorized call would close without removing anything, leaving the lock
149
- * claimed for a peer that is gone.
150
- */
151
- editorPresenceUserIdByPeerId = new Map();
152
- /** Editor peers that have already triggered post-auth git activation. */
153
- gitActivatedPeerIds = new Set();
154
131
  /** Attached editor sockets, each mapped to the peer that opened it. */
155
132
  editorSockets = new Map();
156
133
  /**
@@ -181,73 +158,6 @@ class SocketManager extends TracedEventEmitter {
181
158
  });
182
159
  this.socketServer = wss;
183
160
  let activeGitService = gitService;
184
- const headlessLockOwners = [];
185
- const headlessLockOperationQueue = new OperationQueue();
186
- /**
187
- * Drop every turn this peer owned, and hand the lock back if that was the
188
- * last of them.
189
- *
190
- * Dropping the rows alone leaves the lock stranded. The lock service only
191
- * hands back when *no* gateway is left, so with a replacement socket
192
- * already attached that never fires — and once the owner row is gone, a
193
- * later `editor.releaseTurnLock` finds nothing to release and does
194
- * nothing. The AppBranchLock would then stay held for the rest of that
195
- * socket's life, with no turn behind it.
196
- *
197
- * Queued with the release path so the two cannot both decide they are
198
- * last.
199
- */
200
- const dropHeadlessLockOwners = async (peerId) => {
201
- await headlessLockOperationQueue.enqueue(async () => {
202
- const ownedAny = headlessLockOwners.some((owner) => owner.peerId === peerId);
203
- if (!ownedAny) {
204
- return;
205
- }
206
- for (let index = headlessLockOwners.length - 1; index >= 0; index--) {
207
- if (headlessLockOwners[index]?.peerId === peerId) {
208
- headlessLockOwners.splice(index, 1);
209
- }
210
- }
211
- if (headlessLockOwners.length > 0) {
212
- return;
213
- }
214
- // Re-read after the handback's generation wait: a replacement gateway
215
- // can claim a turn while the old one's write is still finishing.
216
- await lockService?.relinquishLockForTurn(() => headlessLockOwners.length > 0);
217
- });
218
- };
219
- const dropHeadlessLockOwnersInBackground = (peerId, occasion) => {
220
- void dropHeadlessLockOwners(peerId).catch((err) => {
221
- getLogger().warn(`[socket-manager] failed to hand the turn lock back after ${occasion}: ${err instanceof Error ? err.message : String(err)}`);
222
- });
223
- };
224
- lockService?.setOnGatewayLivenessLapsed?.((peerId) => {
225
- dropHeadlessLockOwnersInBackground(peerId, "a gateway's liveness lapsed");
226
- aiService?.endHeadlessTurn(peerId);
227
- });
228
- const releaseHeadlessLock = async (peerId, turnId) => {
229
- return await headlessLockOperationQueue.enqueue(async () => {
230
- const ownerIndex = headlessLockOwners.findIndex((owner) => owner.peerId === peerId &&
231
- (turnId ? owner.turnId === turnId : owner.turnId === undefined));
232
- if (ownerIndex === -1) {
233
- return false;
234
- }
235
- const [owner] = headlessLockOwners.splice(ownerIndex, 1);
236
- if (headlessLockOwners.length > 0) {
237
- return true;
238
- }
239
- try {
240
- await lockService?.relinquishLockForTurn(() => headlessLockOwners.length > 0);
241
- return true;
242
- }
243
- catch (error) {
244
- if (owner) {
245
- headlessLockOwners.splice(ownerIndex, 0, owner);
246
- }
247
- throw error;
248
- }
249
- });
250
- };
251
161
  // Provide sync context (branchName, lastCommitId) to the AI service
252
162
  // for remote context restore fallback from Bucketeer on fresh pods.
253
163
  if (aiService && syncService) {
@@ -415,54 +325,11 @@ class SocketManager extends TracedEventEmitter {
415
325
  };
416
326
  wss.on("connection", (ws, kind, peerId, userId) => {
417
327
  const isEditor = kind === "editor";
418
- const connectionLiveness = kind === "library" ? undefined : this.peerLiveness.bindConnection();
419
- if (connectionLiveness) {
420
- ws.on("message", () => connectionLiveness.markInbound());
421
- }
422
- /**
423
- * When an editor authenticates after git was enabled, the lock may
424
- * still be on 'main'. This calls
425
- * `ensureRuntimeDbfsBranchConsistency` -> `lockService.switchBranch`
426
- * to move it to superblocks/live.
427
- */
428
- const activateGitForAuthedEditor = () => {
429
- if (!activateGitService) {
430
- return;
431
- }
432
- void activateGitService().catch((err) => {
433
- getLogger().warn(`[socket-manager] post-auth git activation failed: ${err instanceof Error ? err.message : String(err)}`);
434
- });
435
- };
436
328
  const authMiddleware = async (params, ctx, _peer, next) => {
437
329
  const result = await checkAuthorization(ctx.peerAuthorization);
438
330
  if (!result.success) {
439
331
  throw new Error(result.error);
440
332
  }
441
- if (isEditor &&
442
- userId &&
443
- !this.editorPresenceUserIdByPeerId.has(peerId) &&
444
- // The authorization above is awaited, so the socket that asked for
445
- // it can be gone by now. `peerIds` loses the peer when its last
446
- // socket closes, so this is what keeps a late answer from
447
- // registering presence nothing is left to remove.
448
- this.peerIds.has(peerId)) {
449
- this.editorPresenceUserIdByPeerId.set(peerId, userId);
450
- lockService?.addConnectedPeerIdToUser(userId, peerId);
451
- }
452
- // After presence, and only for a peer that has proved itself.
453
- //
454
- // Git activation moves the DBFS branch, which asks the lock service
455
- // whether this pod holds the lock. Presence registration is what
456
- // starts the attach-time acquisition, so running this first — as it
457
- // did from the connect handler — asks that question before there is
458
- // an acquisition to observe: `isLockedWhenSettled()` settles what is
459
- // in flight, and nothing is. The switch then answers "no lock",
460
- // releases the one that lands moments later, and does not take it
461
- // back. Ordering them here is what keeps the two consistent.
462
- if (isEditor && !this.gitActivatedPeerIds.has(peerId)) {
463
- this.gitActivatedPeerIds.add(peerId);
464
- activateGitForAuthedEditor();
465
- }
466
333
  // Notify AiService that this peer has just produced a
467
334
  // proof-of-auth. AiService deduplicates internally and uses
468
335
  // this signal to drain state pushes that were deferred at
@@ -495,16 +362,7 @@ class SocketManager extends TracedEventEmitter {
495
362
  // it follows the peer rather than reporting into the socket it
496
363
  // lost. Naming it is required — otherwise any authorized gateway
497
364
  // session could take over another one's turn.
498
- const headlessTurnId = readHeadlessTurnId(params);
499
- if (ctx.method !== "editor.releaseTurnLock") {
500
- aiService?.claimHeadlessTurn(socket, peerId, headlessTurnId, connectionLiveness);
501
- if (headlessTurnId) {
502
- const owner = headlessLockOwners.find((candidate) => candidate.turnId === headlessTurnId);
503
- if (owner) {
504
- owner.peerId = peerId;
505
- }
506
- }
507
- }
365
+ aiService?.claimHeadlessTurn(socket, peerId, readHeadlessTurnId(params));
508
366
  }
509
367
  return await next();
510
368
  };
@@ -552,33 +410,12 @@ class SocketManager extends TracedEventEmitter {
552
410
  if (kind !== "gateway") {
553
411
  return await aiService.handleAiGenerateWithQueue(aiRequest, queueMode);
554
412
  }
555
- // Write access is taken per turn, not held for the whole
556
- // session, so the app is unlocked between prompts and a
557
- // person can open it. Before the turn is bound, and
558
- // awaited: a turn that cannot write must fail here carrying
559
- // the server's own reason — somebody is editing in the
560
- // browser — rather than bind routing and then unwind it.
561
- const requestedTurnId = typeof request.requestId === "string"
413
+ const turnId = aiService.beginHeadlessTurn(socket, peerId, typeof request.requestId === "string"
562
414
  ? request.requestId
563
- : undefined;
564
- const lockOwner = {
565
- peerId,
566
- turnId: requestedTurnId,
567
- };
568
- const turnId = await headlessLockOperationQueue.enqueue(async () => {
569
- await lockService?.acquireLockForTurn();
570
- headlessLockOwners.push(lockOwner);
571
- return aiService.beginHeadlessTurn(socket, peerId, requestedTurnId, connectionLiveness);
572
- });
415
+ : undefined);
573
416
  let result;
574
417
  try {
575
418
  result = await aiService.handleAiGenerateWithQueue(aiRequest, queueMode);
576
- const ownerIndex = headlessLockOwners.indexOf(lockOwner);
577
- for (let index = ownerIndex - 1; index >= 0; index--) {
578
- if (headlessLockOwners[index]?.peerId === peerId) {
579
- headlessLockOwners.splice(index, 1);
580
- }
581
- }
582
419
  }
583
420
  catch (err) {
584
421
  // The turn is bound before the prompt is handed over, so
@@ -586,12 +423,6 @@ class SocketManager extends TracedEventEmitter {
586
423
  // it. Left bound, it would capture the next turn a user
587
424
  // starts in the browser.
588
425
  aiService.abandonHeadlessTurn(peerId, turnId);
589
- try {
590
- await releaseHeadlessLock(peerId, turnId);
591
- }
592
- catch (releaseErr) {
593
- getLogger().warn("[socket-manager] failed to release the lock for a rejected headless turn", getErrorMeta(releaseErr));
594
- }
595
426
  throw err;
596
427
  }
597
428
  // Answering with the turn handle tells the caller two
@@ -601,16 +432,6 @@ class SocketManager extends TracedEventEmitter {
601
432
  return { ...result, headlessTurn: { turnId } };
602
433
  },
603
434
  ],
604
- releaseTurnLock: [
605
- async (request) => {
606
- if (kind !== "gateway") {
607
- throw new Error("Only gateway peers can release a headless turn lock");
608
- }
609
- if (await releaseHeadlessLock(peerId, request.headlessTurnId)) {
610
- aiService?.endHeadlessTurn(peerId, request.headlessTurnId);
611
- }
612
- },
613
- ],
614
435
  aiTriggerMigrationTurn: [
615
436
  async (request) => {
616
437
  if (!aiService) {
@@ -1199,20 +1020,17 @@ class SocketManager extends TracedEventEmitter {
1199
1020
  activeGitService = undefined;
1200
1021
  disconnectInProgress = true;
1201
1022
  clearGitAuthFailed();
1202
- // Keep file-sync / lock wiring; drop only the Git
1203
- // service so the next Clark turn sees unconnected.
1204
- aiService?.setRuntimeGitServices(undefined, syncService, gitOperationLock);
1205
1023
  // Ensure DBFS sync context is no longer tied to
1206
1024
  // `superblocks/live` immediately after native git disconnect.
1207
1025
  syncService?.setBranchName(DEFAULT_NON_GIT_BRANCH);
1208
1026
  // Native git disconnect should move runtime lock context back
1209
1027
  // to default branch before clients reconnect.
1210
1028
  if (lockService) {
1211
- // Retain the pre-switch state for the explicit fallback
1212
- // if an atomic branch transfer fails.
1213
- const wasLockedBeforeDisconnect = await lockService.isLockedWhenSettled();
1029
+ const wasLockedBeforeDisconnect = lockService.isLocked;
1214
1030
  try {
1215
- await lockService.switchBranch(DEFAULT_NON_GIT_BRANCH);
1031
+ await lockService.switchBranch(DEFAULT_NON_GIT_BRANCH, {
1032
+ reacquireLock: wasLockedBeforeDisconnect,
1033
+ });
1216
1034
  }
1217
1035
  catch (err) {
1218
1036
  getLogger().warn("[git] gitDisconnect failed to switch lock branch back to main", {
@@ -1464,9 +1282,6 @@ class SocketManager extends TracedEventEmitter {
1464
1282
  },
1465
1283
  },
1466
1284
  }));
1467
- if (connectionLiveness) {
1468
- this.socketLiveness.set(socket, connectionLiveness);
1469
- }
1470
1285
  // we are storing all of the sockets for a given peerId (editor+library pair of connections)
1471
1286
  // which lets us handle synchronization of operations between the two
1472
1287
  let existingSockets = this.peerIds.get(peerId);
@@ -1479,14 +1294,23 @@ class SocketManager extends TracedEventEmitter {
1479
1294
  // Liveness is granted in the auth middleware, not here: the upgrade
1480
1295
  // path only checks `applicationId`, and holding the sandbox open is
1481
1296
  // not something an unproven socket gets to do.
1482
- this.emit("connect:gateway", socket, peerId, connectionLiveness);
1297
+ this.emit("connect:gateway", socket, peerId);
1483
1298
  }
1484
1299
  else if (isEditor) {
1485
1300
  this.editorSockets.set(socket, peerId);
1486
- this.editorPeerIdsBySocket.set(socket, peerId);
1487
- this.emit("connect:editor", socket, peerId, connectionLiveness);
1488
- // Git activation is deliberately NOT started here — it runs from the
1489
- // auth middleware, after presence. See `activateGitForAuthedEditor`.
1301
+ this.emit("connect:editor", socket, peerId);
1302
+ if (userId) {
1303
+ lockService?.addConnectedPeerIdToUser(userId, peerId);
1304
+ }
1305
+ // When a new editor connects after git was enabled, the lock may
1306
+ // still be on 'main'. Trigger git activation which calls
1307
+ // ensureRuntimeDbfsBranchConsistency → lockService.switchBranch
1308
+ // to transfer the lock to superblocks/live.
1309
+ if (activateGitService) {
1310
+ void activateGitService().catch((err) => {
1311
+ getLogger().warn(`[socket-manager] post-connect git activation failed: ${err instanceof Error ? err.message : String(err)}`);
1312
+ });
1313
+ }
1490
1314
  }
1491
1315
  else {
1492
1316
  this.librarySockets.add(socket);
@@ -1494,73 +1318,61 @@ class SocketManager extends TracedEventEmitter {
1494
1318
  }
1495
1319
  existingSockets.add(socket);
1496
1320
  ws.on("close", (code, reason) => {
1497
- connectionLiveness?.close();
1498
1321
  const peerSockets = this.peerIds.get(peerId);
1499
1322
  peerSockets?.delete(socket);
1500
1323
  if (peerSockets?.size === 0) {
1501
1324
  this.peerIds.delete(peerId);
1502
- // The peer is gone, so a later reconnect under the same id is a
1503
- // new arrival and activates git again.
1504
- this.gitActivatedPeerIds.delete(peerId);
1505
1325
  }
1506
1326
  getLogger().info(`Socket from ${kind} closed code: ${code} reason: ${reason}`);
1507
1327
  if (kind === "gateway") {
1508
1328
  this.gatewaySockets.delete(socket);
1509
1329
  lockService?.markGatewayDetached(peerId);
1510
- // No blanket clear when the last socket goes: every peer's rows are
1511
- // dropped by its own close, and the one that empties the list is
1512
- // what hands the lock back.
1513
- dropHeadlessLockOwnersInBackground(peerId, "a gateway socket closed");
1514
- aiService?.endHeadlessTurn(peerId, connectionLiveness);
1515
- this.emit("disconnect:gateway", peerId, connectionLiveness);
1330
+ aiService?.endHeadlessTurn(peerId);
1331
+ this.emit("disconnect:gateway", peerId);
1516
1332
  }
1517
1333
  else if (isEditor) {
1518
1334
  this.editorSockets.delete(socket);
1519
1335
  const hasOtherEditorSocket = Array.from(peerSockets ?? []).some((peerSocket) => this.editorSockets.has(peerSocket));
1520
- if (!hasOtherEditorSocket) {
1521
- // Keyed by peer, so whichever of the peer's sockets is last out
1522
- // takes the presence down — including one that never authenticated
1523
- // itself.
1524
- const presenceUserId = this.editorPresenceUserIdByPeerId.get(peerId);
1525
- if (presenceUserId) {
1526
- this.editorPresenceUserIdByPeerId.delete(peerId);
1527
- lockService?.removeConnectedPeerIdFromUser(presenceUserId, peerId);
1528
- if (lockService?.connectedUsers.length === 0) {
1529
- if (aiService?.isBusy()) {
1530
- getLogger().info("[socket-manager] All users disconnected but AI service is busy, deferring cache cleanup until generation completes");
1531
- let cleaned = false;
1532
- const deferredCleanup = () => {
1533
- if (cleaned)
1534
- return;
1535
- cleaned = true;
1536
- // Only clean up if no users have reconnected in the meantime
1537
- if (lockService?.connectedUsers.length === 0) {
1538
- getLogger().info("[socket-manager] Generation ended with no connected users, cleaning up AI service cache");
1539
- aiService
1540
- .removeIntegrationCache()
1541
- .catch((error) => {
1542
- getLogger().warn(`Error removing integration cache after deferred cleanup: ${error}`);
1543
- });
1544
- }
1545
- else {
1546
- getLogger().info("[socket-manager] Generation ended but users have reconnected, skipping cache cleanup");
1547
- }
1548
- };
1549
- aiService.once("generationCompleted", deferredCleanup);
1550
- aiService.once("generationFailed", deferredCleanup);
1551
- }
1552
- else {
1553
- getLogger().info("[socket-manager] All users disconnected, cleaning up AI service cache");
1554
- aiService
1555
- ?.removeIntegrationCache()
1556
- .catch((error) => {
1557
- getLogger().warn(`Error removing integration cache after disconnect: ${error}`);
1558
- });
1559
- }
1336
+ if (hasOtherEditorSocket) {
1337
+ return;
1338
+ }
1339
+ if (userId) {
1340
+ lockService?.removeConnectedPeerIdFromUser(userId, peerId);
1341
+ if (lockService?.connectedUsers.length === 0) {
1342
+ if (aiService?.isBusy()) {
1343
+ getLogger().info("[socket-manager] All users disconnected but AI service is busy, deferring cache cleanup until generation completes");
1344
+ let cleaned = false;
1345
+ const deferredCleanup = () => {
1346
+ if (cleaned)
1347
+ return;
1348
+ cleaned = true;
1349
+ // Only clean up if no users have reconnected in the meantime
1350
+ if (lockService?.connectedUsers.length === 0) {
1351
+ getLogger().info("[socket-manager] Generation ended with no connected users, cleaning up AI service cache");
1352
+ aiService
1353
+ .removeIntegrationCache()
1354
+ .catch((error) => {
1355
+ getLogger().warn(`Error removing integration cache after deferred cleanup: ${error}`);
1356
+ });
1357
+ }
1358
+ else {
1359
+ getLogger().info("[socket-manager] Generation ended but users have reconnected, skipping cache cleanup");
1360
+ }
1361
+ };
1362
+ aiService.once("generationCompleted", deferredCleanup);
1363
+ aiService.once("generationFailed", deferredCleanup);
1364
+ }
1365
+ else {
1366
+ getLogger().info("[socket-manager] All users disconnected, cleaning up AI service cache");
1367
+ aiService
1368
+ ?.removeIntegrationCache()
1369
+ .catch((error) => {
1370
+ getLogger().warn(`Error removing integration cache after disconnect: ${error}`);
1371
+ });
1560
1372
  }
1561
1373
  }
1562
1374
  }
1563
- this.emit("disconnect:editor", peerId, socket, connectionLiveness);
1375
+ this.emit("disconnect:editor", peerId);
1564
1376
  }
1565
1377
  else {
1566
1378
  this.librarySockets.delete(socket);
@@ -1592,48 +1404,19 @@ class SocketManager extends TracedEventEmitter {
1592
1404
  }
1593
1405
  }
1594
1406
  /**
1595
- * Any editor socket that does not belong to `exclude`.
1407
+ * Any editor socket that does not belong to `excludePeerId`.
1596
1408
  *
1597
1409
  * Used when a peer disconnects: Clark's RPCs are bound to a single editor,
1598
1410
  * so a survivor is what keeps API execution alive for a user whose tab is
1599
1411
  * still open after a headless build's socket closes.
1600
- *
1601
- * Same-id sockets are preferred when `exclude` is the departing socket: a
1602
- * tab that reconnected keeps the bound Clark context instead of handing it
1603
- * (and in-flight generation / tool-permission prompts) to whichever other
1604
- * editor happened to connect first. A string `exclude` still means "this
1605
- * peer is gone" and skips every socket with that id.
1606
1412
  */
1607
- getSurvivingEditorClient(exclude) {
1608
- const excludedPeerId = typeof exclude === "string" ? exclude : undefined;
1609
- const preferredPeerId = typeof exclude === "string"
1610
- ? undefined
1611
- : this.editorPeerIdsBySocket.get(exclude);
1612
- const pick = (onlyPreferred) => {
1613
- for (const [peer, peerId] of this.editorSockets) {
1614
- const excluded = excludedPeerId !== undefined
1615
- ? peerId === excludedPeerId
1616
- : peer === exclude;
1617
- if (excluded) {
1618
- continue;
1619
- }
1620
- if (onlyPreferred && peerId !== preferredPeerId) {
1621
- continue;
1622
- }
1623
- const liveness = this.socketLiveness.get(peer);
1624
- if (liveness) {
1625
- return { liveness, peer, peerId };
1626
- }
1627
- }
1628
- return undefined;
1629
- };
1630
- if (preferredPeerId !== undefined) {
1631
- const sameId = pick(true);
1632
- if (sameId) {
1633
- return sameId;
1413
+ getSurvivingEditorClient(excludePeerId) {
1414
+ for (const [peer, peerId] of this.editorSockets) {
1415
+ if (peerId !== excludePeerId) {
1416
+ return { peer, peerId };
1634
1417
  }
1635
1418
  }
1636
- return pick(false);
1419
+ return undefined;
1637
1420
  }
1638
1421
  callLibraryClients(fn) {
1639
1422
  for (const socket of this.librarySockets) {