@superblocksteam/vite-plugin-file-sync 2.0.161-next.0 → 2.0.162-next.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.
- package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts +2 -0
- 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 +39 -27
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
- package/dist/ai-service/agent/tools/rbac/effective-permissions.d.ts +13 -0
- package/dist/ai-service/agent/tools/rbac/effective-permissions.d.ts.map +1 -0
- package/dist/ai-service/agent/tools/rbac/effective-permissions.js +49 -0
- package/dist/ai-service/agent/tools/rbac/effective-permissions.js.map +1 -0
- package/dist/ai-service/agent/tools.d.ts +5 -5
- package/dist/ai-service/agent/tools.d.ts.map +1 -1
- package/dist/ai-service/agent/tools.js +27 -9
- package/dist/ai-service/agent/tools.js.map +1 -1
- package/dist/ai-service/agent/tools2/access-control.d.ts +5 -0
- package/dist/ai-service/agent/tools2/access-control.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/access-control.js +16 -0
- package/dist/ai-service/agent/tools2/access-control.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts +2 -0
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js +6 -2
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
- package/dist/ai-service/app-skills/helpers.d.ts +1 -1
- package/dist/ai-service/app-skills/helpers.d.ts.map +1 -1
- package/dist/ai-service/app-skills/helpers.js +2 -1
- package/dist/ai-service/app-skills/helpers.js.map +1 -1
- package/dist/ai-service/chat/chat-session-store.d.ts +2 -0
- package/dist/ai-service/chat/chat-session-store.d.ts.map +1 -1
- package/dist/ai-service/chat/chat-session-store.js +2 -2
- package/dist/ai-service/chat/chat-session-store.js.map +1 -1
- package/dist/ai-service/checkpoint-screenshot.d.ts +9 -0
- package/dist/ai-service/checkpoint-screenshot.d.ts.map +1 -0
- package/dist/ai-service/checkpoint-screenshot.js +40 -0
- package/dist/ai-service/checkpoint-screenshot.js.map +1 -0
- package/dist/ai-service/index.d.ts +15 -9
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +141 -58
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/skills/system/superblocks-frontend/skill.generated.d.ts +1 -1
- package/dist/ai-service/skills/system/superblocks-frontend/skill.generated.d.ts.map +1 -1
- package/dist/ai-service/skills/system/superblocks-frontend/skill.generated.js +9 -7
- package/dist/ai-service/skills/system/superblocks-frontend/skill.generated.js.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts +18 -9
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- 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 +68 -53
- 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 +15 -2
- package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/peer.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/peer.js +21 -9
- package/dist/ai-service/state-machine/helpers/peer.js.map +1 -1
- package/dist/ai-service/state-machine/helpers/stable-peer.d.ts +20 -6
- package/dist/ai-service/state-machine/helpers/stable-peer.d.ts.map +1 -1
- package/dist/ai-service/state-machine/helpers/stable-peer.js +124 -30
- 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 +1 -0
- package/dist/ai-service/state-machine/mocks.js.map +1 -1
- package/dist/ai-service/types.d.ts +4 -0
- package/dist/ai-service/types.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.d.ts +5 -1
- package/dist/file-sync-vite-plugin.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.js +27 -11
- package/dist/file-sync-vite-plugin.js.map +1 -1
- package/dist/inject-index-vite-plugin.d.ts +15 -0
- package/dist/inject-index-vite-plugin.d.ts.map +1 -1
- package/dist/inject-index-vite-plugin.js +31 -0
- package/dist/inject-index-vite-plugin.js.map +1 -1
- package/dist/lock-service/index.d.ts +1 -0
- package/dist/lock-service/index.d.ts.map +1 -1
- package/dist/lock-service/index.js +14 -7
- package/dist/lock-service/index.js.map +1 -1
- package/dist/socket-manager.d.ts +12 -4
- package/dist/socket-manager.d.ts.map +1 -1
- package/dist/socket-manager.js +195 -39
- package/dist/socket-manager.js.map +1 -1
- package/package.json +9 -9
package/dist/socket-manager.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type http from "node:http";
|
|
2
2
|
import type { Tracer } from "@opentelemetry/api";
|
|
3
|
-
import type { EditorClientMethods, LibraryClientMethods } from "@superblocksteam/library-shared/types";
|
|
3
|
+
import type { EditorClientMethods, EditorHasDefaultStructure, GatewayClientMethods, LibraryClientMethods } from "@superblocksteam/library-shared/types";
|
|
4
4
|
import { TracedEventEmitter } from "@superblocksteam/shared";
|
|
5
5
|
import type { ISocketClient } from "@superblocksteam/shared";
|
|
6
6
|
import type { AiService } from "./ai-service/index.js";
|
|
@@ -42,7 +42,7 @@ type SocketManagerEvents = {
|
|
|
42
42
|
PeerConnectionLiveness
|
|
43
43
|
];
|
|
44
44
|
"connect:gateway": [
|
|
45
|
-
ISocketClient<
|
|
45
|
+
ISocketClient<GatewayClientMethods>,
|
|
46
46
|
string,
|
|
47
47
|
PeerConnectionLiveness
|
|
48
48
|
];
|
|
@@ -65,6 +65,10 @@ declare class SocketManager extends TracedEventEmitter<SocketManagerEvents> {
|
|
|
65
65
|
* {@link getSurvivingEditorClient} which peerId to prefer among leftovers.
|
|
66
66
|
*/
|
|
67
67
|
private readonly editorPeerIdsBySocket;
|
|
68
|
+
private readonly authenticatedEditorSockets;
|
|
69
|
+
private readonly authenticatedGatewaySockets;
|
|
70
|
+
private editorStructureReport;
|
|
71
|
+
private readonly editorStructureReportByGateway;
|
|
68
72
|
private peerIds;
|
|
69
73
|
/**
|
|
70
74
|
* Editor peers whose presence is registered with the lock service, and the
|
|
@@ -111,6 +115,8 @@ declare class SocketManager extends TracedEventEmitter<SocketManagerEvents> {
|
|
|
111
115
|
superblocksBaseUrl?: string;
|
|
112
116
|
maxPayloadBytes?: number;
|
|
113
117
|
}): void;
|
|
118
|
+
reportEditorHasDefaultStructure(hasDefaultStructure: EditorHasDefaultStructure): void;
|
|
119
|
+
private sendEditorStructureReport;
|
|
114
120
|
callEditorClients(fn: (socket: ISocketClient<EditorClientMethods>) => Promise<void>): void;
|
|
115
121
|
/**
|
|
116
122
|
* Both registries, for the signals a headless turn needs as much as a browser
|
|
@@ -144,14 +150,16 @@ declare class SocketManager extends TracedEventEmitter<SocketManagerEvents> {
|
|
|
144
150
|
callLibraryClients(fn: (socket: ISocketClient<LibraryClientMethods>) => Promise<void>): void;
|
|
145
151
|
getAnyLibraryClient(): ISocketClient<LibraryClientMethods> | undefined;
|
|
146
152
|
getEditorClients(): Iterable<ISocketClient<EditorClientMethods>>;
|
|
153
|
+
private getAuthenticatedEditorPeers;
|
|
154
|
+
private getAuthenticatedGatewayPeers;
|
|
147
155
|
/**
|
|
148
156
|
* Attached headless gateway peers. Separate from {@link getEditorClients} so
|
|
149
157
|
* a broadcast meant for browser tabs never reaches a gateway, and the
|
|
150
158
|
* gateway's own lifecycle can be observed without editor semantics.
|
|
151
159
|
*/
|
|
152
|
-
getGatewayClients(): Iterable<ISocketClient<
|
|
160
|
+
getGatewayClients(): Iterable<ISocketClient<GatewayClientMethods>>;
|
|
153
161
|
/** The attached gateway peer with this peer id, if it is still connected. */
|
|
154
|
-
getGatewayClient(peerId: string): ISocketClient<
|
|
162
|
+
getGatewayClient(peerId: string): ISocketClient<GatewayClientMethods> | undefined;
|
|
155
163
|
closeAllClients(): void;
|
|
156
164
|
dispose(): void;
|
|
157
165
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket-manager.d.ts","sourceRoot":"","sources":["../src/socket-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"socket-manager.d.ts","sourceRoot":"","sources":["../src/socket-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/socket-manager.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { cp, mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
2
3
|
import { tmpdir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { WebSocketServer } from "ws";
|
|
5
6
|
import { createISocketClient, DEFAULT_NON_GIT_BRANCH, MAX_SOCKET_PAYLOAD_BYTES, SocketErrorException, SUPERBLOCKS_LIVE_GIT_BRANCH, TracedEventEmitter, TracedSocket, } from "@superblocksteam/shared";
|
|
7
|
+
import { uploadCheckpointScreenshot } from "./ai-service/checkpoint-screenshot.js";
|
|
6
8
|
import { uploadContextToBucketeer } from "./ai-service/context-upload.js";
|
|
7
9
|
import { createSocketRecordingMiddleware } from "./ai-service/recording/recorders/socket-recorder.js";
|
|
8
10
|
import { parseJwt } from "./ai-service/util/parse-jwt.js";
|
|
@@ -136,6 +138,10 @@ class SocketManager extends TracedEventEmitter {
|
|
|
136
138
|
* {@link getSurvivingEditorClient} which peerId to prefer among leftovers.
|
|
137
139
|
*/
|
|
138
140
|
editorPeerIdsBySocket = new WeakMap();
|
|
141
|
+
authenticatedEditorSockets = new WeakSet();
|
|
142
|
+
authenticatedGatewaySockets = new WeakSet();
|
|
143
|
+
editorStructureReport;
|
|
144
|
+
editorStructureReportByGateway = new WeakMap();
|
|
139
145
|
peerIds = new Map();
|
|
140
146
|
/**
|
|
141
147
|
* Editor peers whose presence is registered with the lock service, and the
|
|
@@ -182,6 +188,7 @@ class SocketManager extends TracedEventEmitter {
|
|
|
182
188
|
this.socketServer = wss;
|
|
183
189
|
let activeGitService = gitService;
|
|
184
190
|
const headlessLockOwners = [];
|
|
191
|
+
const headlessLockHandbackQueue = new OperationQueue();
|
|
185
192
|
const headlessLockOperationQueue = new OperationQueue();
|
|
186
193
|
/**
|
|
187
194
|
* Drop every turn this peer owned, and hand the lock back if that was the
|
|
@@ -194,28 +201,50 @@ class SocketManager extends TracedEventEmitter {
|
|
|
194
201
|
* nothing. The AppBranchLock would then stay held for the rest of that
|
|
195
202
|
* socket's life, with no turn behind it.
|
|
196
203
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
204
|
+
* Owner decisions and handbacks use separate queues so generation waits
|
|
205
|
+
* serialize releases without keeping later headless prompts from starting.
|
|
199
206
|
*/
|
|
200
|
-
const
|
|
201
|
-
await headlessLockOperationQueue.enqueue(async () => {
|
|
202
|
-
const
|
|
203
|
-
if (!
|
|
204
|
-
|
|
207
|
+
const dropHeadlessLockOwnersNow = async (peerId) => {
|
|
208
|
+
const ownedTurns = await headlessLockOperationQueue.enqueue(async () => {
|
|
209
|
+
const owned = headlessLockOwners.filter((owner) => owner.peerId === peerId);
|
|
210
|
+
if (headlessLockOwners.some((owner) => !owner.handbackFailed && !owned.includes(owner))) {
|
|
211
|
+
for (let index = headlessLockOwners.length - 1; index >= 0; index--) {
|
|
212
|
+
const owner = headlessLockOwners[index];
|
|
213
|
+
if (owner && owned.includes(owner)) {
|
|
214
|
+
headlessLockOwners.splice(index, 1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return [];
|
|
205
218
|
}
|
|
219
|
+
return owned;
|
|
220
|
+
});
|
|
221
|
+
if (ownedTurns.length === 0) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
await lockService?.relinquishLockForTurn(() => headlessLockOwners.some((owner) => !owner.handbackFailed &&
|
|
226
|
+
(!ownedTurns.includes(owner) || owner.peerId !== peerId)));
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
await headlessLockOperationQueue.enqueue(async () => {
|
|
230
|
+
for (const owner of ownedTurns) {
|
|
231
|
+
if (headlessLockOwners.includes(owner) && owner.peerId === peerId) {
|
|
232
|
+
owner.handbackFailed = true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
await headlessLockOperationQueue.enqueue(async () => {
|
|
206
239
|
for (let index = headlessLockOwners.length - 1; index >= 0; index--) {
|
|
207
|
-
|
|
240
|
+
const owner = headlessLockOwners[index];
|
|
241
|
+
if (owner && owner.peerId === peerId && ownedTurns.includes(owner)) {
|
|
208
242
|
headlessLockOwners.splice(index, 1);
|
|
209
243
|
}
|
|
210
244
|
}
|
|
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
245
|
});
|
|
218
246
|
};
|
|
247
|
+
const dropHeadlessLockOwners = (peerId) => headlessLockHandbackQueue.enqueue(() => dropHeadlessLockOwnersNow(peerId));
|
|
219
248
|
const dropHeadlessLockOwnersInBackground = (peerId, occasion) => {
|
|
220
249
|
void dropHeadlessLockOwners(peerId).catch((err) => {
|
|
221
250
|
getLogger().warn(`[socket-manager] failed to hand the turn lock back after ${occasion}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -225,29 +254,52 @@ class SocketManager extends TracedEventEmitter {
|
|
|
225
254
|
dropHeadlessLockOwnersInBackground(peerId, "a gateway's liveness lapsed");
|
|
226
255
|
aiService?.endHeadlessTurn(peerId);
|
|
227
256
|
});
|
|
228
|
-
const
|
|
229
|
-
|
|
257
|
+
const releaseHeadlessLockNow = async (peerId, turnId) => {
|
|
258
|
+
const ownedTurn = await headlessLockOperationQueue.enqueue(async () => {
|
|
230
259
|
const ownerIndex = headlessLockOwners.findIndex((owner) => owner.peerId === peerId &&
|
|
231
260
|
(turnId ? owner.turnId === turnId : owner.turnId === undefined));
|
|
232
261
|
if (ownerIndex === -1) {
|
|
233
|
-
return
|
|
262
|
+
return undefined;
|
|
234
263
|
}
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
237
|
-
return
|
|
264
|
+
const owner = headlessLockOwners[ownerIndex];
|
|
265
|
+
if (!owner) {
|
|
266
|
+
return undefined;
|
|
238
267
|
}
|
|
239
|
-
|
|
240
|
-
|
|
268
|
+
if (headlessLockOwners.some((candidate) => candidate !== owner && !candidate.handbackFailed)) {
|
|
269
|
+
headlessLockOwners.splice(ownerIndex, 1);
|
|
241
270
|
return true;
|
|
242
271
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
272
|
+
owner.handbackFailed = false;
|
|
273
|
+
return owner;
|
|
274
|
+
});
|
|
275
|
+
if (!ownedTurn) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
if (ownedTurn === true) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
await lockService?.relinquishLockForTurn(() => headlessLockOwners.some((owner) => !owner.handbackFailed &&
|
|
283
|
+
(owner !== ownedTurn || owner.peerId !== peerId)));
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
await headlessLockOperationQueue.enqueue(async () => {
|
|
287
|
+
if (headlessLockOwners.includes(ownedTurn)) {
|
|
288
|
+
ownedTurn.handbackFailed = true;
|
|
246
289
|
}
|
|
247
|
-
|
|
290
|
+
});
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
return await headlessLockOperationQueue.enqueue(async () => {
|
|
294
|
+
const ownerIndex = headlessLockOwners.findIndex((owner) => owner === ownedTurn && owner.peerId === peerId);
|
|
295
|
+
if (ownerIndex === -1) {
|
|
296
|
+
return false;
|
|
248
297
|
}
|
|
298
|
+
headlessLockOwners.splice(ownerIndex, 1);
|
|
299
|
+
return true;
|
|
249
300
|
});
|
|
250
301
|
};
|
|
302
|
+
const releaseHeadlessLock = (peerId, turnId) => headlessLockHandbackQueue.enqueue(() => releaseHeadlessLockNow(peerId, turnId));
|
|
251
303
|
// Provide sync context (branchName, lastCommitId) to the AI service
|
|
252
304
|
// for remote context restore fallback from Bucketeer on fresh pods.
|
|
253
305
|
if (aiService && syncService) {
|
|
@@ -256,6 +308,8 @@ class SocketManager extends TracedEventEmitter {
|
|
|
256
308
|
getLastCommitId: () => syncService.lastCommitId,
|
|
257
309
|
});
|
|
258
310
|
}
|
|
311
|
+
aiService?.setAuthenticatedEditorPeersProvider?.(() => this.getAuthenticatedEditorPeers());
|
|
312
|
+
aiService?.setAuthenticatedGatewayPeersProvider?.(() => this.getAuthenticatedGatewayPeers());
|
|
259
313
|
// Sticky guard. Set to true by `gitDisconnect` and deliberately NOT
|
|
260
314
|
// cleared when it finishes: any callers of `getActiveGitService()` that
|
|
261
315
|
// started before disconnect but resolve after it completes must still
|
|
@@ -438,6 +492,13 @@ class SocketManager extends TracedEventEmitter {
|
|
|
438
492
|
if (!result.success) {
|
|
439
493
|
throw new SocketErrorException(result.status, result.error);
|
|
440
494
|
}
|
|
495
|
+
if (isEditor && this.editorSockets.has(socket)) {
|
|
496
|
+
this.authenticatedEditorSockets.add(socket);
|
|
497
|
+
}
|
|
498
|
+
if (kind === "gateway" && this.gatewaySockets.has(socket)) {
|
|
499
|
+
this.authenticatedGatewaySockets.add(socket);
|
|
500
|
+
this.sendEditorStructureReport(socket);
|
|
501
|
+
}
|
|
441
502
|
if (isEditor &&
|
|
442
503
|
userId &&
|
|
443
504
|
!this.editorPresenceUserIdByPeerId.has(peerId) &&
|
|
@@ -535,7 +596,8 @@ class SocketManager extends TracedEventEmitter {
|
|
|
535
596
|
// AI + draft handling
|
|
536
597
|
aiGenerate: [
|
|
537
598
|
async (request) => {
|
|
538
|
-
|
|
599
|
+
const inputSource = { kind, peerId };
|
|
600
|
+
await aiService?.handleAiGenerate(request, inputSource);
|
|
539
601
|
},
|
|
540
602
|
],
|
|
541
603
|
aiGenerateWithQueue: [
|
|
@@ -547,10 +609,12 @@ class SocketManager extends TracedEventEmitter {
|
|
|
547
609
|
// gateway peer that asked for the work, not to whichever
|
|
548
610
|
// socket connected most recently. The prompt's own
|
|
549
611
|
// `requestId` is the correlator that same caller presents to
|
|
550
|
-
// reacquire this turn after losing its socket.
|
|
612
|
+
// reacquire this turn after losing its socket. Legacy
|
|
613
|
+
// callers that omit it receive a server-minted identity.
|
|
551
614
|
const { queueMode, ...aiRequest } = request;
|
|
615
|
+
const inputSource = { kind, peerId };
|
|
552
616
|
if (kind !== "gateway") {
|
|
553
|
-
return await aiService.handleAiGenerateWithQueue(aiRequest, queueMode);
|
|
617
|
+
return await aiService.handleAiGenerateWithQueue(aiRequest, queueMode, inputSource);
|
|
554
618
|
}
|
|
555
619
|
// Write access is taken per turn, not held for the whole
|
|
556
620
|
// session, so the app is unlocked between prompts and a
|
|
@@ -558,10 +622,16 @@ class SocketManager extends TracedEventEmitter {
|
|
|
558
622
|
// awaited: a turn that cannot write must fail here carrying
|
|
559
623
|
// the server's own reason — somebody is editing in the
|
|
560
624
|
// browser — rather than bind routing and then unwind it.
|
|
561
|
-
const requestedTurnId = typeof request.requestId === "string"
|
|
625
|
+
const requestedTurnId = typeof request.requestId === "string" &&
|
|
626
|
+
request.requestId.length > 0
|
|
562
627
|
? request.requestId
|
|
563
|
-
:
|
|
628
|
+
: randomUUID();
|
|
629
|
+
const headlessRequest = {
|
|
630
|
+
...aiRequest,
|
|
631
|
+
requestId: requestedTurnId,
|
|
632
|
+
};
|
|
564
633
|
const lockOwner = {
|
|
634
|
+
handbackFailed: false,
|
|
565
635
|
peerId,
|
|
566
636
|
turnId: requestedTurnId,
|
|
567
637
|
};
|
|
@@ -572,7 +642,7 @@ class SocketManager extends TracedEventEmitter {
|
|
|
572
642
|
});
|
|
573
643
|
let result;
|
|
574
644
|
try {
|
|
575
|
-
result = await aiService.handleAiGenerateWithQueue(
|
|
645
|
+
result = await aiService.handleAiGenerateWithQueue(headlessRequest, queueMode, inputSource);
|
|
576
646
|
const ownerIndex = headlessLockOwners.indexOf(lockOwner);
|
|
577
647
|
for (let index = ownerIndex - 1; index >= 0; index--) {
|
|
578
648
|
if (headlessLockOwners[index]?.peerId === peerId) {
|
|
@@ -650,9 +720,27 @@ class SocketManager extends TracedEventEmitter {
|
|
|
650
720
|
commitId: result?.commitId,
|
|
651
721
|
silently: request.silently,
|
|
652
722
|
});
|
|
653
|
-
const
|
|
723
|
+
const handleCleanupError = (error) => {
|
|
724
|
+
if (!result?.commitId) {
|
|
725
|
+
throw error;
|
|
726
|
+
}
|
|
727
|
+
return false;
|
|
728
|
+
};
|
|
729
|
+
let draftStateCleared = await draftStateReport.catch(handleCleanupError);
|
|
654
730
|
if (draftStateCleared !== true) {
|
|
655
|
-
|
|
731
|
+
draftStateCleared = await syncService
|
|
732
|
+
.reportDraftStateToServer(false)
|
|
733
|
+
.catch(handleCleanupError);
|
|
734
|
+
}
|
|
735
|
+
if (draftStateCleared !== true) {
|
|
736
|
+
if (!result?.commitId) {
|
|
737
|
+
throw new Error("Failed to clear server draft state");
|
|
738
|
+
}
|
|
739
|
+
getLogger().warn("[aiAcceptDraft] checkpoint saved but draft-state cleanup failed", {
|
|
740
|
+
applicationId: syncService.applicationId,
|
|
741
|
+
branchName: syncService.branchName,
|
|
742
|
+
commitId: result.commitId,
|
|
743
|
+
});
|
|
656
744
|
}
|
|
657
745
|
if (result?.commitId) {
|
|
658
746
|
// Upload context file to bucketeer asynchronously (fire-and-forget)
|
|
@@ -697,14 +785,41 @@ class SocketManager extends TracedEventEmitter {
|
|
|
697
785
|
}
|
|
698
786
|
})();
|
|
699
787
|
}
|
|
700
|
-
return
|
|
701
|
-
commitId: result.commitId
|
|
702
|
-
|
|
788
|
+
return draftStateCleared === true
|
|
789
|
+
? { commitId: result.commitId }
|
|
790
|
+
: {
|
|
791
|
+
commitId: result.commitId,
|
|
792
|
+
warning: {
|
|
793
|
+
code: "draft_state_cleanup_failed",
|
|
794
|
+
message: "Checkpoint saved, but the server draft state could not be cleared",
|
|
795
|
+
},
|
|
796
|
+
};
|
|
703
797
|
}
|
|
704
798
|
getLogger().info("[aiAcceptDraft] No new checkpoint created for ai:accept (no hash changes from last checkpoint)");
|
|
705
799
|
return { commitId: "" };
|
|
706
800
|
},
|
|
707
801
|
],
|
|
802
|
+
aiUploadCheckpointScreenshot: [
|
|
803
|
+
async (request, context) => {
|
|
804
|
+
if (!syncService) {
|
|
805
|
+
throw new Error("Sync service not available");
|
|
806
|
+
}
|
|
807
|
+
const jwt = aiService?.getClarkJwt() ||
|
|
808
|
+
context.peerAuthorization?.replace("Bearer ", "");
|
|
809
|
+
const resolvedBaseUrl = superblocksBaseUrl || aiService?.superblocksBaseUrl;
|
|
810
|
+
if (!jwt || !resolvedBaseUrl) {
|
|
811
|
+
throw new Error("Screenshot upload credentials are not available");
|
|
812
|
+
}
|
|
813
|
+
await uploadCheckpointScreenshot({
|
|
814
|
+
applicationId: syncService.applicationId,
|
|
815
|
+
branchName: syncService.branchName,
|
|
816
|
+
commitId: request.commitId,
|
|
817
|
+
jwt,
|
|
818
|
+
screenshotDataUrl: request.screenshotDataUrl,
|
|
819
|
+
superblocksBaseUrl: resolvedBaseUrl,
|
|
820
|
+
});
|
|
821
|
+
},
|
|
822
|
+
],
|
|
708
823
|
aiClearDraftState: [
|
|
709
824
|
async () => {
|
|
710
825
|
await syncService?.clearDraftsDirectory();
|
|
@@ -717,8 +832,8 @@ class SocketManager extends TracedEventEmitter {
|
|
|
717
832
|
},
|
|
718
833
|
],
|
|
719
834
|
aiNotifyIntegrationSetupClosed: [
|
|
720
|
-
async () => {
|
|
721
|
-
await aiService?.handleIntegrationSetupClosed();
|
|
835
|
+
async (request) => {
|
|
836
|
+
await aiService?.handleIntegrationSetupClosed(request);
|
|
722
837
|
},
|
|
723
838
|
],
|
|
724
839
|
aiInterpretPrompt: [
|
|
@@ -1568,6 +1683,33 @@ class SocketManager extends TracedEventEmitter {
|
|
|
1568
1683
|
});
|
|
1569
1684
|
});
|
|
1570
1685
|
}
|
|
1686
|
+
reportEditorHasDefaultStructure(hasDefaultStructure) {
|
|
1687
|
+
if (hasDefaultStructure !== "yes" &&
|
|
1688
|
+
hasDefaultStructure !== "no" &&
|
|
1689
|
+
hasDefaultStructure !== "unknown") {
|
|
1690
|
+
throw new Error("Invalid editor structure report");
|
|
1691
|
+
}
|
|
1692
|
+
this.editorStructureReport = { hasDefaultStructure };
|
|
1693
|
+
for (const { peer } of this.getAuthenticatedGatewayPeers()) {
|
|
1694
|
+
this.sendEditorStructureReport(peer);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
sendEditorStructureReport(gateway) {
|
|
1698
|
+
const report = this.editorStructureReport;
|
|
1699
|
+
if (!report ||
|
|
1700
|
+
this.editorStructureReportByGateway.get(gateway) ===
|
|
1701
|
+
report.hasDefaultStructure) {
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
this.editorStructureReportByGateway.set(gateway, report.hasDefaultStructure);
|
|
1705
|
+
gateway.call.aiSetEditorHasDefaultStructure(report).catch((error) => {
|
|
1706
|
+
if (this.editorStructureReportByGateway.get(gateway) ===
|
|
1707
|
+
report.hasDefaultStructure) {
|
|
1708
|
+
this.editorStructureReportByGateway.delete(gateway);
|
|
1709
|
+
}
|
|
1710
|
+
errorHandler(error);
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1571
1713
|
callEditorClients(fn) {
|
|
1572
1714
|
for (const socket of this.editorSockets.keys()) {
|
|
1573
1715
|
fn(socket).catch(errorHandler);
|
|
@@ -1646,6 +1788,20 @@ class SocketManager extends TracedEventEmitter {
|
|
|
1646
1788
|
getEditorClients() {
|
|
1647
1789
|
return this.editorSockets.keys();
|
|
1648
1790
|
}
|
|
1791
|
+
*getAuthenticatedEditorPeers() {
|
|
1792
|
+
for (const [peer, peerId] of this.editorSockets) {
|
|
1793
|
+
if (this.authenticatedEditorSockets.has(peer)) {
|
|
1794
|
+
yield { peer, peerId };
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
*getAuthenticatedGatewayPeers() {
|
|
1799
|
+
for (const [peer, peerId] of this.gatewaySockets) {
|
|
1800
|
+
if (this.authenticatedGatewaySockets.has(peer)) {
|
|
1801
|
+
yield { peer, peerId };
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1649
1805
|
/**
|
|
1650
1806
|
* Attached headless gateway peers. Separate from {@link getEditorClients} so
|
|
1651
1807
|
* a broadcast meant for browser tabs never reaches a gateway, and the
|