@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.
- package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts +2 -9
- package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js +6 -34
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts +1 -2
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.d.ts.map +1 -1
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.js +9 -52
- package/dist/ai-service/agent/prompts/build-security-scan-prompt.js.map +1 -1
- package/dist/ai-service/agent/tool-permission-delivery.d.ts +16 -25
- package/dist/ai-service/agent/tool-permission-delivery.d.ts.map +1 -1
- package/dist/ai-service/agent/tool-permission-delivery.js +38 -384
- package/dist/ai-service/agent/tool-permission-delivery.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts +35 -9
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js +98 -71
- package/dist/ai-service/agent/tools/apis/get-sdk-api-docs.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/test-api.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/apis/test-api.js +2 -3
- package/dist/ai-service/agent/tools/apis/test-api.js.map +1 -1
- package/dist/ai-service/agent/tools/app-environment/update-app-environment.d.ts +1 -1
- package/dist/ai-service/agent/tools/app-environment/update-app-environment.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/app-environment/update-app-environment.js +1 -13
- package/dist/ai-service/agent/tools/app-environment/update-app-environment.js.map +1 -1
- package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-capture-screenshot.js +3 -8
- package/dist/ai-service/agent/tools/build-capture-screenshot.js.map +1 -1
- package/dist/ai-service/agent/tools/build-navigate-preview.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-navigate-preview.js +2 -4
- package/dist/ai-service/agent/tools/build-navigate-preview.js.map +1 -1
- package/dist/ai-service/agent/tools/build-reload-file.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-reload-file.js +2 -4
- package/dist/ai-service/agent/tools/build-reload-file.js.map +1 -1
- package/dist/ai-service/agent/tools/get-logs.d.ts +2 -2
- package/dist/ai-service/agent/tools/integrations/delete-integration.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/integrations/delete-integration.js +3 -6
- package/dist/ai-service/agent/tools/integrations/delete-integration.js.map +1 -1
- package/dist/ai-service/agent/tools/integrations/execute-request.d.ts +13 -13
- package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts +6 -18
- package/dist/ai-service/agent/tools/integrations/integration-setup.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/integrations/integration-setup.js +15 -41
- package/dist/ai-service/agent/tools/integrations/integration-setup.js.map +1 -1
- package/dist/ai-service/agent/tools/integrations/internal.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/integrations/internal.js +2 -4
- package/dist/ai-service/agent/tools/integrations/internal.js.map +1 -1
- package/dist/ai-service/agent/tools/report-security-findings.d.ts +23 -23
- package/dist/ai-service/agent/tools.d.ts +2 -4
- package/dist/ai-service/agent/tools.d.ts.map +1 -1
- package/dist/ai-service/agent/tools.js +14 -21
- package/dist/ai-service/agent/tools.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/git.d.ts +2 -2
- package/dist/ai-service/index.d.ts +25 -26
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +121 -340
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/integrations/store.d.ts +1 -2
- package/dist/ai-service/integrations/store.d.ts.map +1 -1
- package/dist/ai-service/integrations/store.js +2 -3
- package/dist/ai-service/integrations/store.js.map +1 -1
- package/dist/ai-service/judge/tools/playwright-action.d.ts +1 -1
- package/dist/ai-service/judge/tools/submit-feedback.d.ts +1 -1
- package/dist/ai-service/llm/client.d.ts +3 -9
- package/dist/ai-service/llm/client.d.ts.map +1 -1
- package/dist/ai-service/llm/client.js +3 -10
- package/dist/ai-service/llm/client.js.map +1 -1
- package/dist/ai-service/llm/context-v2/context-metrics.d.ts.map +1 -1
- package/dist/ai-service/llm/context-v2/context-metrics.js +0 -3
- package/dist/ai-service/llm/context-v2/context-metrics.js.map +1 -1
- package/dist/ai-service/llm/stream/observers/llmobs.d.ts +6 -6
- package/dist/ai-service/llm/stream/observers/llmobs.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/observers/llmobs.js +9 -10
- package/dist/ai-service/llm/stream/observers/llmobs.js.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.d.ts +0 -4
- package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.js +41 -19
- package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
- package/dist/ai-service/llm/stream/retry-engine.d.ts +1 -1
- package/dist/ai-service/llm/stream/retry-engine.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/retry-engine.js +1 -26
- package/dist/ai-service/llm/stream/retry-engine.js.map +1 -1
- package/dist/ai-service/policy-agent-metrics.d.ts +0 -10
- package/dist/ai-service/policy-agent-metrics.d.ts.map +1 -1
- package/dist/ai-service/policy-agent-metrics.js +0 -33
- package/dist/ai-service/policy-agent-metrics.js.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts +1 -29
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.js +1 -3
- package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.js +6 -7
- package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.js +5 -18
- package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js +6 -8
- package/dist/ai-service/state-machine/helpers/fetch-with-reconnect-retry.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.d.ts +14 -0
- package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.js +36 -0
- package/dist/ai-service/state-machine/helpers/policy-gate-usage-metrics.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/pre-session-metrics.d.ts +0 -2
- package/dist/ai-service/state-machine/helpers/pre-session-metrics.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/pre-session-metrics.js +0 -3
- package/dist/ai-service/state-machine/helpers/pre-session-metrics.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts +0 -1
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js +27 -60
- package/dist/ai-service/state-machine/helpers/prompt-request-metrics.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/stable-peer.d.ts +1 -12
- package/dist/ai-service/state-machine/helpers/stable-peer.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/stable-peer.js +9 -42
- package/dist/ai-service/state-machine/helpers/stable-peer.js.map +1 -1
- package/dist/ai-service/state-machine/mocks.d.ts.map +1 -1
- package/dist/ai-service/state-machine/mocks.js +0 -6
- package/dist/ai-service/state-machine/mocks.js.map +1 -1
- package/dist/ai-service/util/rpc-timeout.d.ts +5 -12
- package/dist/ai-service/util/rpc-timeout.d.ts.map +1 -1
- package/dist/ai-service/util/rpc-timeout.js +5 -12
- package/dist/ai-service/util/rpc-timeout.js.map +1 -1
- package/dist/file-sync-vite-plugin.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.js +21 -8
- package/dist/file-sync-vite-plugin.js.map +1 -1
- package/dist/lock-service/activity-tracker.d.ts +5 -0
- package/dist/lock-service/activity-tracker.d.ts.map +1 -1
- package/dist/lock-service/activity-tracker.js +13 -0
- package/dist/lock-service/activity-tracker.js.map +1 -1
- package/dist/lock-service/index.d.ts +27 -308
- package/dist/lock-service/index.d.ts.map +1 -1
- package/dist/lock-service/index.js +156 -680
- package/dist/lock-service/index.js.map +1 -1
- package/dist/policy-gate-callback-mapper.d.ts +2 -3
- package/dist/policy-gate-callback-mapper.d.ts.map +1 -1
- package/dist/policy-gate-callback-mapper.js.map +1 -1
- package/dist/policy-gate-runner.d.ts +2 -4
- package/dist/policy-gate-runner.d.ts.map +1 -1
- package/dist/policy-gate-runner.js +8 -31
- package/dist/policy-gate-runner.js.map +1 -1
- package/dist/socket-manager.d.ts +6 -47
- package/dist/socket-manager.d.ts.map +1 -1
- package/dist/socket-manager.js +66 -283
- package/dist/socket-manager.js.map +1 -1
- package/dist/sync-service/index.d.ts.map +1 -1
- package/dist/sync-service/index.js +3 -3
- package/dist/sync-service/index.js.map +1 -1
- package/package.json +8 -8
- package/dist/ai-service/agent/tool-permission-delivery-metrics.d.ts +0 -3
- package/dist/ai-service/agent/tool-permission-delivery-metrics.d.ts.map +0 -1
- package/dist/ai-service/agent/tool-permission-delivery-metrics.js +0 -23
- package/dist/ai-service/agent/tool-permission-delivery-metrics.js.map +0 -1
- package/dist/ai-service/llm/stream/observers/step-token-usage.d.ts +0 -15
- package/dist/ai-service/llm/stream/observers/step-token-usage.d.ts.map +0 -1
- package/dist/ai-service/llm/stream/observers/step-token-usage.js +0 -88
- package/dist/ai-service/llm/stream/observers/step-token-usage.js.map +0 -1
- package/dist/ai-service/llm/stream/terminal-reason.d.ts +0 -6
- package/dist/ai-service/llm/stream/terminal-reason.d.ts.map +0 -1
- package/dist/ai-service/llm/stream/terminal-reason.js +0 -73
- package/dist/ai-service/llm/stream/terminal-reason.js.map +0 -1
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.d.ts +0 -28
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.d.ts.map +0 -1
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.js +0 -254
- package/dist/ai-service/state-machine/helpers/prompt-degradation-metrics.js.map +0 -1
- package/dist/ai-service/util/call-peer.d.ts +0 -11
- package/dist/ai-service/util/call-peer.d.ts.map +0 -1
- package/dist/ai-service/util/call-peer.js +0 -47
- package/dist/ai-service/util/call-peer.js.map +0 -1
- package/dist/ai-service/util/peer-liveness.d.ts +0 -23
- package/dist/ai-service/util/peer-liveness.d.ts.map +0 -1
- package/dist/ai-service/util/peer-liveness.js +0 -42
- package/dist/ai-service/util/peer-liveness.js.map +0 -1
- package/dist/scan-scope-merge.d.ts +0 -16
- package/dist/scan-scope-merge.d.ts.map +0 -1
- package/dist/scan-scope-merge.js +0 -90
- package/dist/scan-scope-merge.js.map +0 -1
- package/dist/test-setup.d.ts +0 -2
- package/dist/test-setup.d.ts.map +0 -1
- package/dist/test-setup.js +0 -21
- package/dist/test-setup.js.map +0 -1
package/dist/socket-manager.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1297
|
+
this.emit("connect:gateway", socket, peerId);
|
|
1483
1298
|
}
|
|
1484
1299
|
else if (isEditor) {
|
|
1485
1300
|
this.editorSockets.set(socket, peerId);
|
|
1486
|
-
this.
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
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
|
-
|
|
1511
|
-
|
|
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 (
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
if (
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
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
|
|
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 `
|
|
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(
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1610
|
-
|
|
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
|
|
1419
|
+
return undefined;
|
|
1637
1420
|
}
|
|
1638
1421
|
callLibraryClients(fn) {
|
|
1639
1422
|
for (const socket of this.librarySockets) {
|