surf-cli 2.7.2 → 2.9.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/README.md +208 -13
- package/dist/content/index.js +116 -0
- package/dist/content/index.js.map +1 -0
- package/dist/manifest.json +2 -11
- package/dist/options/options.js +3 -3
- package/dist/options/options.js.map +1 -1
- package/dist/service-worker/index.js +261 -61
- package/dist/service-worker/index.js.map +1 -1
- package/native/abort.cjs +65 -0
- package/native/ai-queue.cjs +64 -0
- package/native/aistudio-build.cjs +21 -13
- package/native/aistudio-client.cjs +40 -20
- package/native/browser-lock.cjs +169 -0
- package/native/chatgpt-client.cjs +63 -30
- package/native/cli.cjs +947 -460
- package/native/client-transport.cjs +168 -0
- package/native/config.cjs +2 -2
- package/native/do-executor.cjs +25 -51
- package/native/do-parser.cjs +12 -0
- package/native/doctor.cjs +633 -0
- package/native/endpoint.cjs +174 -0
- package/native/file-transfer.cjs +734 -0
- package/native/gemini-client.cjs +244 -88
- package/native/grok-client.cjs +321 -212
- package/native/host-helpers.cjs +88 -16
- package/native/host-sessions.cjs +283 -0
- package/native/host.cjs +811 -616
- package/native/listener.cjs +20 -0
- package/native/mcp-server.cjs +60 -62
- package/native/network-export.cjs +113 -0
- package/native/perplexity-client.cjs +46 -17
- package/native/remote-auth.cjs +279 -0
- package/native/remote-transport.cjs +337 -0
- package/native/request-pending.cjs +148 -0
- package/native/socket-path.cjs +46 -0
- package/package.json +11 -9
- package/scripts/install-native-host.cjs +184 -51
- package/scripts/uninstall-native-host.cjs +93 -15
- package/skills/README.md +11 -5
- package/skills/deep-x-research/SKILL.md +106 -0
- package/skills/surf/SKILL.md +77 -22
- package/dist/content/accessibility-tree.js +0 -11
- package/dist/content/accessibility-tree.js.map +0 -1
- package/dist/content/visual-indicator.js +0 -111
- package/dist/content/visual-indicator.js.map +0 -1
package/native/host.cjs
CHANGED
|
@@ -3,6 +3,8 @@ const net = require("net");
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const os = require("os");
|
|
6
|
+
const { AsyncLocalStorage } = require("async_hooks");
|
|
7
|
+
const requestStorage = new AsyncLocalStorage();
|
|
6
8
|
const https = require("https");
|
|
7
9
|
const { execSync } = require("child_process");
|
|
8
10
|
const { GoogleGenerativeAI } = require("@google/generative-ai");
|
|
@@ -12,13 +14,84 @@ const perplexityClient = require("./perplexity-client.cjs");
|
|
|
12
14
|
const grokClient = require("./grok-client.cjs");
|
|
13
15
|
const aistudioClient = require("./aistudio-client.cjs");
|
|
14
16
|
const aistudioBuild = require("./aistudio-build.cjs");
|
|
15
|
-
const { mapToolToMessage, mapComputerAction, formatToolContent } = require("./host-helpers.cjs");
|
|
17
|
+
const { mapToolToMessage, mapComputerAction, formatToolContent, buildProviderUploadMessage } = require("./host-helpers.cjs");
|
|
16
18
|
|
|
17
19
|
const IS_WIN = process.platform === "win32";
|
|
18
|
-
const SURF_TMP =
|
|
19
|
-
const
|
|
20
|
+
const { SOCKET_PATH, SURF_TMP } = require("./socket-path.cjs");
|
|
21
|
+
const { parseListenEndpoint } = require("./listener.cjs");
|
|
22
|
+
const { getStateDir } = require("./remote-auth.cjs");
|
|
23
|
+
const { createFrameParser, createServerAuthSession, createSocketWriter, isClientAuthorized, writeFrame, MAX_FRAME_BYTES } = require("./remote-transport.cjs");
|
|
24
|
+
const { HostSessionManager, resolveRequestDeadlineMs } = require("./host-sessions.cjs");
|
|
25
|
+
const { abortError, throwIfAborted } = require("./abort.cjs");
|
|
26
|
+
const { BoundedAiQueue } = require("./ai-queue.cjs");
|
|
27
|
+
const { RequestPendingMap } = require("./request-pending.cjs");
|
|
28
|
+
const { cleanupFilePaths, createStagingDirectory, createTransferState, materializeRemoteTool, rewriteTransferPaths, streamFileDownload, transferError } = require("./file-transfer.cjs");
|
|
29
|
+
const { writeNetworkExport } = require("./network-export.cjs");
|
|
30
|
+
const MAX_CLIENT_FRAME_BYTES = MAX_FRAME_BYTES;
|
|
31
|
+
const TEST_REQUEST_DEADLINE_MS = process.env.SURF_TEST_MODE === "1" && Number.isFinite(Number(process.env.SURF_TEST_REQUEST_DEADLINE_MS))
|
|
32
|
+
? Number(process.env.SURF_TEST_REQUEST_DEADLINE_MS)
|
|
33
|
+
: null;
|
|
20
34
|
if (IS_WIN) { try { fs.mkdirSync(SURF_TMP, { recursive: true }); } catch {} }
|
|
21
35
|
|
|
36
|
+
// The endpoint passed here is already validated by the caller. Keeping this
|
|
37
|
+
// lifecycle separate lets tests use an ephemeral loopback port without adding
|
|
38
|
+
// a localhost escape hatch to SURF_LISTEN parsing.
|
|
39
|
+
function createListenerLifecycle({ localPath, tcpEndpoint, handler, onReady, onFatal }) {
|
|
40
|
+
const localServer = net.createServer(handler);
|
|
41
|
+
const tcpServer = tcpEndpoint ? net.createServer(handler) : null;
|
|
42
|
+
let shuttingDown = false;
|
|
43
|
+
let startPromise = null;
|
|
44
|
+
const close = (server) => {
|
|
45
|
+
if (!server) return;
|
|
46
|
+
try { server.close(); } catch (error) {
|
|
47
|
+
if (error.code !== "ERR_SERVER_NOT_RUNNING") throw error;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const unlink = () => { if (!IS_WIN) { try { fs.unlinkSync(localPath); } catch {} } };
|
|
51
|
+
const listen = (server, options) => new Promise((resolve, reject) => {
|
|
52
|
+
server.once("error", reject);
|
|
53
|
+
server.listen(options, () => {
|
|
54
|
+
server.removeListener("error", reject);
|
|
55
|
+
if (shuttingDown) { close(server); unlink(); }
|
|
56
|
+
resolve();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
const start = () => {
|
|
60
|
+
if (startPromise) return startPromise;
|
|
61
|
+
startPromise = (async () => {
|
|
62
|
+
try {
|
|
63
|
+
await listen(localServer, localPath);
|
|
64
|
+
if (shuttingDown) return false;
|
|
65
|
+
if (!IS_WIN) { try { fs.chmodSync(localPath, 0o600); } catch {} }
|
|
66
|
+
if (tcpServer) {
|
|
67
|
+
await listen(tcpServer, tcpEndpoint);
|
|
68
|
+
if (shuttingDown) return false;
|
|
69
|
+
}
|
|
70
|
+
onReady();
|
|
71
|
+
return true;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (!shuttingDown) onFatal(error);
|
|
74
|
+
close(localServer); close(tcpServer); unlink();
|
|
75
|
+
return false;
|
|
76
|
+
} finally {
|
|
77
|
+
if (shuttingDown) { close(localServer); close(tcpServer); unlink(); }
|
|
78
|
+
}
|
|
79
|
+
})();
|
|
80
|
+
return startPromise;
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
localServer,
|
|
84
|
+
tcpServer,
|
|
85
|
+
start,
|
|
86
|
+
async shutdown() {
|
|
87
|
+
shuttingDown = true;
|
|
88
|
+
close(localServer); close(tcpServer); unlink();
|
|
89
|
+
if (startPromise) await startPromise;
|
|
90
|
+
unlink();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
22
95
|
// Cross-platform image resize (macOS: sips, Linux: ImageMagick)
|
|
23
96
|
function resizeImage(filePath, maxSize) {
|
|
24
97
|
const platform = process.platform;
|
|
@@ -55,29 +128,9 @@ function resizeImage(filePath, maxSize) {
|
|
|
55
128
|
}
|
|
56
129
|
}
|
|
57
130
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
function queueAiRequest(handler) {
|
|
62
|
-
return new Promise((resolve, reject) => {
|
|
63
|
-
aiRequestQueue.push({ handler, resolve, reject });
|
|
64
|
-
processAiQueue();
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function processAiQueue() {
|
|
69
|
-
if (aiRequestInProgress || aiRequestQueue.length === 0) return;
|
|
70
|
-
aiRequestInProgress = true;
|
|
71
|
-
const { handler, resolve, reject } = aiRequestQueue.shift();
|
|
72
|
-
try {
|
|
73
|
-
const result = await handler();
|
|
74
|
-
resolve(result);
|
|
75
|
-
} catch (err) {
|
|
76
|
-
reject(err);
|
|
77
|
-
} finally {
|
|
78
|
-
aiRequestInProgress = false;
|
|
79
|
-
setTimeout(processAiQueue, 2000);
|
|
80
|
-
}
|
|
131
|
+
let aiQueue;
|
|
132
|
+
function queueAiRequest(handler, request = requestStorage.getStore()) {
|
|
133
|
+
return aiQueue.enqueue(handler, request);
|
|
81
134
|
}
|
|
82
135
|
const LOG_FILE = path.join(SURF_TMP, "surf-host.log");
|
|
83
136
|
const AUTH_FILE = path.join(os.homedir(), ".pi", "agent", "auth.json");
|
|
@@ -90,7 +143,8 @@ const DEFAULT_RETRY_OPTIONS = {
|
|
|
90
143
|
retryableStatusCodes: [429, 500, 502, 503, 504]
|
|
91
144
|
};
|
|
92
145
|
|
|
93
|
-
async function withRetry(fn, retryOptions = DEFAULT_RETRY_OPTIONS, retryCount = 0) {
|
|
146
|
+
async function withRetry(fn, retryOptions = DEFAULT_RETRY_OPTIONS, retryCount = 0, signal) {
|
|
147
|
+
throwIfAborted(signal);
|
|
94
148
|
try {
|
|
95
149
|
return await fn();
|
|
96
150
|
} catch (error) {
|
|
@@ -126,8 +180,8 @@ async function withRetry(fn, retryOptions = DEFAULT_RETRY_OPTIONS, retryCount =
|
|
|
126
180
|
const jitter = 0.8 + Math.random() * 0.4;
|
|
127
181
|
const delayWithJitter = Math.floor(delay * jitter);
|
|
128
182
|
|
|
129
|
-
await
|
|
130
|
-
return withRetry(fn, retryOptions, retryCount + 1);
|
|
183
|
+
await require("./abort.cjs").abortableDelay(delayWithJitter, signal);
|
|
184
|
+
return withRetry(fn, retryOptions, retryCount + 1, signal);
|
|
131
185
|
}
|
|
132
186
|
}
|
|
133
187
|
|
|
@@ -195,13 +249,14 @@ class GeminiClient {
|
|
|
195
249
|
|
|
196
250
|
async analyze(query, pageContext, options = {}) {
|
|
197
251
|
const mode = options.mode || detectQueryMode(query);
|
|
252
|
+
throwIfAborted(options.signal);
|
|
198
253
|
const promptFn = AI_PROMPTS[mode];
|
|
199
254
|
const prompt = promptFn(query, pageContext);
|
|
200
255
|
|
|
201
256
|
const result = await withRetry(async () => {
|
|
202
257
|
const response = await this.model.generateContent(prompt);
|
|
203
258
|
return response.response.text();
|
|
204
|
-
});
|
|
259
|
+
}, DEFAULT_RETRY_OPTIONS, 0, options.signal);
|
|
205
260
|
|
|
206
261
|
let content = result.trim();
|
|
207
262
|
|
|
@@ -292,57 +347,238 @@ const log = (msg) => {
|
|
|
292
347
|
fs.appendFileSync(LOG_FILE, `${new Date().toISOString()} ${msg}\n`);
|
|
293
348
|
};
|
|
294
349
|
|
|
350
|
+
if (require.main === module) {
|
|
295
351
|
log("Host starting...");
|
|
296
352
|
|
|
297
353
|
if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} }
|
|
298
354
|
|
|
299
355
|
const pendingRequests = new Map();
|
|
300
|
-
const pendingToolRequests = new
|
|
356
|
+
const pendingToolRequests = new RequestPendingMap({ getRequest: () => requestStorage.getStore() });
|
|
301
357
|
const activeStreams = new Map();
|
|
358
|
+
const socketContexts = new WeakMap();
|
|
359
|
+
const socketWriters = new WeakMap();
|
|
302
360
|
let requestCounter = 0;
|
|
303
361
|
|
|
304
|
-
function
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
362
|
+
function auditSession(event) {
|
|
363
|
+
const context = event.context;
|
|
364
|
+
const principal = context?.principal;
|
|
365
|
+
const request = event.request;
|
|
366
|
+
log(`SESSION ${JSON.stringify({
|
|
367
|
+
event: event.event,
|
|
368
|
+
outcome: event.outcome,
|
|
369
|
+
principalId: principal?.clientId || "local",
|
|
370
|
+
principalLabel: principal?.label || "local",
|
|
371
|
+
peer: context?.socket?.remoteAddress || "local",
|
|
372
|
+
requestId: request?.id,
|
|
373
|
+
tool: request?.tool,
|
|
374
|
+
elapsedMs: event.elapsedMs,
|
|
375
|
+
})}`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
aiQueue = new BoundedAiQueue({
|
|
379
|
+
maxQueued: 8,
|
|
380
|
+
audit: (event) => auditSession(event),
|
|
381
|
+
run: (handler, request) => request
|
|
382
|
+
? requestStorage.run(request, () => handler())
|
|
383
|
+
: handler(),
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
function sendSocket(socket, value, options = {}) {
|
|
387
|
+
const writer = socketWriters.get(socket);
|
|
388
|
+
return writer ? writer.send(value, options) : writeFrame(socket, value);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function sendOwnedExtensionMessage(request, message) {
|
|
392
|
+
const cleanupMessage = typeof message?.type === "string" && /(?:CLOSE_TAB|TAB_CLOSE)$/.test(message.type);
|
|
393
|
+
if (request?.hardBoundary) throwIfAborted(request.signal, "Request timed out");
|
|
394
|
+
if (!cleanupMessage) throwIfAborted(request?.signal, "Request cancelled");
|
|
395
|
+
writeMessage(message);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function requestCallExtension(request, tool, message, timeoutMs = 30000, cleanup = false) {
|
|
399
|
+
cleanup = cleanup || tool === "close_tab";
|
|
400
|
+
if (request?.hardBoundary) throwIfAborted(request.signal, "Request timed out");
|
|
401
|
+
if (!cleanup) throwIfAborted(request?.signal, "Request cancelled");
|
|
402
|
+
return new Promise((resolve, reject) => {
|
|
403
|
+
const id = ++requestCounter;
|
|
404
|
+
const timer = setTimeout(() => {
|
|
405
|
+
pendingToolRequests.expire(id, new Error(`Timeout waiting for extension: ${tool}`));
|
|
406
|
+
}, timeoutMs);
|
|
407
|
+
const pending = {
|
|
408
|
+
request,
|
|
409
|
+
cleanup,
|
|
410
|
+
tool,
|
|
411
|
+
resolve: (result) => {
|
|
412
|
+
clearTimeout(timer);
|
|
413
|
+
resolve(result);
|
|
414
|
+
},
|
|
415
|
+
reject: (error) => {
|
|
416
|
+
clearTimeout(timer);
|
|
417
|
+
reject(error);
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
pendingToolRequests.set(id, pending);
|
|
421
|
+
try {
|
|
422
|
+
if (cleanup) writeMessage({ ...message, id });
|
|
423
|
+
else sendOwnedExtensionMessage(request, { ...message, id });
|
|
424
|
+
} catch (error) {
|
|
425
|
+
pendingToolRequests.delete(id);
|
|
426
|
+
reject(error);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const sessionManager = new HostSessionManager({
|
|
432
|
+
audit: auditSession,
|
|
433
|
+
onTimeout(context, request) {
|
|
434
|
+
pendingToolRequests.hardDeadline(request);
|
|
435
|
+
const response = {
|
|
436
|
+
type: "tool_response",
|
|
437
|
+
id: request.id,
|
|
438
|
+
error: { content: [{ type: "text", text: "Request timed out" }] },
|
|
439
|
+
};
|
|
440
|
+
cleanupRequestTransfers(request)
|
|
441
|
+
.then(() => {
|
|
442
|
+
sessionManager.complete(context, request.id, "hard-timeout");
|
|
443
|
+
if (!context.closed) return sendSocket(context.socket, response);
|
|
444
|
+
})
|
|
445
|
+
.catch((error) => log(`Error settling timed-out request: ${error.message}`));
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
async function discardRequestTransfers(message, state) {
|
|
450
|
+
if (!state) return;
|
|
451
|
+
const ids = [];
|
|
452
|
+
for (const entry of message?._surfTransfers?.uploads || []) if (typeof entry?.transferId === "string") ids.push(entry.transferId);
|
|
453
|
+
for (const entry of message?._surfTransfers?.downloads || []) if (typeof entry?.transferId === "string") ids.push(entry.transferId);
|
|
454
|
+
await Promise.all([...new Set(ids)].map((id) => state.discardCompleted(id)));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async function applyRequestTransfers(msg, request, transferState, getTransferState) {
|
|
458
|
+
if (!request.context?.isRemote) return;
|
|
459
|
+
const materialized = await materializeRemoteTool({
|
|
460
|
+
tool: request.tool,
|
|
461
|
+
args: msg.params?.args || {},
|
|
462
|
+
metadata: msg._surfTransfers,
|
|
463
|
+
pathRefs: msg._surfPaths || [],
|
|
464
|
+
transferState,
|
|
465
|
+
getTransferState,
|
|
466
|
+
});
|
|
467
|
+
request.transferState = materialized.transferState;
|
|
468
|
+
request.outputTransfers = materialized.outputTransfers;
|
|
469
|
+
request.pathRewrites = materialized.pathRewrites;
|
|
470
|
+
request.transferCleanup = materialized.transferCleanup;
|
|
471
|
+
msg.params = { ...msg.params, args: materialized.args };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function cleanupRequestTransfers(request) {
|
|
475
|
+
if (!request || request.transferCleanupStarted) return request?.transferCleanupPromise;
|
|
476
|
+
request.transferCleanupStarted = true;
|
|
477
|
+
const paths = request.transferCleanup || [];
|
|
478
|
+
request.transferCleanup = [];
|
|
479
|
+
request.transferCleanupPromise = cleanupFilePaths(paths);
|
|
480
|
+
await request.transferCleanupPromise;
|
|
481
|
+
return request.transferCleanupPromise;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function completeOwnedRequest(context, id, outcome) {
|
|
485
|
+
const request = context?.activeRequest;
|
|
486
|
+
if (!request || request.id !== id) return Promise.resolve();
|
|
487
|
+
if (request.pendingEntries?.size && !request.hardBoundary) {
|
|
488
|
+
if (request.completionRequested) return request.completionPromise;
|
|
489
|
+
request.completionRequested = true;
|
|
490
|
+
request.completionOutcome = outcome;
|
|
491
|
+
request.completionPromise = new Promise((resolve) => {
|
|
492
|
+
pendingToolRequests.onDrain(request, () => {
|
|
493
|
+
sessionManager.complete(context, id, request.completionOutcome);
|
|
494
|
+
resolve();
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
return request.completionPromise;
|
|
311
498
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
499
|
+
sessionManager.complete(context, id, outcome);
|
|
500
|
+
return Promise.resolve();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async function sendRequestDownloads(context, request, result) {
|
|
504
|
+
if (!request) return result;
|
|
505
|
+
let rewritten = result;
|
|
506
|
+
for (const output of request.outputTransfers || []) {
|
|
507
|
+
await streamFileDownload({
|
|
508
|
+
writer: { send: (frame) => sendSocket(context.socket, frame) },
|
|
509
|
+
state: request.transferState,
|
|
510
|
+
filePath: output.path,
|
|
511
|
+
transferId: output.transferId,
|
|
512
|
+
original: output.original,
|
|
513
|
+
});
|
|
317
514
|
}
|
|
515
|
+
rewritten = rewriteTransferPaths(rewritten, request.pathRewrites || []);
|
|
516
|
+
return rewritten;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function sendToolResponse(socket, id, result, error) {
|
|
520
|
+
const context = socketContexts.get(socket);
|
|
521
|
+
if (context && !sessionManager.canRespond(context, id)) return;
|
|
522
|
+
const request = context?.activeRequest;
|
|
523
|
+
let finalError = error;
|
|
524
|
+
(async () => {
|
|
525
|
+
let output = result;
|
|
526
|
+
try {
|
|
527
|
+
if (!error) output = await sendRequestDownloads(context, request, result);
|
|
528
|
+
} catch (transferFailure) {
|
|
529
|
+
finalError = transferFailure.message;
|
|
530
|
+
}
|
|
531
|
+
if (finalError && request) {
|
|
532
|
+
finalError = rewriteTransferPaths(finalError, request.pathRewrites || []);
|
|
533
|
+
}
|
|
534
|
+
await cleanupRequestTransfers(request);
|
|
535
|
+
if (request?.settled) return;
|
|
536
|
+
const outcome = request?.signal.aborted
|
|
537
|
+
? (request.tombstoned ? "cleanup-settled" : "cancelled")
|
|
538
|
+
: finalError ? "error" : "completed";
|
|
539
|
+
await completeOwnedRequest(context, id, outcome);
|
|
540
|
+
const response = { type: "tool_response", id };
|
|
541
|
+
if (finalError) response.error = { content: [{ type: "text", text: finalError }] };
|
|
542
|
+
else response.result = { content: formatToolContent(output, log, { suppressImages: Boolean(context?.isRemote) }) };
|
|
543
|
+
if (!context?.closed) await sendSocket(socket, response);
|
|
544
|
+
})().catch((sendError) => log(`Error sending tool_response: ${sendError.message}`));
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function stopActiveStream(streamId, { notifyExtension = true } = {}) {
|
|
548
|
+
const stream = activeStreams.get(streamId);
|
|
549
|
+
if (!stream) return;
|
|
550
|
+
activeStreams.delete(streamId);
|
|
551
|
+
sessionManager.stopStream(socketContexts.get(stream.socket));
|
|
552
|
+
if (notifyExtension) writeMessage({ type: "STREAM_STOP", streamId });
|
|
318
553
|
}
|
|
319
554
|
|
|
320
555
|
function handleStreamRequest(msg, socket) {
|
|
321
556
|
const { streamType, options, id: originalId } = msg;
|
|
322
557
|
const tabId = msg.tabId;
|
|
323
558
|
const streamId = ++requestCounter;
|
|
324
|
-
|
|
559
|
+
|
|
325
560
|
activeStreams.set(streamId, {
|
|
326
561
|
socket,
|
|
327
562
|
originalId,
|
|
328
563
|
streamType,
|
|
329
564
|
});
|
|
330
|
-
|
|
565
|
+
|
|
331
566
|
writeMessage({
|
|
332
567
|
type: streamType,
|
|
333
568
|
streamId,
|
|
334
569
|
options: options || {},
|
|
335
570
|
tabId,
|
|
336
571
|
});
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
572
|
+
|
|
573
|
+
sendSocket(socket, { type: "stream_started", streamId }, { stream: true }).catch((error) => {
|
|
574
|
+
log(`Error sending stream_started: ${error.message}`);
|
|
575
|
+
stopActiveStream(streamId);
|
|
576
|
+
socket.destroy(error);
|
|
577
|
+
});
|
|
343
578
|
}
|
|
344
579
|
|
|
345
|
-
function handleToolRequest(msg, socket) {
|
|
580
|
+
function handleToolRequest(msg, socket, requestContext = requestStorage.getStore()) {
|
|
581
|
+
const writeMessage = (message) => sendOwnedExtensionMessage(requestContext, message);
|
|
346
582
|
const { method, params } = msg;
|
|
347
583
|
const originalId = msg.id || null;
|
|
348
584
|
|
|
@@ -384,14 +620,14 @@ function handleToolRequest(msg, socket) {
|
|
|
384
620
|
}
|
|
385
621
|
|
|
386
622
|
if (extensionMsg.type === "LOCAL_WAIT") {
|
|
387
|
-
|
|
388
|
-
sendToolResponse(socket, originalId, { success: true }, null)
|
|
389
|
-
|
|
623
|
+
require("./abort.cjs").abortableDelay(extensionMsg.seconds * 1000, requestContext.signal)
|
|
624
|
+
.then(() => sendToolResponse(socket, originalId, { success: true }, null))
|
|
625
|
+
.catch((error) => sendToolResponse(socket, originalId, null, error.message));
|
|
390
626
|
return;
|
|
391
627
|
}
|
|
392
628
|
|
|
393
629
|
if (extensionMsg.type === "BATCH_EXECUTE") {
|
|
394
|
-
executeBatch(extensionMsg.actions, extensionMsg.tabId, socket, originalId);
|
|
630
|
+
executeBatch(extensionMsg.actions, extensionMsg.tabId, socket, originalId, requestContext);
|
|
395
631
|
return;
|
|
396
632
|
}
|
|
397
633
|
|
|
@@ -407,46 +643,23 @@ function handleToolRequest(msg, socket) {
|
|
|
407
643
|
return;
|
|
408
644
|
}
|
|
409
645
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
try {
|
|
428
|
-
const gemini = getGeminiClient(apiKey);
|
|
429
|
-
const result = await gemini.analyze(extensionMsg.query, pageContent, { mode: extensionMsg.mode });
|
|
430
|
-
|
|
431
|
-
if (result.mode === "find") {
|
|
432
|
-
sendToolResponse(socket, originalId, {
|
|
433
|
-
ref: result.content === "NOT_FOUND" ? null : result.content,
|
|
434
|
-
mode: result.mode,
|
|
435
|
-
aiResult: true
|
|
436
|
-
}, null);
|
|
437
|
-
} else {
|
|
438
|
-
sendToolResponse(socket, originalId, {
|
|
439
|
-
content: result.content,
|
|
440
|
-
mode: result.mode,
|
|
441
|
-
aiResult: true
|
|
442
|
-
}, null);
|
|
443
|
-
}
|
|
444
|
-
} catch (err) {
|
|
445
|
-
sendToolResponse(socket, originalId, null, `AI analysis failed: ${err.message}`);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
646
|
+
requestCallExtension(
|
|
647
|
+
requestContext,
|
|
648
|
+
"read_page",
|
|
649
|
+
{ type: "READ_PAGE", options: { filter: "interactive" }, tabId: extensionMsg.tabId },
|
|
650
|
+
45000,
|
|
651
|
+
).then(async (pageResult) => {
|
|
652
|
+
if (pageResult.error) throw new Error(`Failed to read page: ${pageResult.error}`);
|
|
653
|
+
const pageContent = pageResult.pageContent || "";
|
|
654
|
+
if (!pageContent) throw new Error("No page content available");
|
|
655
|
+
const gemini = getGeminiClient(apiKey);
|
|
656
|
+
const result = await gemini.analyze(extensionMsg.query, pageContent, { mode: extensionMsg.mode, signal: requestContext.signal });
|
|
657
|
+
return result.mode === "find"
|
|
658
|
+
? { ref: result.content === "NOT_FOUND" ? null : result.content, mode: result.mode, aiResult: true }
|
|
659
|
+
: { content: result.content, mode: result.mode, aiResult: true };
|
|
660
|
+
}).then((result) => sendToolResponse(socket, originalId, result, null)).catch((err) => {
|
|
661
|
+
sendToolResponse(socket, originalId, null, err.message);
|
|
448
662
|
});
|
|
449
|
-
writeMessage({ type: "READ_PAGE", options: { filter: "interactive" }, tabId: extensionMsg.tabId, id: pageRequestId });
|
|
450
663
|
return;
|
|
451
664
|
}
|
|
452
665
|
|
|
@@ -456,16 +669,12 @@ function handleToolRequest(msg, socket) {
|
|
|
456
669
|
queueAiRequest(async () => {
|
|
457
670
|
let pageContext = null;
|
|
458
671
|
if (withPage) {
|
|
459
|
-
const pageResult = await
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
onComplete: resolve
|
|
466
|
-
});
|
|
467
|
-
writeMessage({ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId, id: pageId });
|
|
468
|
-
});
|
|
672
|
+
const pageResult = await requestCallExtension(
|
|
673
|
+
requestContext,
|
|
674
|
+
"read_page",
|
|
675
|
+
{ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId },
|
|
676
|
+
45000,
|
|
677
|
+
);
|
|
469
678
|
if (pageResult && !pageResult.error) {
|
|
470
679
|
pageContext = {
|
|
471
680
|
url: pageResult.url,
|
|
@@ -481,59 +690,36 @@ function handleToolRequest(msg, socket) {
|
|
|
481
690
|
|
|
482
691
|
const result = await chatgptClient.query({
|
|
483
692
|
prompt: fullPrompt,
|
|
693
|
+
signal: requestContext.signal,
|
|
484
694
|
model,
|
|
485
695
|
file,
|
|
486
696
|
timeout,
|
|
487
|
-
getCookies: () =>
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
onComplete: (r) => resolve(r)
|
|
514
|
-
});
|
|
515
|
-
writeMessage({ type: "CHATGPT_CLOSE_TAB", tabId: tabIdToClose, id: tabCloseId });
|
|
516
|
-
}),
|
|
517
|
-
cdpEvaluate: (tabId, expression) => new Promise((resolve) => {
|
|
518
|
-
const evalId = ++requestCounter;
|
|
519
|
-
pendingToolRequests.set(evalId, {
|
|
520
|
-
socket: null,
|
|
521
|
-
originalId: null,
|
|
522
|
-
tool: "cdp_evaluate",
|
|
523
|
-
onComplete: (r) => resolve(r)
|
|
524
|
-
});
|
|
525
|
-
writeMessage({ type: "CHATGPT_EVALUATE", tabId, expression, id: evalId });
|
|
526
|
-
}),
|
|
527
|
-
cdpCommand: (tabId, method, params) => new Promise((resolve) => {
|
|
528
|
-
const cmdId = ++requestCounter;
|
|
529
|
-
pendingToolRequests.set(cmdId, {
|
|
530
|
-
socket: null,
|
|
531
|
-
originalId: null,
|
|
532
|
-
tool: "cdp_command",
|
|
533
|
-
onComplete: (r) => resolve(r)
|
|
534
|
-
});
|
|
535
|
-
writeMessage({ type: "CHATGPT_CDP_COMMAND", tabId, method, params, id: cmdId });
|
|
536
|
-
}),
|
|
697
|
+
getCookies: () => requestCallExtension(
|
|
698
|
+
requestContext,
|
|
699
|
+
"get_cookies",
|
|
700
|
+
{ type: "GET_CHATGPT_COOKIES" },
|
|
701
|
+
),
|
|
702
|
+
createTab: () => requestCallExtension(
|
|
703
|
+
requestContext,
|
|
704
|
+
"create_tab",
|
|
705
|
+
{ type: "CHATGPT_NEW_TAB" },
|
|
706
|
+
),
|
|
707
|
+
closeTab: (tabIdToClose) => requestCallExtension(requestContext, "close_tab", { type: "CHATGPT_CLOSE_TAB", tabId: tabIdToClose }, 45000, true),
|
|
708
|
+
cdpEvaluate: (tabId, expression) => requestCallExtension(
|
|
709
|
+
requestContext,
|
|
710
|
+
"cdp_evaluate",
|
|
711
|
+
{ type: "CHATGPT_EVALUATE", tabId, expression },
|
|
712
|
+
),
|
|
713
|
+
cdpCommand: (tabId, method, params) => requestCallExtension(
|
|
714
|
+
requestContext,
|
|
715
|
+
"cdp_command",
|
|
716
|
+
{ type: "CHATGPT_CDP_COMMAND", tabId, method, params },
|
|
717
|
+
),
|
|
718
|
+
uploadFile: (tabId, filePaths) => requestCallExtension(
|
|
719
|
+
requestContext,
|
|
720
|
+
"upload_file",
|
|
721
|
+
buildProviderUploadMessage("chatgpt", tabId, filePaths),
|
|
722
|
+
),
|
|
537
723
|
log: (msg) => log(`[chatgpt] ${msg}`)
|
|
538
724
|
});
|
|
539
725
|
|
|
@@ -557,16 +743,12 @@ function handleToolRequest(msg, socket) {
|
|
|
557
743
|
queueAiRequest(async () => {
|
|
558
744
|
let pageContext = null;
|
|
559
745
|
if (withPage) {
|
|
560
|
-
const pageResult = await
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
onComplete: resolve
|
|
567
|
-
});
|
|
568
|
-
writeMessage({ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId, id: pageId });
|
|
569
|
-
});
|
|
746
|
+
const pageResult = await requestCallExtension(
|
|
747
|
+
requestContext,
|
|
748
|
+
"read_page",
|
|
749
|
+
{ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId },
|
|
750
|
+
45000,
|
|
751
|
+
);
|
|
570
752
|
if (pageResult && !pageResult.error) {
|
|
571
753
|
pageContext = {
|
|
572
754
|
url: pageResult.url,
|
|
@@ -582,49 +764,26 @@ function handleToolRequest(msg, socket) {
|
|
|
582
764
|
|
|
583
765
|
const result = await perplexityClient.query({
|
|
584
766
|
prompt: fullPrompt,
|
|
767
|
+
signal: requestContext.signal,
|
|
585
768
|
mode: mode || 'search',
|
|
586
769
|
model,
|
|
587
770
|
timeout: timeout || 120000,
|
|
588
|
-
createTab: () =>
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
onComplete: (r) => resolve(r)
|
|
605
|
-
});
|
|
606
|
-
writeMessage({ type: "PERPLEXITY_CLOSE_TAB", tabId: tabIdToClose, id: tabCloseId });
|
|
607
|
-
}),
|
|
608
|
-
cdpEvaluate: (tabId, expression) => new Promise((resolve) => {
|
|
609
|
-
const evalId = ++requestCounter;
|
|
610
|
-
pendingToolRequests.set(evalId, {
|
|
611
|
-
socket: null,
|
|
612
|
-
originalId: null,
|
|
613
|
-
tool: "cdp_evaluate",
|
|
614
|
-
onComplete: (r) => resolve(r)
|
|
615
|
-
});
|
|
616
|
-
writeMessage({ type: "PERPLEXITY_EVALUATE", tabId, expression, id: evalId });
|
|
617
|
-
}),
|
|
618
|
-
cdpCommand: (tabId, method, params) => new Promise((resolve) => {
|
|
619
|
-
const cmdId = ++requestCounter;
|
|
620
|
-
pendingToolRequests.set(cmdId, {
|
|
621
|
-
socket: null,
|
|
622
|
-
originalId: null,
|
|
623
|
-
tool: "cdp_command",
|
|
624
|
-
onComplete: (r) => resolve(r)
|
|
625
|
-
});
|
|
626
|
-
writeMessage({ type: "PERPLEXITY_CDP_COMMAND", tabId, method, params, id: cmdId });
|
|
627
|
-
}),
|
|
771
|
+
createTab: () => requestCallExtension(
|
|
772
|
+
requestContext,
|
|
773
|
+
"create_tab",
|
|
774
|
+
{ type: "PERPLEXITY_NEW_TAB" },
|
|
775
|
+
),
|
|
776
|
+
closeTab: (tabIdToClose) => requestCallExtension(requestContext, "close_tab", { type: "PERPLEXITY_CLOSE_TAB", tabId: tabIdToClose }, 45000, true),
|
|
777
|
+
cdpEvaluate: (tabId, expression) => requestCallExtension(
|
|
778
|
+
requestContext,
|
|
779
|
+
"cdp_evaluate",
|
|
780
|
+
{ type: "PERPLEXITY_EVALUATE", tabId, expression },
|
|
781
|
+
),
|
|
782
|
+
cdpCommand: (tabId, method, params) => requestCallExtension(
|
|
783
|
+
requestContext,
|
|
784
|
+
"cdp_command",
|
|
785
|
+
{ type: "PERPLEXITY_CDP_COMMAND", tabId, method, params },
|
|
786
|
+
),
|
|
628
787
|
log: (msg) => log(`[perplexity] ${msg}`)
|
|
629
788
|
});
|
|
630
789
|
|
|
@@ -652,16 +811,12 @@ function handleToolRequest(msg, socket) {
|
|
|
652
811
|
// 1. Get page context if requested
|
|
653
812
|
let pageContext = null;
|
|
654
813
|
if (withPage) {
|
|
655
|
-
const pageResult = await
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
onComplete: resolve
|
|
662
|
-
});
|
|
663
|
-
writeMessage({ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId, id: pageId });
|
|
664
|
-
});
|
|
814
|
+
const pageResult = await requestCallExtension(
|
|
815
|
+
requestContext,
|
|
816
|
+
"get_page_text",
|
|
817
|
+
{ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId },
|
|
818
|
+
45000,
|
|
819
|
+
);
|
|
665
820
|
if (pageResult && !pageResult.error) {
|
|
666
821
|
pageContext = {
|
|
667
822
|
url: pageResult.url,
|
|
@@ -679,7 +834,8 @@ function handleToolRequest(msg, socket) {
|
|
|
679
834
|
// 3. Call Gemini client
|
|
680
835
|
const result = await geminiClient.query({
|
|
681
836
|
prompt: fullPrompt,
|
|
682
|
-
|
|
837
|
+
signal: requestContext.signal,
|
|
838
|
+
model: model || "gemini-3.1-pro",
|
|
683
839
|
file,
|
|
684
840
|
generateImage,
|
|
685
841
|
editImage,
|
|
@@ -687,67 +843,32 @@ function handleToolRequest(msg, socket) {
|
|
|
687
843
|
youtube,
|
|
688
844
|
aspectRatio,
|
|
689
845
|
timeout: timeout || 300000,
|
|
690
|
-
getCookies: () =>
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
onComplete: (r) => resolve(r)
|
|
717
|
-
});
|
|
718
|
-
writeMessage({ type: "GEMINI_CLOSE_TAB", tabId: tabIdToClose, id: tabCloseId });
|
|
719
|
-
}),
|
|
720
|
-
jsEval: (tabId, code) => new Promise((resolve) => {
|
|
721
|
-
const jsId = ++requestCounter;
|
|
722
|
-
pendingToolRequests.set(jsId, {
|
|
723
|
-
socket: null,
|
|
724
|
-
originalId: null,
|
|
725
|
-
tool: "js_eval",
|
|
726
|
-
onComplete: (r) => resolve(r)
|
|
727
|
-
});
|
|
728
|
-
log(`[gemini] Sending EXECUTE_JAVASCRIPT id=${jsId} tabId=${tabId} code=${code.length} chars`);
|
|
729
|
-
writeMessage({ type: "EXECUTE_JAVASCRIPT", tabId, code, id: jsId });
|
|
730
|
-
}),
|
|
731
|
-
uploadFile: (tabId, filePaths) => new Promise((resolve) => {
|
|
732
|
-
const uploadId = ++requestCounter;
|
|
733
|
-
pendingToolRequests.set(uploadId, {
|
|
734
|
-
socket: null,
|
|
735
|
-
originalId: null,
|
|
736
|
-
tool: "upload_file",
|
|
737
|
-
onComplete: (r) => resolve(r)
|
|
738
|
-
});
|
|
739
|
-
writeMessage({ type: "UPLOAD_FILE_TO_TAB", tabId, filePaths, id: uploadId });
|
|
740
|
-
}),
|
|
741
|
-
fetchUrl: (url) => new Promise((resolve) => {
|
|
742
|
-
const fetchId = ++requestCounter;
|
|
743
|
-
pendingToolRequests.set(fetchId, {
|
|
744
|
-
socket: null,
|
|
745
|
-
originalId: null,
|
|
746
|
-
tool: "fetch_url",
|
|
747
|
-
onComplete: (r) => resolve(r)
|
|
748
|
-
});
|
|
749
|
-
writeMessage({ type: "GEMINI_FETCH_URL", url, id: fetchId });
|
|
750
|
-
}),
|
|
846
|
+
getCookies: () => requestCallExtension(
|
|
847
|
+
requestContext,
|
|
848
|
+
"get_cookies",
|
|
849
|
+
{ type: "GET_GOOGLE_COOKIES" },
|
|
850
|
+
),
|
|
851
|
+
createTab: () => requestCallExtension(
|
|
852
|
+
requestContext,
|
|
853
|
+
"create_tab",
|
|
854
|
+
{ type: "GEMINI_NEW_TAB" },
|
|
855
|
+
),
|
|
856
|
+
closeTab: (tabIdToClose) => requestCallExtension(requestContext, "close_tab", { type: "GEMINI_CLOSE_TAB", tabId: tabIdToClose }, 45000, true),
|
|
857
|
+
jsEval: (tabId, code) => requestCallExtension(
|
|
858
|
+
requestContext,
|
|
859
|
+
"js_eval",
|
|
860
|
+
{ type: "EXECUTE_JAVASCRIPT", tabId, code },
|
|
861
|
+
),
|
|
862
|
+
uploadFile: (tabId, filePaths) => requestCallExtension(
|
|
863
|
+
requestContext,
|
|
864
|
+
"upload_file",
|
|
865
|
+
buildProviderUploadMessage("gemini", tabId, filePaths),
|
|
866
|
+
),
|
|
867
|
+
fetchUrl: (url) => requestCallExtension(
|
|
868
|
+
requestContext,
|
|
869
|
+
"fetch_url",
|
|
870
|
+
{ type: "GEMINI_FETCH_URL", url },
|
|
871
|
+
),
|
|
751
872
|
log: (msg) => log(`[gemini] ${msg}`)
|
|
752
873
|
});
|
|
753
874
|
|
|
@@ -776,16 +897,12 @@ function handleToolRequest(msg, socket) {
|
|
|
776
897
|
// 1. Get page context if requested
|
|
777
898
|
let pageContext = null;
|
|
778
899
|
if (withPage) {
|
|
779
|
-
const pageResult = await
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
onComplete: resolve
|
|
786
|
-
});
|
|
787
|
-
writeMessage({ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId, id: pageId });
|
|
788
|
-
});
|
|
900
|
+
const pageResult = await requestCallExtension(
|
|
901
|
+
requestContext,
|
|
902
|
+
"get_page_text",
|
|
903
|
+
{ type: "GET_PAGE_TEXT", tabId: extensionMsg.tabId },
|
|
904
|
+
45000,
|
|
905
|
+
);
|
|
789
906
|
if (pageResult && !pageResult.error) {
|
|
790
907
|
pageContext = {
|
|
791
908
|
url: pageResult.url,
|
|
@@ -803,59 +920,31 @@ function handleToolRequest(msg, socket) {
|
|
|
803
920
|
// 3. Call Grok client
|
|
804
921
|
const result = await grokClient.query({
|
|
805
922
|
prompt: fullPrompt,
|
|
923
|
+
signal: requestContext.signal,
|
|
806
924
|
model: model,
|
|
807
925
|
deepSearch: deepSearch || false,
|
|
808
926
|
timeout: timeout || 300000,
|
|
809
|
-
getCookies: () =>
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const tabCloseId = ++requestCounter;
|
|
831
|
-
pendingToolRequests.set(tabCloseId, {
|
|
832
|
-
socket: null,
|
|
833
|
-
originalId: null,
|
|
834
|
-
tool: "close_tab",
|
|
835
|
-
onComplete: (r) => resolve(r)
|
|
836
|
-
});
|
|
837
|
-
writeMessage({ type: "GROK_CLOSE_TAB", tabId: tabIdToClose, id: tabCloseId });
|
|
838
|
-
}),
|
|
839
|
-
cdpEvaluate: (tabId, expression) => new Promise((resolve) => {
|
|
840
|
-
const evalId = ++requestCounter;
|
|
841
|
-
pendingToolRequests.set(evalId, {
|
|
842
|
-
socket: null,
|
|
843
|
-
originalId: null,
|
|
844
|
-
tool: "cdp_evaluate",
|
|
845
|
-
onComplete: (r) => resolve(r)
|
|
846
|
-
});
|
|
847
|
-
writeMessage({ type: "GROK_EVALUATE", tabId, expression, id: evalId });
|
|
848
|
-
}),
|
|
849
|
-
cdpCommand: (tabId, method, params) => new Promise((resolve) => {
|
|
850
|
-
const cmdId = ++requestCounter;
|
|
851
|
-
pendingToolRequests.set(cmdId, {
|
|
852
|
-
socket: null,
|
|
853
|
-
originalId: null,
|
|
854
|
-
tool: "cdp_command",
|
|
855
|
-
onComplete: (r) => resolve(r)
|
|
856
|
-
});
|
|
857
|
-
writeMessage({ type: "GROK_CDP_COMMAND", tabId, method, params, id: cmdId });
|
|
858
|
-
}),
|
|
927
|
+
getCookies: () => requestCallExtension(
|
|
928
|
+
requestContext,
|
|
929
|
+
"get_cookies",
|
|
930
|
+
{ type: "GET_TWITTER_COOKIES" },
|
|
931
|
+
),
|
|
932
|
+
createTab: () => requestCallExtension(
|
|
933
|
+
requestContext,
|
|
934
|
+
"create_tab",
|
|
935
|
+
{ type: "GROK_NEW_TAB" },
|
|
936
|
+
),
|
|
937
|
+
closeTab: (tabIdToClose) => requestCallExtension(requestContext, "close_tab", { type: "GROK_CLOSE_TAB", tabId: tabIdToClose }, 45000, true),
|
|
938
|
+
cdpEvaluate: (tabId, expression) => requestCallExtension(
|
|
939
|
+
requestContext,
|
|
940
|
+
"cdp_evaluate",
|
|
941
|
+
{ type: "GROK_EVALUATE", tabId, expression },
|
|
942
|
+
),
|
|
943
|
+
cdpCommand: (tabId, method, params) => requestCallExtension(
|
|
944
|
+
requestContext,
|
|
945
|
+
"cdp_command",
|
|
946
|
+
{ type: "GROK_CDP_COMMAND", tabId, method, params },
|
|
947
|
+
),
|
|
859
948
|
log: (msg) => log(`[grok] ${msg}`)
|
|
860
949
|
});
|
|
861
950
|
|
|
@@ -894,46 +983,29 @@ function handleToolRequest(msg, socket) {
|
|
|
894
983
|
|
|
895
984
|
queueAiRequest(async () => {
|
|
896
985
|
const result = await grokClient.validate({
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
socket: null,
|
|
921
|
-
originalId: null,
|
|
922
|
-
tool: "close_tab",
|
|
923
|
-
onComplete: (r) => resolve(r)
|
|
924
|
-
});
|
|
925
|
-
writeMessage({ type: "GROK_CLOSE_TAB", tabId: tabIdToClose, id: tabCloseId });
|
|
926
|
-
}),
|
|
927
|
-
cdpEvaluate: (tabId, expression) => new Promise((resolve) => {
|
|
928
|
-
const evalId = ++requestCounter;
|
|
929
|
-
pendingToolRequests.set(evalId, {
|
|
930
|
-
socket: null,
|
|
931
|
-
originalId: null,
|
|
932
|
-
tool: "cdp_evaluate",
|
|
933
|
-
onComplete: (r) => resolve(r)
|
|
934
|
-
});
|
|
935
|
-
writeMessage({ type: "GROK_EVALUATE", tabId, expression, id: evalId });
|
|
936
|
-
}),
|
|
986
|
+
signal: requestContext.signal,
|
|
987
|
+
getCookies: () => requestCallExtension(
|
|
988
|
+
requestContext,
|
|
989
|
+
"get_cookies",
|
|
990
|
+
{ type: "GET_TWITTER_COOKIES" },
|
|
991
|
+
),
|
|
992
|
+
createTab: () => requestCallExtension(
|
|
993
|
+
requestContext,
|
|
994
|
+
"create_tab",
|
|
995
|
+
{ type: "GROK_NEW_TAB" },
|
|
996
|
+
),
|
|
997
|
+
closeTab: (tabIdToClose) => requestCallExtension(
|
|
998
|
+
requestContext,
|
|
999
|
+
"close_tab",
|
|
1000
|
+
{ type: "GROK_CLOSE_TAB", tabId: tabIdToClose },
|
|
1001
|
+
45000,
|
|
1002
|
+
true,
|
|
1003
|
+
),
|
|
1004
|
+
cdpEvaluate: (tabId, expression) => requestCallExtension(
|
|
1005
|
+
requestContext,
|
|
1006
|
+
"cdp_evaluate",
|
|
1007
|
+
{ type: "GROK_EVALUATE", tabId, expression },
|
|
1008
|
+
),
|
|
937
1009
|
log: (msg) => log(`[grok:validate] ${msg}`)
|
|
938
1010
|
});
|
|
939
1011
|
|
|
@@ -941,19 +1013,25 @@ function handleToolRequest(msg, socket) {
|
|
|
941
1013
|
}).then((result) => {
|
|
942
1014
|
// If --save-models flag was passed and we found models, save them
|
|
943
1015
|
if (saveModels && result.models && result.models.length > 0) {
|
|
944
|
-
// Convert scraped model names to
|
|
1016
|
+
// Convert scraped model names to selectable IDs.
|
|
945
1017
|
const modelMap = {};
|
|
1018
|
+
const defaultModels = Object.values(grokClient.DEFAULT_GROK_MODELS || {});
|
|
946
1019
|
result.models.forEach(name => {
|
|
947
1020
|
const nameLower = name.toLowerCase();
|
|
1021
|
+
const normalizedName = grokClient.normalizeGrokModelLabel(name);
|
|
1022
|
+
const knownModel = defaultModels.find(model => {
|
|
1023
|
+
const normalizedDefaultName = grokClient.normalizeGrokModelLabel(model.name);
|
|
1024
|
+
return normalizedName.includes(normalizedDefaultName) || normalizedDefaultName.includes(normalizedName);
|
|
1025
|
+
});
|
|
948
1026
|
// Match known model keywords to generate consistent short IDs
|
|
949
1027
|
let shortId;
|
|
950
|
-
if (
|
|
1028
|
+
if (knownModel) shortId = knownModel.id;
|
|
951
1029
|
else if (nameLower.includes('expert')) shortId = 'expert';
|
|
952
1030
|
else if (nameLower.includes('fast')) shortId = 'fast';
|
|
953
1031
|
else if (nameLower.includes('auto')) shortId = 'auto';
|
|
954
1032
|
else shortId = nameLower.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
955
1033
|
|
|
956
|
-
modelMap[shortId] = { id: shortId, name: name, desc: "" };
|
|
1034
|
+
modelMap[shortId] = { id: shortId, name: name, desc: knownModel?.desc || "" };
|
|
957
1035
|
});
|
|
958
1036
|
const saveResult = grokClient.saveModels(modelMap);
|
|
959
1037
|
result.savedModels = saveResult;
|
|
@@ -972,30 +1050,12 @@ function handleToolRequest(msg, socket) {
|
|
|
972
1050
|
queueAiRequest(async () => {
|
|
973
1051
|
const EXT_CALL_TIMEOUT_MS = 30000;
|
|
974
1052
|
|
|
975
|
-
const callExtension = (toolName, msg, timeoutMs = EXT_CALL_TIMEOUT_MS) =>
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
if (msg && msg.type === "AISTUDIO_NEW_TAB") {
|
|
1053
|
+
const callExtension = (toolName, msg, timeoutMs = EXT_CALL_TIMEOUT_MS) => {
|
|
1054
|
+
if (msg?.type === "AISTUDIO_NEW_TAB") {
|
|
979
1055
|
log(`[aistudio] Opening tab: ${(msg.url || "https://aistudio.google.com/prompts/new_chat")}`);
|
|
980
1056
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
pendingToolRequests.delete(id);
|
|
984
|
-
reject(new Error(`Timeout waiting for extension: ${toolName}`));
|
|
985
|
-
}, timeoutMs);
|
|
986
|
-
|
|
987
|
-
pendingToolRequests.set(id, {
|
|
988
|
-
socket: null,
|
|
989
|
-
originalId: null,
|
|
990
|
-
tool: toolName,
|
|
991
|
-
onComplete: (r) => {
|
|
992
|
-
clearTimeout(timeoutId);
|
|
993
|
-
resolve(r);
|
|
994
|
-
}
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
writeMessage({ ...msg, id });
|
|
998
|
-
});
|
|
1057
|
+
return requestCallExtension(requestContext, toolName, msg, timeoutMs);
|
|
1058
|
+
};
|
|
999
1059
|
|
|
1000
1060
|
// 1. Get page context if requested
|
|
1001
1061
|
let pageContext = null;
|
|
@@ -1029,6 +1089,7 @@ function handleToolRequest(msg, socket) {
|
|
|
1029
1089
|
// 3. Call AI Studio client
|
|
1030
1090
|
const result = await aistudioClient.query({
|
|
1031
1091
|
prompt: fullPrompt,
|
|
1092
|
+
signal: requestContext.signal,
|
|
1032
1093
|
model: model || undefined,
|
|
1033
1094
|
timeout: timeout || 300000,
|
|
1034
1095
|
getCookies: () => callExtension("get_cookies", { type: "GET_GOOGLE_COOKIES" }, 45000),
|
|
@@ -1087,33 +1148,16 @@ function handleToolRequest(msg, socket) {
|
|
|
1087
1148
|
queueAiRequest(async () => {
|
|
1088
1149
|
const EXT_CALL_TIMEOUT_MS = 30000;
|
|
1089
1150
|
|
|
1090
|
-
const callExtension = (toolName, msg, timeoutMs = EXT_CALL_TIMEOUT_MS) =>
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
if (msg && msg.type === "AISTUDIO_NEW_TAB") {
|
|
1151
|
+
const callExtension = (toolName, msg, timeoutMs = EXT_CALL_TIMEOUT_MS) => {
|
|
1152
|
+
if (msg?.type === "AISTUDIO_NEW_TAB") {
|
|
1094
1153
|
log(`[aistudio] Opening tab: ${(msg.url || "https://aistudio.google.com/apps")}`);
|
|
1095
1154
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
pendingToolRequests.delete(id);
|
|
1099
|
-
reject(new Error(`Timeout waiting for extension: ${toolName}`));
|
|
1100
|
-
}, timeoutMs);
|
|
1101
|
-
|
|
1102
|
-
pendingToolRequests.set(id, {
|
|
1103
|
-
socket: null,
|
|
1104
|
-
originalId: null,
|
|
1105
|
-
tool: toolName,
|
|
1106
|
-
onComplete: (r) => {
|
|
1107
|
-
clearTimeout(timeoutId);
|
|
1108
|
-
resolve(r);
|
|
1109
|
-
}
|
|
1110
|
-
});
|
|
1111
|
-
|
|
1112
|
-
writeMessage({ ...msg, id });
|
|
1113
|
-
});
|
|
1155
|
+
return requestCallExtension(requestContext, toolName, msg, timeoutMs);
|
|
1156
|
+
};
|
|
1114
1157
|
|
|
1115
1158
|
const result = await aistudioBuild.build({
|
|
1116
1159
|
prompt: query,
|
|
1160
|
+
signal: requestContext.signal,
|
|
1117
1161
|
model: model || undefined,
|
|
1118
1162
|
output,
|
|
1119
1163
|
keepOpen,
|
|
@@ -1164,6 +1208,7 @@ function handleToolRequest(msg, socket) {
|
|
|
1164
1208
|
let lastError = null;
|
|
1165
1209
|
|
|
1166
1210
|
const sendNextKey = () => {
|
|
1211
|
+
if (requestContext.signal.aborted) return;
|
|
1167
1212
|
if (completed >= repeat) {
|
|
1168
1213
|
if (lastError) {
|
|
1169
1214
|
sendToolResponse(socket, originalId, null, `Key repeat failed: ${lastError}`);
|
|
@@ -1172,18 +1217,15 @@ function handleToolRequest(msg, socket) {
|
|
|
1172
1217
|
}
|
|
1173
1218
|
return;
|
|
1174
1219
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
socket: null,
|
|
1178
|
-
originalId: null,
|
|
1220
|
+
requestCallExtension(
|
|
1221
|
+
requestContext,
|
|
1179
1222
|
tool,
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
});
|
|
1186
|
-
writeMessage({ type: "EXECUTE_KEY", key, tabId: tid, id });
|
|
1223
|
+
{ type: "EXECUTE_KEY", key, tabId: tid },
|
|
1224
|
+
).then((result) => {
|
|
1225
|
+
if (result.error) lastError = result.error;
|
|
1226
|
+
completed++;
|
|
1227
|
+
return require("./abort.cjs").abortableDelay(50, requestContext.signal);
|
|
1228
|
+
}).then(sendNextKey).catch((error) => sendToolResponse(socket, originalId, null, error.message));
|
|
1187
1229
|
};
|
|
1188
1230
|
sendNextKey();
|
|
1189
1231
|
return;
|
|
@@ -1191,23 +1233,25 @@ function handleToolRequest(msg, socket) {
|
|
|
1191
1233
|
|
|
1192
1234
|
if (extensionMsg.type === "NAMED_TAB_SWITCH" || extensionMsg.type === "NAMED_TAB_CLOSE") {
|
|
1193
1235
|
const { name, type: opType } = extensionMsg;
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
sendToolResponse(socket, originalId, null, result.error || `No tab found with name "${name}"`);
|
|
1202
|
-
return;
|
|
1203
|
-
}
|
|
1204
|
-
const actionId = ++requestCounter;
|
|
1205
|
-
const actionType = opType === "NAMED_TAB_SWITCH" ? "SWITCH_TAB" : "CLOSE_TAB";
|
|
1206
|
-
pendingToolRequests.set(actionId, { socket, originalId, tool, tabId: result.tabId });
|
|
1207
|
-
writeMessage({ type: actionType, tabId: result.tabId, id: actionId });
|
|
1236
|
+
requestCallExtension(
|
|
1237
|
+
requestContext,
|
|
1238
|
+
"tabs_get_by_name",
|
|
1239
|
+
{ type: "TABS_GET_BY_NAME", name },
|
|
1240
|
+
).then((result) => {
|
|
1241
|
+
if (result.error || !result.tabId) {
|
|
1242
|
+
throw new Error(result.error || `No tab found with name "${name}"`);
|
|
1208
1243
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1244
|
+
const actionType = opType === "NAMED_TAB_SWITCH" ? "SWITCH_TAB" : "CLOSE_TAB";
|
|
1245
|
+
const actionTool = opType === "NAMED_TAB_SWITCH" ? "switch_tab" : "close_tab";
|
|
1246
|
+
return requestCallExtension(
|
|
1247
|
+
requestContext,
|
|
1248
|
+
actionTool,
|
|
1249
|
+
{ type: actionType, tabId: result.tabId },
|
|
1250
|
+
30000,
|
|
1251
|
+
actionTool === "close_tab",
|
|
1252
|
+
);
|
|
1253
|
+
}).then((result) => sendToolResponse(socket, originalId, result, result?.error || null))
|
|
1254
|
+
.catch((error) => sendToolResponse(socket, originalId, null, error.message));
|
|
1211
1255
|
return;
|
|
1212
1256
|
}
|
|
1213
1257
|
|
|
@@ -1217,7 +1261,11 @@ function handleToolRequest(msg, socket) {
|
|
|
1217
1261
|
originalId,
|
|
1218
1262
|
tool,
|
|
1219
1263
|
savePath: extensionMsg.savePath || args?.savePath,
|
|
1220
|
-
autoScreenshot: args?.autoScreenshot,
|
|
1264
|
+
autoScreenshot: args?.autoScreenshot === true,
|
|
1265
|
+
autoScreenshotOutput: args?.autoScreenshotOutput,
|
|
1266
|
+
networkExport: extensionMsg.type === "EXPORT_NETWORK_REQUESTS",
|
|
1267
|
+
networkExportPath: args?.output,
|
|
1268
|
+
networkExportFormat: extensionMsg.har ? "har" : extensionMsg.jsonl ? "jsonl" : "json",
|
|
1221
1269
|
fullRes: extensionMsg.fullRes || args?.fullRes,
|
|
1222
1270
|
maxSize: extensionMsg.maxSize || args?.maxSize,
|
|
1223
1271
|
tabId: extensionMsg.tabId || tabId
|
|
@@ -1230,12 +1278,14 @@ function handleToolRequest(msg, socket) {
|
|
|
1230
1278
|
writeMessage(finalMsg);
|
|
1231
1279
|
}
|
|
1232
1280
|
|
|
1233
|
-
function executeBatch(actions, tabId, socket, originalId) {
|
|
1281
|
+
function executeBatch(actions, tabId, socket, originalId, requestContext = requestStorage.getStore()) {
|
|
1282
|
+
const writeMessage = (message) => sendOwnedExtensionMessage(requestContext, message);
|
|
1234
1283
|
const results = [];
|
|
1235
1284
|
const DELAY_MS = 100;
|
|
1236
1285
|
let currentIndex = 0;
|
|
1237
1286
|
|
|
1238
1287
|
function executeNextAction() {
|
|
1288
|
+
if (requestContext.signal.aborted) return;
|
|
1239
1289
|
if (currentIndex >= actions.length) {
|
|
1240
1290
|
sendToolResponse(socket, originalId, {
|
|
1241
1291
|
success: true,
|
|
@@ -1266,16 +1316,14 @@ function executeBatch(actions, tabId, socket, originalId) {
|
|
|
1266
1316
|
if (extensionMsg.type === "LOCAL_WAIT") {
|
|
1267
1317
|
results.push({ index: currentIndex, type: action.type, success: true });
|
|
1268
1318
|
currentIndex++;
|
|
1269
|
-
|
|
1319
|
+
require("./abort.cjs").abortableDelay(extensionMsg.seconds * 1000, requestContext.signal)
|
|
1320
|
+
.then(executeNextAction)
|
|
1321
|
+
.catch((error) => sendToolResponse(socket, originalId, null, error.message));
|
|
1270
1322
|
return;
|
|
1271
1323
|
}
|
|
1272
1324
|
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
socket: null,
|
|
1276
|
-
originalId: null,
|
|
1277
|
-
tool: toolName,
|
|
1278
|
-
onComplete: (result) => {
|
|
1325
|
+
requestCallExtension(requestContext, toolName, extensionMsg, 30000)
|
|
1326
|
+
.then((result) => {
|
|
1279
1327
|
if (result.error) {
|
|
1280
1328
|
results.push({ index: currentIndex, type: action.type, success: false, error: result.error });
|
|
1281
1329
|
sendToolResponse(socket, originalId, {
|
|
@@ -1287,15 +1335,12 @@ function executeBatch(actions, tabId, socket, originalId) {
|
|
|
1287
1335
|
}, null);
|
|
1288
1336
|
return;
|
|
1289
1337
|
}
|
|
1290
|
-
|
|
1291
1338
|
results.push({ index: currentIndex, type: action.type, success: true });
|
|
1292
1339
|
currentIndex++;
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
writeMessage({ ...extensionMsg, id });
|
|
1340
|
+
return require("./abort.cjs").abortableDelay(DELAY_MS, requestContext.signal)
|
|
1341
|
+
.then(executeNextAction);
|
|
1342
|
+
})
|
|
1343
|
+
.catch((error) => sendToolResponse(socket, originalId, null, error.message));
|
|
1299
1344
|
}
|
|
1300
1345
|
|
|
1301
1346
|
executeNextAction();
|
|
@@ -1356,7 +1401,7 @@ function processInput() {
|
|
|
1356
1401
|
|
|
1357
1402
|
try {
|
|
1358
1403
|
const msg = JSON.parse(jsonStr);
|
|
1359
|
-
log(`Received from extension: ${
|
|
1404
|
+
log(`Received from extension: ${msg.type || "unknown"}${msg.id !== undefined ? ` id=${msg.id}` : ""}`);
|
|
1360
1405
|
|
|
1361
1406
|
if (msg.type === "GET_AUTH") {
|
|
1362
1407
|
log("Handling GET_AUTH from extension");
|
|
@@ -1390,24 +1435,24 @@ function processInput() {
|
|
|
1390
1435
|
if (msg.type === "STREAM_EVENT") {
|
|
1391
1436
|
const stream = activeStreams.get(msg.streamId);
|
|
1392
1437
|
if (stream) {
|
|
1393
|
-
|
|
1394
|
-
stream
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
writeMessage({ type: "STREAM_STOP", streamId: msg.streamId });
|
|
1399
|
-
}
|
|
1438
|
+
sendSocket(stream.socket, msg.event, { stream: true }).catch((error) => {
|
|
1439
|
+
log(`Error forwarding stream event: ${error.message}`);
|
|
1440
|
+
stopActiveStream(msg.streamId);
|
|
1441
|
+
stream.socket.destroy(error);
|
|
1442
|
+
});
|
|
1400
1443
|
}
|
|
1401
1444
|
return;
|
|
1402
1445
|
}
|
|
1403
|
-
|
|
1446
|
+
|
|
1404
1447
|
if (msg.type === "STREAM_ERROR") {
|
|
1405
1448
|
const stream = activeStreams.get(msg.streamId);
|
|
1406
1449
|
if (stream) {
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1450
|
+
sendSocket(stream.socket, { error: msg.error }, { stream: true })
|
|
1451
|
+
.catch((error) => {
|
|
1452
|
+
log(`Error forwarding stream error: ${error.message}`);
|
|
1453
|
+
stream.socket.destroy(error);
|
|
1454
|
+
})
|
|
1455
|
+
.finally(() => stopActiveStream(msg.streamId));
|
|
1411
1456
|
}
|
|
1412
1457
|
return;
|
|
1413
1458
|
}
|
|
@@ -1415,19 +1460,41 @@ function processInput() {
|
|
|
1415
1460
|
|
|
1416
1461
|
if (msg.id && pendingToolRequests.has(msg.id)) {
|
|
1417
1462
|
const pending = pendingToolRequests.get(msg.id);
|
|
1463
|
+
if (pending.request?.signal.aborted || pending.request?.tombstoned) {
|
|
1464
|
+
const request = pending.request;
|
|
1465
|
+
const topLevelResponse = !pending.resolve && !pending.onComplete;
|
|
1466
|
+
pendingToolRequests.resolve(msg.id, msg);
|
|
1467
|
+
if (topLevelResponse && request?.context) {
|
|
1468
|
+
completeOwnedRequest(request.context, request.id, "cleanup-settled");
|
|
1469
|
+
}
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
if (pending.resolve || pending.onComplete) {
|
|
1473
|
+
pendingToolRequests.resolve(msg.id, msg);
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1418
1476
|
pendingToolRequests.delete(msg.id);
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
pending.onComplete(msg);
|
|
1422
|
-
} else {
|
|
1477
|
+
{
|
|
1478
|
+
|
|
1423
1479
|
const { socket, originalId, savePath, autoScreenshot, tabId: storedTabId } = pending;
|
|
1424
1480
|
const tabId = storedTabId || msg._resolvedTabId;
|
|
1481
|
+
const failAutoScreenshot = (message) => pending.autoScreenshotOutput
|
|
1482
|
+
? sendToolResponse(socket, originalId, null, `Auto-screenshot failed: ${message}`)
|
|
1483
|
+
: sendToolResponse(socket, originalId, { ...msg, autoScreenshotError: message }, null);
|
|
1425
1484
|
|
|
1426
|
-
if (
|
|
1485
|
+
if (pending.networkExport && Array.isArray(msg.entries)) {
|
|
1486
|
+
try {
|
|
1487
|
+
const exportResult = writeNetworkExport(pending.networkExportPath, msg.entries, pending.networkExportFormat);
|
|
1488
|
+
sendToolResponse(socket, originalId, exportResult, null);
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
sendToolResponse(socket, originalId, null, `Failed to export network requests: ${error.message}`);
|
|
1491
|
+
}
|
|
1492
|
+
} else if (savePath && msg.base64) {
|
|
1427
1493
|
try {
|
|
1428
1494
|
const dir = path.dirname(savePath);
|
|
1429
1495
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1430
|
-
fs.writeFileSync(savePath, Buffer.from(msg.base64, "base64"));
|
|
1496
|
+
fs.writeFileSync(savePath, Buffer.from(msg.base64, "base64"), { mode: 0o600 });
|
|
1497
|
+
try { fs.chmodSync(savePath, 0o600); } catch {}
|
|
1431
1498
|
const origWidth = msg.width || 0;
|
|
1432
1499
|
const origHeight = msg.height || 0;
|
|
1433
1500
|
const maxSize = pending.maxSize || 1200;
|
|
@@ -1450,8 +1517,7 @@ function processInput() {
|
|
|
1450
1517
|
}
|
|
1451
1518
|
} else if (autoScreenshot && tabId && !msg.error && !msg.base64) {
|
|
1452
1519
|
|
|
1453
|
-
const
|
|
1454
|
-
const screenshotPath = path.join(SURF_TMP, `pi-auto-${Date.now()}.png`);
|
|
1520
|
+
const screenshotPath = pending.autoScreenshotOutput || path.join(SURF_TMP, `pi-auto-${Date.now()}.png`);
|
|
1455
1521
|
|
|
1456
1522
|
const autoFiles = fs.readdirSync(SURF_TMP)
|
|
1457
1523
|
.filter(f => f.startsWith("pi-auto-") && f.endsWith(".png"))
|
|
@@ -1462,14 +1528,17 @@ function processInput() {
|
|
|
1462
1528
|
try { fs.unlinkSync(path.join(SURF_TMP, f.name)); } catch (e) {}
|
|
1463
1529
|
});
|
|
1464
1530
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1531
|
+
require("./abort.cjs").abortableDelay(500, pending.request?.signal)
|
|
1532
|
+
.then(() => requestCallExtension(
|
|
1533
|
+
pending.request,
|
|
1534
|
+
"screenshot",
|
|
1535
|
+
{ type: "EXECUTE_SCREENSHOT", tabId },
|
|
1536
|
+
))
|
|
1537
|
+
.then((screenshotMsg) => {
|
|
1470
1538
|
if (screenshotMsg.base64) {
|
|
1471
1539
|
try {
|
|
1472
|
-
fs.writeFileSync(screenshotPath, Buffer.from(screenshotMsg.base64, "base64"));
|
|
1540
|
+
fs.writeFileSync(screenshotPath, Buffer.from(screenshotMsg.base64, "base64"), { mode: 0o600 });
|
|
1541
|
+
try { fs.chmodSync(screenshotPath, 0o600); } catch {}
|
|
1473
1542
|
const origW = screenshotMsg.width || 0;
|
|
1474
1543
|
const origH = screenshotMsg.height || 0;
|
|
1475
1544
|
let finalW = origW, finalH = origH;
|
|
@@ -1486,16 +1555,17 @@ function processInput() {
|
|
|
1486
1555
|
autoScreenshot: { path: screenshotPath, width: finalW, height: finalH, originalWidth: origW, originalHeight: origH }
|
|
1487
1556
|
}, null);
|
|
1488
1557
|
} catch (e) {
|
|
1489
|
-
|
|
1558
|
+
failAutoScreenshot(e.message);
|
|
1490
1559
|
}
|
|
1491
1560
|
} else {
|
|
1492
1561
|
const errMsg = screenshotMsg.error || "Failed to capture";
|
|
1493
|
-
|
|
1562
|
+
failAutoScreenshot(errMsg);
|
|
1494
1563
|
}
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
setTimeout(() => writeMessage({ type: "EXECUTE_SCREENSHOT", tabId, id: screenshotId }), 500);
|
|
1564
|
+
})
|
|
1565
|
+
.catch((error) => failAutoScreenshot(error.message));
|
|
1498
1566
|
return;
|
|
1567
|
+
} else if (autoScreenshot && pending.autoScreenshotOutput && !msg.error) {
|
|
1568
|
+
failAutoScreenshot(tabId ? "screenshot response was invalid" : "no tab available");
|
|
1499
1569
|
} else if (msg.results && msg.savePath) {
|
|
1500
1570
|
try {
|
|
1501
1571
|
const dir = msg.savePath;
|
|
@@ -1529,11 +1599,7 @@ function processInput() {
|
|
|
1529
1599
|
}
|
|
1530
1600
|
} else if (msg.id && pendingRequests.has(msg.id)) {
|
|
1531
1601
|
const { socket } = pendingRequests.get(msg.id);
|
|
1532
|
-
|
|
1533
|
-
socket.write(JSON.stringify(msg) + "\n");
|
|
1534
|
-
} catch (e) {
|
|
1535
|
-
log(`Error writing to CLI socket: ${e.message}`);
|
|
1536
|
-
}
|
|
1602
|
+
sendSocket(socket, msg).catch((error) => log(`Error writing to CLI socket: ${error.message}`));
|
|
1537
1603
|
pendingRequests.delete(msg.id);
|
|
1538
1604
|
}
|
|
1539
1605
|
} catch (e) {
|
|
@@ -1556,17 +1622,12 @@ const connectedSockets = new Set();
|
|
|
1556
1622
|
process.stdin.on("end", () => {
|
|
1557
1623
|
log("stdin ended (extension disconnected), notifying clients");
|
|
1558
1624
|
for (const socket of Array.from(connectedSockets)) {
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
}) + "\n");
|
|
1564
|
-
socket.end();
|
|
1565
|
-
} catch (e) {
|
|
1566
|
-
// Socket may already be closed
|
|
1567
|
-
}
|
|
1625
|
+
sendSocket(socket, {
|
|
1626
|
+
type: "extension_disconnected",
|
|
1627
|
+
message: "Surf extension was reloaded. Restart your command."
|
|
1628
|
+
}).finally(() => socket.end()).catch(() => socket.end());
|
|
1568
1629
|
}
|
|
1569
|
-
|
|
1630
|
+
shutdown(0);
|
|
1570
1631
|
});
|
|
1571
1632
|
|
|
1572
1633
|
process.stdin.on("error", (err) => {
|
|
@@ -1577,142 +1638,276 @@ process.stdout.on("error", (err) => {
|
|
|
1577
1638
|
log(`stdout error: ${err.message}`);
|
|
1578
1639
|
});
|
|
1579
1640
|
|
|
1580
|
-
const
|
|
1641
|
+
const handleClient = (socket) => {
|
|
1642
|
+
const isRemote = Boolean(socket.remoteAddress && socket.remotePort);
|
|
1643
|
+
let transferState;
|
|
1644
|
+
let transferReady;
|
|
1645
|
+
let transferCleanupPromise;
|
|
1646
|
+
const cleanupTransfers = () => {
|
|
1647
|
+
if (!transferCleanupPromise) {
|
|
1648
|
+
transferCleanupPromise = transferReady
|
|
1649
|
+
? transferReady.then((state) => state?.cleanup())
|
|
1650
|
+
: Promise.resolve();
|
|
1651
|
+
}
|
|
1652
|
+
return transferCleanupPromise;
|
|
1653
|
+
};
|
|
1654
|
+
socket.transferCleanup = cleanupTransfers;
|
|
1655
|
+
const ensureTransferState = () => {
|
|
1656
|
+
if (context?.closed || socket.destroyed) throw transferError("transfer connection is closed", "SURF_TRANSFER_CLOSED");
|
|
1657
|
+
if (!transferReady) {
|
|
1658
|
+
transferCleanupPromise = undefined;
|
|
1659
|
+
transferReady = createStagingDirectory(SURF_TMP)
|
|
1660
|
+
.then(async (directory) => {
|
|
1661
|
+
if (context?.closed || socket.destroyed) {
|
|
1662
|
+
await fs.promises.rm(directory, { recursive: true, force: true }).catch(() => {});
|
|
1663
|
+
throw transferError("transfer connection is closed", "SURF_TRANSFER_CLOSED");
|
|
1664
|
+
}
|
|
1665
|
+
try {
|
|
1666
|
+
return createTransferState({ directory, writer: { send: (frame) => sendSocket(socket, frame) }, onActivity: () => sessionManager.touch(socketContexts.get(socket)) });
|
|
1667
|
+
} catch (error) {
|
|
1668
|
+
return fs.promises.rm(directory, { recursive: true, force: true }).catch(() => {}).then(() => { throw error; });
|
|
1669
|
+
}
|
|
1670
|
+
})
|
|
1671
|
+
.catch((error) => { transferReady = undefined; throw error; });
|
|
1672
|
+
}
|
|
1673
|
+
return transferReady;
|
|
1674
|
+
};
|
|
1675
|
+
let context;
|
|
1676
|
+
try {
|
|
1677
|
+
context = sessionManager.admit(socket, isRemote);
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
sendSocket(socket, { error: error.message }).finally(() => socket.destroy()).catch(() => socket.destroy());
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
const writer = createSocketWriter(socket, {
|
|
1683
|
+
maxPendingBytes: 4 * 1024 * 1024,
|
|
1684
|
+
onOverflow: ({ stream, error }) => {
|
|
1685
|
+
auditSession({ event: stream ? "stream" : "writer", context, outcome: "overflow", request: context.activeRequest });
|
|
1686
|
+
sessionManager.stopStream(context);
|
|
1687
|
+
socket.destroy(error);
|
|
1688
|
+
},
|
|
1689
|
+
});
|
|
1690
|
+
socketContexts.set(socket, context);
|
|
1691
|
+
socketWriters.set(socket, writer);
|
|
1581
1692
|
log("CLI client connected");
|
|
1582
1693
|
connectedSockets.add(socket);
|
|
1583
1694
|
socket.on("close", () => connectedSockets.delete(socket));
|
|
1584
1695
|
|
|
1585
|
-
|
|
1696
|
+
const stateDir = getStateDir();
|
|
1697
|
+
let principal = null;
|
|
1698
|
+
const authSession = isRemote ? createServerAuthSession({
|
|
1699
|
+
socket,
|
|
1700
|
+
stateDir,
|
|
1701
|
+
send: (value) => sendSocket(socket, value),
|
|
1702
|
+
async onAuthenticated(authenticatedPrincipal) {
|
|
1703
|
+
sessionManager.authenticate(context, authenticatedPrincipal);
|
|
1704
|
+
principal = authenticatedPrincipal;
|
|
1705
|
+
log(`Remote client authenticated: ${authenticatedPrincipal.label} (${authenticatedPrincipal.clientId})`);
|
|
1706
|
+
},
|
|
1707
|
+
onError(error) {
|
|
1708
|
+
log(`Remote authentication rejected: ${error.message}`);
|
|
1709
|
+
sendSocket(socket, { type: "auth_error", message: error.message }).finally(() => socket.destroy()).catch(() => socket.destroy());
|
|
1710
|
+
},
|
|
1711
|
+
}) : null;
|
|
1712
|
+
let messageChain = Promise.resolve();
|
|
1713
|
+
const processMessage = async (msg) => {
|
|
1714
|
+
if (context.closed) return;
|
|
1715
|
+
if (isRemote && !authSession.authenticated) {
|
|
1716
|
+
await authSession.handle(msg);
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
if (isRemote) {
|
|
1720
|
+
let authorized = false;
|
|
1721
|
+
try {
|
|
1722
|
+
authorized = Boolean(principal && isClientAuthorized(stateDir, principal.clientId));
|
|
1723
|
+
} catch (error) {
|
|
1724
|
+
log(`Remote authorization registry check failed: ${error.message}`);
|
|
1725
|
+
}
|
|
1726
|
+
if (!authorized) {
|
|
1727
|
+
await sendSocket(socket, { error: "remote client authorization is unavailable or revoked" }).catch(() => {});
|
|
1728
|
+
socket.destroy();
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1586
1732
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1733
|
+
if (isRemote && msg.type && msg.type.startsWith("transfer_")) {
|
|
1734
|
+
transferState ||= await ensureTransferState();
|
|
1735
|
+
await transferState.handle(msg);
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1591
1738
|
|
|
1592
|
-
|
|
1593
|
-
|
|
1739
|
+
if (msg.type === "tool_request") {
|
|
1740
|
+
const tool = msg.params?.tool || "unknown";
|
|
1741
|
+
let request;
|
|
1594
1742
|
try {
|
|
1595
|
-
const
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
} else {
|
|
1608
|
-
socket.write(JSON.stringify({
|
|
1609
|
-
id: msg.id || 0,
|
|
1610
|
-
auth: null,
|
|
1611
|
-
hint: "No OAuth credentials found. Run 'pi --login anthropic' in terminal to authenticate with Claude Max."
|
|
1612
|
-
}) + "\n");
|
|
1613
|
-
}
|
|
1614
|
-
} catch (e) {
|
|
1615
|
-
log(`Error reading auth file: ${e.message}`);
|
|
1616
|
-
socket.write(JSON.stringify({
|
|
1617
|
-
id: msg.id || 0,
|
|
1618
|
-
auth: null,
|
|
1619
|
-
hint: "Failed to read auth credentials. Run 'pi --login anthropic' in terminal to authenticate."
|
|
1620
|
-
}) + "\n");
|
|
1621
|
-
}
|
|
1622
|
-
continue;
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
if (msg.type === "tool_request") {
|
|
1626
|
-
log("Handling tool_request: " + msg.method + " " + (msg.params?.tool || ""));
|
|
1627
|
-
try {
|
|
1628
|
-
handleToolRequest(msg, socket);
|
|
1629
|
-
} catch (e) {
|
|
1630
|
-
socket.write(JSON.stringify({ error: e.message || "Request failed" }) + "\n");
|
|
1631
|
-
}
|
|
1632
|
-
continue;
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
if (msg.type === "stream_request") {
|
|
1636
|
-
log("Handling stream_request: " + msg.streamType);
|
|
1637
|
-
handleStreamRequest(msg, socket);
|
|
1638
|
-
continue;
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
if (msg.type === "stream_stop") {
|
|
1642
|
-
log("Handling stream_stop");
|
|
1643
|
-
for (const [streamId, stream] of activeStreams.entries()) {
|
|
1644
|
-
if (stream.socket === socket) {
|
|
1645
|
-
writeMessage({ type: "STREAM_STOP", streamId });
|
|
1646
|
-
activeStreams.delete(streamId);
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
continue;
|
|
1743
|
+
const deadlineMs = TEST_REQUEST_DEADLINE_MS || resolveRequestDeadlineMs(tool, msg.params?.args);
|
|
1744
|
+
request = await sessionManager.beginRequest(context, { id: msg.id, tool, deadlineMs });
|
|
1745
|
+
request.context = context;
|
|
1746
|
+
} catch (error) {
|
|
1747
|
+
if (transferState) await discardRequestTransfers(msg, transferState);
|
|
1748
|
+
await sendSocket(socket, { type: "tool_response", id: msg.id || null, error: { content: [{ type: "text", text: error.message }] } }).catch(() => {});
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
log(`Handling tool_request: ${msg.method} ${tool}${principal ? ` for ${principal.label}` : ""}`);
|
|
1752
|
+
try {
|
|
1753
|
+
if (isRemote) {
|
|
1754
|
+
await applyRequestTransfers(msg, request, transferState, ensureTransferState);
|
|
1650
1755
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
log(`Forwarding to extension: id=${id} type=${msg.type}`);
|
|
1654
|
-
pendingRequests.set(id, { socket });
|
|
1655
|
-
writeMessage({ ...msg, id });
|
|
1756
|
+
throwIfAborted(request.signal, "Request cancelled");
|
|
1757
|
+
requestStorage.run(request, () => handleToolRequest(msg, socket, request));
|
|
1656
1758
|
} catch (e) {
|
|
1657
|
-
|
|
1658
|
-
socket.
|
|
1759
|
+
await discardRequestTransfers(msg, transferState);
|
|
1760
|
+
sendToolResponse(socket, msg.id || null, null, e.message || "Request failed");
|
|
1659
1761
|
}
|
|
1762
|
+
return;
|
|
1660
1763
|
}
|
|
1764
|
+
|
|
1765
|
+
if (msg.type === "stream_request") {
|
|
1766
|
+
if (msg.streamType !== "STREAM_CONSOLE" && msg.streamType !== "STREAM_NETWORK") {
|
|
1767
|
+
log(`Rejecting unsupported stream type: ${msg.streamType}`);
|
|
1768
|
+
await sendSocket(socket, { error: `Unsupported stream type: ${msg.streamType}` }).catch(() => {});
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
if (!sessionManager.canStartStream(context)) {
|
|
1772
|
+
await sendSocket(socket, { error: "stream limit reached or connection is not stream-only" }).catch(() => {});
|
|
1773
|
+
socket.destroy();
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
log(`Handling stream_request: ${msg.streamType}`);
|
|
1777
|
+
handleStreamRequest(msg, socket);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
if (msg.type === "stream_stop") {
|
|
1782
|
+
log("Handling stream_stop");
|
|
1783
|
+
for (const [streamId, stream] of activeStreams.entries()) {
|
|
1784
|
+
if (stream.socket === socket) stopActiveStream(streamId);
|
|
1785
|
+
}
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
log(`Rejecting unsupported socket request type: ${msg.type}`);
|
|
1790
|
+
await sendSocket(socket, { error: `Unsupported request type: ${msg.type}` }).catch(() => {});
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
const parser = createFrameParser({
|
|
1794
|
+
onFrame(msg) {
|
|
1795
|
+
messageChain = messageChain.then(() => processMessage(msg)).catch((error) => {
|
|
1796
|
+
log(`Error handling CLI request: ${error.message}`);
|
|
1797
|
+
if (isRemote && msg.type && msg.type.startsWith("transfer_")) {
|
|
1798
|
+
sendSocket(socket, { type: "transfer_error", version: 1, transferId: msg.transferId, error: error.message || "Transfer failed" })
|
|
1799
|
+
.finally(() => socket.destroy()).catch(() => socket.destroy());
|
|
1800
|
+
} else {
|
|
1801
|
+
sendSocket(socket, { error: error.message || "Request failed" }).catch(() => {});
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
},
|
|
1805
|
+
onError(error) {
|
|
1806
|
+
log(`CLI frame rejected: ${error.message}`);
|
|
1807
|
+
if (isRemote && !authSession.authenticated) {
|
|
1808
|
+
sendSocket(socket, { type: "auth_error", message: error.message }).finally(() => socket.destroy()).catch(() => socket.destroy());
|
|
1809
|
+
} else {
|
|
1810
|
+
socket.destroy();
|
|
1811
|
+
}
|
|
1812
|
+
},
|
|
1813
|
+
maxFrameBytes: MAX_CLIENT_FRAME_BYTES,
|
|
1661
1814
|
});
|
|
1662
1815
|
|
|
1816
|
+
socket.on("data", (data) => parser.push(data));
|
|
1817
|
+
|
|
1663
1818
|
socket.on("error", (err) => {
|
|
1664
1819
|
log(`CLI socket error: ${err.message}`);
|
|
1665
1820
|
});
|
|
1666
1821
|
|
|
1667
1822
|
socket.on("close", () => {
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1823
|
+
parser.close();
|
|
1824
|
+
authSession?.close();
|
|
1825
|
+
const activeRequest = context.activeRequest;
|
|
1826
|
+
let cleanupPendingId;
|
|
1827
|
+
if (activeRequest && !activeRequest.queued) {
|
|
1828
|
+
cleanupPendingId = `transfer-cleanup-${++requestCounter}`;
|
|
1829
|
+
pendingToolRequests.set(cleanupPendingId, {
|
|
1830
|
+
request: activeRequest,
|
|
1831
|
+
cleanup: true,
|
|
1832
|
+
tool: "transfer_cleanup",
|
|
1833
|
+
resolve: () => {},
|
|
1834
|
+
reject: () => {},
|
|
1835
|
+
});
|
|
1836
|
+
completeOwnedRequest(context, activeRequest.id, "cleanup-settled");
|
|
1678
1837
|
}
|
|
1838
|
+
const cleanupPromise = cleanupTransfers();
|
|
1839
|
+
cleanupPromise.finally(() => {
|
|
1840
|
+
if (cleanupPendingId) pendingToolRequests.delete(cleanupPendingId);
|
|
1841
|
+
}).catch(() => {});
|
|
1842
|
+
writer.close();
|
|
1843
|
+
sessionManager.close(context);
|
|
1844
|
+
if (activeRequest) pendingToolRequests.tombstoneAfterAbort(activeRequest);
|
|
1845
|
+
log("CLI client disconnected");
|
|
1679
1846
|
for (const [streamId, stream] of activeStreams.entries()) {
|
|
1680
|
-
if (stream.socket === socket)
|
|
1681
|
-
writeMessage({ type: "STREAM_STOP", streamId });
|
|
1682
|
-
activeStreams.delete(streamId);
|
|
1683
|
-
}
|
|
1847
|
+
if (stream.socket === socket) stopActiveStream(streamId);
|
|
1684
1848
|
}
|
|
1685
1849
|
});
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
server.listen(SOCKET_PATH, () => {
|
|
1689
|
-
log("Socket server listening on " + SOCKET_PATH);
|
|
1690
|
-
if (!IS_WIN) { try { fs.chmodSync(SOCKET_PATH, 0o600); } catch {} }
|
|
1691
|
-
writeMessage({ type: "HOST_READY" });
|
|
1692
|
-
log("Sent HOST_READY to extension");
|
|
1693
|
-
});
|
|
1850
|
+
};
|
|
1694
1851
|
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1852
|
+
let listenerLifecycle = null;
|
|
1853
|
+
let shuttingDown = false;
|
|
1854
|
+
let exitCode = 0;
|
|
1855
|
+
let exitScheduled = false;
|
|
1856
|
+
function scheduleExit() {
|
|
1857
|
+
if (exitScheduled) return;
|
|
1858
|
+
exitScheduled = true;
|
|
1859
|
+
setTimeout(() => process.exit(exitCode), 50);
|
|
1860
|
+
}
|
|
1861
|
+
function shutdown(code = 0) {
|
|
1862
|
+
exitCode = Math.max(exitCode, code);
|
|
1863
|
+
if (shuttingDown) return;
|
|
1864
|
+
shuttingDown = true;
|
|
1865
|
+
const cleanupPromises = [...connectedSockets].map((socket) => socket.transferCleanup?.() || Promise.resolve());
|
|
1866
|
+
for (const socket of connectedSockets) socket.destroy();
|
|
1867
|
+
pendingRequests.clear(); pendingToolRequests.clear(); activeStreams.clear();
|
|
1868
|
+
Promise.allSettled([...cleanupPromises, Promise.resolve(listenerLifecycle?.shutdown())]).finally(scheduleExit);
|
|
1869
|
+
}
|
|
1870
|
+
function failStartup(error, endpoint) {
|
|
1871
|
+
log(`Listener startup failed (${endpoint}): ${error.message}`);
|
|
1872
|
+
shutdown(1);
|
|
1873
|
+
}
|
|
1874
|
+
async function startListeners() {
|
|
1875
|
+
let endpoint;
|
|
1876
|
+
try {
|
|
1877
|
+
endpoint = process.env.SURF_LISTEN ? parseListenEndpoint(process.env.SURF_LISTEN) : null;
|
|
1878
|
+
listenerLifecycle = createListenerLifecycle({
|
|
1879
|
+
localPath: SOCKET_PATH,
|
|
1880
|
+
tcpEndpoint: endpoint && { host: endpoint.host, port: endpoint.port },
|
|
1881
|
+
handler: handleClient,
|
|
1882
|
+
onReady: () => {
|
|
1883
|
+
if (endpoint) log(`TCP listener listening on ${endpoint.display}`);
|
|
1884
|
+
writeMessage({ type: "HOST_READY" });
|
|
1885
|
+
log("Sent HOST_READY to extension");
|
|
1886
|
+
},
|
|
1887
|
+
onFatal: (error) => failStartup(error, endpoint?.display || process.env.SURF_LISTEN || SOCKET_PATH),
|
|
1888
|
+
});
|
|
1889
|
+
if (shuttingDown) await listenerLifecycle.shutdown();
|
|
1890
|
+
await listenerLifecycle.start();
|
|
1891
|
+
} catch (error) { failStartup(error, endpoint?.display || process.env.SURF_LISTEN || SOCKET_PATH); }
|
|
1892
|
+
}
|
|
1893
|
+
startListeners();
|
|
1698
1894
|
|
|
1699
1895
|
process.on("SIGTERM", () => {
|
|
1700
1896
|
log("SIGTERM received");
|
|
1701
|
-
|
|
1702
|
-
if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} }
|
|
1703
|
-
process.exit(0);
|
|
1897
|
+
shutdown();
|
|
1704
1898
|
});
|
|
1705
1899
|
|
|
1706
1900
|
process.on("SIGINT", () => {
|
|
1707
1901
|
log("SIGINT received");
|
|
1708
|
-
|
|
1709
|
-
if (!IS_WIN) { try { fs.unlinkSync(SOCKET_PATH); } catch {} }
|
|
1710
|
-
process.exit(0);
|
|
1902
|
+
shutdown();
|
|
1711
1903
|
});
|
|
1712
1904
|
|
|
1713
1905
|
process.on("uncaughtException", (err) => {
|
|
1714
1906
|
log(`Uncaught exception: ${err.message}\n${err.stack}`);
|
|
1715
|
-
|
|
1907
|
+
shutdown(1);
|
|
1716
1908
|
});
|
|
1717
1909
|
|
|
1718
1910
|
log("Host initialization complete, waiting for connections...");
|
|
1911
|
+
} else {
|
|
1912
|
+
module.exports = { createListenerLifecycle, MAX_CLIENT_FRAME_BYTES };
|
|
1913
|
+
}
|