@parall/daemon 1.50.0 → 1.50.1
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/bundle/manifest.json +11 -11
- package/bundle/parall-browser-pod.js +267 -104
- package/bundle/parall-claude-agent.js +25 -18
- package/bundle/parall-codex-agent.js +25 -18
- package/bundle/parall-daemon.js +317 -184
- package/dist/browser-pod.d.ts.map +1 -1
- package/dist/browser-pod.js +9 -14
- package/dist/browser-profile-viewer-control.d.ts +15 -0
- package/dist/browser-profile-viewer-control.d.ts.map +1 -0
- package/dist/browser-profile-viewer-control.js +27 -0
- package/dist/clip-runtime/bb-viewer-command-client.d.ts +19 -0
- package/dist/clip-runtime/bb-viewer-command-client.d.ts.map +1 -0
- package/dist/clip-runtime/bb-viewer-command-client.js +89 -0
- package/dist/clip-runtime/browser-profile-manager.d.ts +3 -1
- package/dist/clip-runtime/browser-profile-manager.d.ts.map +1 -1
- package/dist/clip-runtime/browser-profile-manager.js +2 -2
- package/dist/clip-runtime/browser-viewer-streamer.d.ts +7 -7
- package/dist/clip-runtime/browser-viewer-streamer.d.ts.map +1 -1
- package/dist/clip-runtime/browser-viewer-streamer.js +39 -47
- package/dist/clip-runtime/clip-provider.d.ts +3 -2
- package/dist/clip-runtime/clip-provider.d.ts.map +1 -1
- package/dist/clip-runtime/clip-provider.js +13 -6
- package/dist/clip-runtime/hosted-viewer-command-queue.d.ts +43 -0
- package/dist/clip-runtime/hosted-viewer-command-queue.d.ts.map +1 -0
- package/dist/clip-runtime/hosted-viewer-command-queue.js +72 -0
- package/dist/clip-runtime/index.d.ts +1 -0
- package/dist/clip-runtime/index.d.ts.map +1 -1
- package/dist/clip-runtime/index.js +1 -0
- package/dist/clip-runtime/viewer-command-deadline.d.ts +14 -0
- package/dist/clip-runtime/viewer-command-deadline.d.ts.map +1 -0
- package/dist/clip-runtime/viewer-command-deadline.js +29 -0
- package/dist/clip-runtime/viewer-command-error.d.ts +18 -0
- package/dist/clip-runtime/viewer-command-error.d.ts.map +1 -0
- package/dist/clip-runtime/viewer-command-error.js +23 -0
- package/dist/supervisor.d.ts +0 -8
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +3 -32
- package/package.json +6 -6
package/bundle/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.50.
|
|
3
|
-
"built_at": "2026-07-
|
|
2
|
+
"version": "1.50.1",
|
|
3
|
+
"built_at": "2026-07-28T15:25:57.441Z",
|
|
4
4
|
"min_node_version": "20.0.0",
|
|
5
5
|
"files": {
|
|
6
6
|
"bb-browser-daemon.js": {
|
|
@@ -16,29 +16,29 @@
|
|
|
16
16
|
"size": 18
|
|
17
17
|
},
|
|
18
18
|
"parall-browser-pod.js": {
|
|
19
|
-
"sha256": "
|
|
20
|
-
"size":
|
|
19
|
+
"sha256": "f20c74453ddfbef10528afc1d9b6c4de48fd4a780ddb715431d61185c3454b20",
|
|
20
|
+
"size": 2908552
|
|
21
21
|
},
|
|
22
22
|
"parall-channel-exec.js": {
|
|
23
23
|
"sha256": "7c2a6e12483a45e9eb4a42cd7d4cdd3c4fa21dd09ac1347d9a3e177b4ddee930",
|
|
24
24
|
"size": 7220
|
|
25
25
|
},
|
|
26
26
|
"parall-claude-agent.js": {
|
|
27
|
-
"sha256": "
|
|
28
|
-
"size":
|
|
27
|
+
"sha256": "38048b9d69561a7d92ddc97b6322ae215e2e59dbcbbb6353bfa371632683a17c",
|
|
28
|
+
"size": 2737754
|
|
29
29
|
},
|
|
30
30
|
"parall-codex-agent.js": {
|
|
31
|
-
"sha256": "
|
|
32
|
-
"size":
|
|
31
|
+
"sha256": "c65c89982b58f6e1aea155991d364f1325eef166fe0fb92ab989ea477dfe3dd3",
|
|
32
|
+
"size": 2779494
|
|
33
33
|
},
|
|
34
34
|
"parall-daemon.js": {
|
|
35
|
-
"sha256": "
|
|
36
|
-
"size":
|
|
35
|
+
"sha256": "fd0bf688bcf0df4227c7eef61907276437b2f40961e4ebba28c12d2dda34de73",
|
|
36
|
+
"size": 2969136
|
|
37
37
|
},
|
|
38
38
|
"parall-openclaw-agent.js": {
|
|
39
39
|
"sha256": "856a1c3a9ae0ffef8efe2481fa1171dbeb1d67cccb88616aa9bf16da96a69355",
|
|
40
40
|
"size": 9923
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
|
-
"signature": "
|
|
43
|
+
"signature": "VOZtJbTWb3YvTHoc3hGgfBw3bnL4JM9jdTXmPW6rdc3YpZXCunf//CcfviSu0Acn/p6vnHSEizSX00RAJbrCAw=="
|
|
44
44
|
}
|
|
@@ -63495,6 +63495,17 @@ function clearAllProviderCreds(env) {
|
|
|
63495
63495
|
delete env.PRLL_CLAUDE_ALLOW_API_KEY;
|
|
63496
63496
|
}
|
|
63497
63497
|
|
|
63498
|
+
// ts/sdk/dist/browser-viewer.js
|
|
63499
|
+
var BROWSER_VIEWER_READINESS_TIMEOUT_MS = 6e4;
|
|
63500
|
+
function browserViewerRequestOptions(command, opts) {
|
|
63501
|
+
if (command !== "stream.answer" && command !== "stream.switch")
|
|
63502
|
+
return opts;
|
|
63503
|
+
return {
|
|
63504
|
+
...opts,
|
|
63505
|
+
timeoutMs: Math.max(opts?.timeoutMs ?? 0, BROWSER_VIEWER_READINESS_TIMEOUT_MS)
|
|
63506
|
+
};
|
|
63507
|
+
}
|
|
63508
|
+
|
|
63498
63509
|
// ts/sdk/dist/constants.js
|
|
63499
63510
|
var API_BASE = "/api/v1";
|
|
63500
63511
|
var WIKI_BASE = "/wiki/v1";
|
|
@@ -64838,11 +64849,7 @@ var ParallClient = class _ParallClient {
|
|
|
64838
64849
|
async postBrowseResponse(requestId, response) {
|
|
64839
64850
|
return this.request("POST", ENDPOINTS.MACHINES_ME_BROWSE_RESPONSE(requestId), response);
|
|
64840
64851
|
}
|
|
64841
|
-
/**
|
|
64842
|
-
* `POST /machines/me/browser-profiles/viewer-response/{requestId}` — daemon
|
|
64843
|
-
* reply to a `machine.browser_profile.viewer` control command. Wakes the
|
|
64844
|
-
* api-server request/reply bridge (mirrors {@link postBrowseResponse}).
|
|
64845
|
-
*/
|
|
64852
|
+
/** Reply to `machine.browser_profile.viewer` and wake its request/reply bridge. */
|
|
64846
64853
|
async postBrowserProfileViewerResponse(requestId, response) {
|
|
64847
64854
|
return this.request("POST", ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_VIEWER_RESPONSE(requestId), response);
|
|
64848
64855
|
}
|
|
@@ -64852,7 +64859,7 @@ var ParallClient = class _ParallClient {
|
|
|
64852
64859
|
* owner or org admin. api-server brokers the command to the host daemon.
|
|
64853
64860
|
*/
|
|
64854
64861
|
async browserViewerCommand(orgId, profileId, req, opts) {
|
|
64855
|
-
return this.request("POST", ENDPOINTS.BROWSER_PROFILE_VIEWER_COMMAND(orgId, profileId), req, void 0, false, opts);
|
|
64862
|
+
return this.request("POST", ENDPOINTS.BROWSER_PROFILE_VIEWER_COMMAND(orgId, profileId), req, void 0, false, browserViewerRequestOptions(req.command, opts));
|
|
64856
64863
|
}
|
|
64857
64864
|
/**
|
|
64858
64865
|
* Drive the Cloud Edge live viewer (V1b, design §6): WebRTC signaling + input +
|
|
@@ -66880,8 +66887,113 @@ function comparableUrl(url) {
|
|
|
66880
66887
|
|
|
66881
66888
|
// ts/daemon/dist/clip-runtime/browser-viewer-streamer.js
|
|
66882
66889
|
import { spawn } from "node:child_process";
|
|
66883
|
-
|
|
66890
|
+
|
|
66891
|
+
// ts/daemon/dist/clip-runtime/viewer-command-error.js
|
|
66892
|
+
var ViewerCommandError = class extends Error {
|
|
66893
|
+
code;
|
|
66894
|
+
constructor(message, code) {
|
|
66895
|
+
super(message);
|
|
66896
|
+
this.code = code;
|
|
66897
|
+
this.name = "ViewerCommandError";
|
|
66898
|
+
}
|
|
66899
|
+
};
|
|
66900
|
+
function viewerCommandFailure(error2) {
|
|
66901
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
66902
|
+
if (error2 instanceof ViewerCommandError && error2.code) {
|
|
66903
|
+
return { code: error2.code, message };
|
|
66904
|
+
}
|
|
66905
|
+
return { message };
|
|
66906
|
+
}
|
|
66907
|
+
|
|
66908
|
+
// ts/daemon/dist/clip-runtime/viewer-command-deadline.js
|
|
66909
|
+
var HOSTED_VIEWER_READINESS_POD_BUDGET_MS = 3e4;
|
|
66910
|
+
var HOSTED_VIEWER_REPLY_RESERVE_MS = 5e3;
|
|
66911
|
+
function isViewerReadinessCommand(command) {
|
|
66912
|
+
return command === "stream.answer" || command === "stream.switch";
|
|
66913
|
+
}
|
|
66914
|
+
function parseViewerDispatchDeadlineUnixMs(value) {
|
|
66915
|
+
let parsed;
|
|
66916
|
+
if (typeof value === "number") {
|
|
66917
|
+
parsed = value;
|
|
66918
|
+
} else if (typeof value === "string" && /^[1-9]\d*$/.test(value)) {
|
|
66919
|
+
parsed = Number(value);
|
|
66920
|
+
} else {
|
|
66921
|
+
return void 0;
|
|
66922
|
+
}
|
|
66923
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
66924
|
+
}
|
|
66925
|
+
function viewerReadinessDeadlineError(phase) {
|
|
66926
|
+
return new ViewerCommandError(`viewer readiness deadline elapsed ${phase}`, "VIEWER_STREAM_CANCELLED");
|
|
66927
|
+
}
|
|
66928
|
+
|
|
66929
|
+
// ts/daemon/dist/clip-runtime/bb-viewer-command-client.js
|
|
66884
66930
|
var BB_VIEWER_COMMAND_TIMEOUT_MS = 15e3;
|
|
66931
|
+
var BB_VIEWER_READINESS_COMMAND_TIMEOUT_MS = 3e4;
|
|
66932
|
+
function bbViewerCommandTimeoutMs(method) {
|
|
66933
|
+
return method === "answer" || method === "switch" ? BB_VIEWER_READINESS_COMMAND_TIMEOUT_MS : BB_VIEWER_COMMAND_TIMEOUT_MS;
|
|
66934
|
+
}
|
|
66935
|
+
function bbViewerCommandEffectiveTimeoutMs(method, deadlineUnixMs, now = Date.now()) {
|
|
66936
|
+
const configured = bbViewerCommandTimeoutMs(method);
|
|
66937
|
+
if (deadlineUnixMs === void 0)
|
|
66938
|
+
return configured;
|
|
66939
|
+
const remaining = deadlineUnixMs - now;
|
|
66940
|
+
if (remaining <= 0) {
|
|
66941
|
+
throw viewerReadinessDeadlineError(`before bb-viewer ${method}`);
|
|
66942
|
+
}
|
|
66943
|
+
return Math.max(1, Math.min(configured, remaining));
|
|
66944
|
+
}
|
|
66945
|
+
async function sendBBViewerCommand(port, method, params, deadlineUnixMs) {
|
|
66946
|
+
const controller = new AbortController();
|
|
66947
|
+
const timeoutMs = bbViewerCommandEffectiveTimeoutMs(method, deadlineUnixMs);
|
|
66948
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
66949
|
+
try {
|
|
66950
|
+
const resp = await fetch(`http://127.0.0.1:${port}/command`, {
|
|
66951
|
+
method: "POST",
|
|
66952
|
+
headers: { "Content-Type": "application/json" },
|
|
66953
|
+
body: JSON.stringify({ method, params }),
|
|
66954
|
+
signal: controller.signal
|
|
66955
|
+
});
|
|
66956
|
+
const text = await resp.text();
|
|
66957
|
+
return parseBBViewerCommandResponse(resp.status, text, method);
|
|
66958
|
+
} catch (err) {
|
|
66959
|
+
if (controller.signal.aborted && (method === "answer" || method === "switch")) {
|
|
66960
|
+
throw viewerReadinessDeadlineError(`while waiting for bb-viewer ${method}`);
|
|
66961
|
+
}
|
|
66962
|
+
throw err;
|
|
66963
|
+
} finally {
|
|
66964
|
+
clearTimeout(timer);
|
|
66965
|
+
}
|
|
66966
|
+
}
|
|
66967
|
+
function parseBBViewerCommandResponse(status, text, method) {
|
|
66968
|
+
let parsed;
|
|
66969
|
+
try {
|
|
66970
|
+
parsed = text ? JSON.parse(text) : {};
|
|
66971
|
+
} catch {
|
|
66972
|
+
throw new ViewerCommandError(`bb-viewer ${method} returned invalid JSON`);
|
|
66973
|
+
}
|
|
66974
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
66975
|
+
throw new ViewerCommandError(`bb-viewer ${method} returned an invalid response`);
|
|
66976
|
+
}
|
|
66977
|
+
const envelope = parsed;
|
|
66978
|
+
if (envelope.error) {
|
|
66979
|
+
const code = typeof envelope.error.code === "string" && envelope.error.code.trim() ? envelope.error.code.trim() : void 0;
|
|
66980
|
+
const message = typeof envelope.error.message === "string" && envelope.error.message.trim() ? envelope.error.message : `bb-viewer ${method} failed`;
|
|
66981
|
+
throw new ViewerCommandError(message, code);
|
|
66982
|
+
}
|
|
66983
|
+
if (status < 200 || status >= 300) {
|
|
66984
|
+
throw new ViewerCommandError(`bb-viewer ${method} returned ${status}`);
|
|
66985
|
+
}
|
|
66986
|
+
if (envelope.result === void 0 || envelope.result === null) {
|
|
66987
|
+
return {};
|
|
66988
|
+
}
|
|
66989
|
+
if (typeof envelope.result !== "object" || Array.isArray(envelope.result)) {
|
|
66990
|
+
throw new ViewerCommandError(`bb-viewer ${method} returned an invalid result`);
|
|
66991
|
+
}
|
|
66992
|
+
return envelope.result;
|
|
66993
|
+
}
|
|
66994
|
+
|
|
66995
|
+
// ts/daemon/dist/clip-runtime/browser-viewer-streamer.js
|
|
66996
|
+
var BB_VIEWER_HEALTH_TIMEOUT_MS = 1e4;
|
|
66885
66997
|
var BB_VIEWER_UNANSWERED_REAP_MS = 9e4;
|
|
66886
66998
|
var BB_VIEWER_TERM_GRACE_MS = 5e3;
|
|
66887
66999
|
function isLoopbackBindHost(host) {
|
|
@@ -66918,7 +67030,7 @@ var BrowserViewerStreamer = class {
|
|
|
66918
67030
|
* partial result. `data.input` / `data.turn` / `data.session_id` come from
|
|
66919
67031
|
* MachineBrowserProfileViewerData.
|
|
66920
67032
|
*/
|
|
66921
|
-
async handleViewerCommand(profileId, sessionId, command, input, turn) {
|
|
67033
|
+
async handleViewerCommand(profileId, sessionId, command, input, turn, execution) {
|
|
66922
67034
|
if (!profileId)
|
|
66923
67035
|
throw new Error("browser profile id is required");
|
|
66924
67036
|
if (command !== "stream.start" && sessionId && this.kickedSessions.get(profileId) === sessionId) {
|
|
@@ -66928,11 +67040,11 @@ var BrowserViewerStreamer = class {
|
|
|
66928
67040
|
case "stream.start":
|
|
66929
67041
|
return this.viewerStreamStart(profileId, sessionId, turn);
|
|
66930
67042
|
case "stream.answer":
|
|
66931
|
-
return this.viewerStreamAnswer(profileId, sessionId, input);
|
|
67043
|
+
return this.viewerStreamAnswer(profileId, sessionId, input, execution?.deadlineUnixMs);
|
|
66932
67044
|
case "stream.close":
|
|
66933
67045
|
return this.viewerStreamClose(profileId, sessionId, input);
|
|
66934
67046
|
case "stream.switch":
|
|
66935
|
-
return this.viewerStreamSwitch(profileId, sessionId, input);
|
|
67047
|
+
return this.viewerStreamSwitch(profileId, sessionId, input, execution?.deadlineUnixMs);
|
|
66936
67048
|
case "kick":
|
|
66937
67049
|
return this.viewerKick(profileId, sessionId, input);
|
|
66938
67050
|
case "close":
|
|
@@ -66984,7 +67096,7 @@ var BrowserViewerStreamer = class {
|
|
|
66984
67096
|
await this.bringTargetToFront(cdpUrl);
|
|
66985
67097
|
const streamer = await this.spawnStreamer(profileId, sessionId, turn);
|
|
66986
67098
|
try {
|
|
66987
|
-
const result = await
|
|
67099
|
+
const result = await sendBBViewerCommand(streamer.port, "connect", { cdpUrl });
|
|
66988
67100
|
streamer.streamedTabId = targetIdFromCdpUrl(cdpUrl);
|
|
66989
67101
|
const reapTimer = setTimeout(() => {
|
|
66990
67102
|
const current = this.streamers.get(profileId);
|
|
@@ -67005,14 +67117,17 @@ var BrowserViewerStreamer = class {
|
|
|
67005
67117
|
* Rejects if the streamer's sessionId no longer matches (a stale viewer
|
|
67006
67118
|
* session from a superseded stream.start).
|
|
67007
67119
|
*/
|
|
67008
|
-
async viewerStreamAnswer(profileId, sessionId, input) {
|
|
67120
|
+
async viewerStreamAnswer(profileId, sessionId, input, deadlineUnixMs) {
|
|
67009
67121
|
const streamer = this.streamers.get(profileId);
|
|
67010
67122
|
if (!streamer)
|
|
67011
67123
|
throw new Error("no active viewer streamer for profile");
|
|
67012
67124
|
if (streamer.sessionId !== sessionId)
|
|
67013
67125
|
throw new Error("stale viewer session");
|
|
67126
|
+
this.assertViewerExecutionDeadline(deadlineUnixMs, "before bb-viewer answer");
|
|
67014
67127
|
streamer.answered = true;
|
|
67015
|
-
|
|
67128
|
+
const result = await sendBBViewerCommand(streamer.port, "answer", input ?? {}, deadlineUnixMs);
|
|
67129
|
+
this.assertViewerExecutionDeadline(deadlineUnixMs, "after bb-viewer answer");
|
|
67130
|
+
return result;
|
|
67016
67131
|
}
|
|
67017
67132
|
/**
|
|
67018
67133
|
* stream.close — best-effort stop the streamer, then kill + remove it. A
|
|
@@ -67033,7 +67148,7 @@ var BrowserViewerStreamer = class {
|
|
|
67033
67148
|
return { ok: true, stale: true };
|
|
67034
67149
|
}
|
|
67035
67150
|
try {
|
|
67036
|
-
await
|
|
67151
|
+
await sendBBViewerCommand(streamer.port, "stop", {});
|
|
67037
67152
|
} catch (err) {
|
|
67038
67153
|
this.host.log.warn(`[bb-viewer] stop command failed for ${profileId} (killing anyway): ${formatErrorForLog(err)}`);
|
|
67039
67154
|
}
|
|
@@ -67050,7 +67165,7 @@ var BrowserViewerStreamer = class {
|
|
|
67050
67165
|
* the stream.start response), so a session-less switch is by definition not
|
|
67051
67166
|
* the current viewer.
|
|
67052
67167
|
*/
|
|
67053
|
-
async viewerStreamSwitch(profileId, sessionId, input) {
|
|
67168
|
+
async viewerStreamSwitch(profileId, sessionId, input, deadlineUnixMs) {
|
|
67054
67169
|
const streamer = this.streamers.get(profileId);
|
|
67055
67170
|
if (!streamer)
|
|
67056
67171
|
throw new Error("no active viewer streamer for profile");
|
|
@@ -67060,9 +67175,13 @@ var BrowserViewerStreamer = class {
|
|
|
67060
67175
|
if (tab === void 0 || tab === null || tab === "") {
|
|
67061
67176
|
throw new Error("stream.switch requires input.tab");
|
|
67062
67177
|
}
|
|
67178
|
+
this.assertViewerExecutionDeadline(deadlineUnixMs, "before resolving the switch target");
|
|
67063
67179
|
const cdpUrl = await this.host.withDaemonRecovery(() => this.resolveAccountPageCdpUrl(profileId, tab), `viewer stream.switch ${profileId}`);
|
|
67064
|
-
|
|
67065
|
-
|
|
67180
|
+
this.assertViewerExecutionDeadline(deadlineUnixMs, "after resolving the switch target");
|
|
67181
|
+
await this.bringTargetToFront(cdpUrl, deadlineUnixMs);
|
|
67182
|
+
this.assertViewerExecutionDeadline(deadlineUnixMs, "before bb-viewer switch");
|
|
67183
|
+
const result = await sendBBViewerCommand(streamer.port, "switch", { cdpUrl }, deadlineUnixMs);
|
|
67184
|
+
this.assertViewerExecutionDeadline(deadlineUnixMs, "after bb-viewer switch");
|
|
67066
67185
|
streamer.streamedTabId = targetIdFromCdpUrl(cdpUrl);
|
|
67067
67186
|
return { ...result, streamed_tab_id: streamer.streamedTabId };
|
|
67068
67187
|
}
|
|
@@ -67086,7 +67205,7 @@ var BrowserViewerStreamer = class {
|
|
|
67086
67205
|
}
|
|
67087
67206
|
const cdpUrl = await this.host.withDaemonRecovery(() => this.resolveAccountPageCdpUrl(profileId), `viewer close re-switch ${profileId}`);
|
|
67088
67207
|
await this.bringTargetToFront(cdpUrl);
|
|
67089
|
-
const sw = await
|
|
67208
|
+
const sw = await sendBBViewerCommand(streamer.port, "switch", { cdpUrl });
|
|
67090
67209
|
streamer.streamedTabId = targetIdFromCdpUrl(cdpUrl);
|
|
67091
67210
|
return { ...result, ...sw, streamed_tab_id: streamer.streamedTabId };
|
|
67092
67211
|
}
|
|
@@ -67098,15 +67217,17 @@ var BrowserViewerStreamer = class {
|
|
|
67098
67217
|
* must not block the stream. host:port + targetId are parsed from the cdpUrl
|
|
67099
67218
|
* (`ws://host:port/devtools/page/<targetId>`) to avoid an extra /status call.
|
|
67100
67219
|
*/
|
|
67101
|
-
async bringTargetToFront(cdpUrl) {
|
|
67220
|
+
async bringTargetToFront(cdpUrl, deadlineUnixMs) {
|
|
67102
67221
|
const m3 = /^ws:\/\/([^/]+)\/devtools\/page\/(.+)$/.exec(cdpUrl);
|
|
67103
67222
|
if (!m3)
|
|
67104
67223
|
return;
|
|
67105
67224
|
const [, hostPort, targetId] = m3;
|
|
67225
|
+
this.assertViewerExecutionDeadline(deadlineUnixMs, "before foregrounding the switch target");
|
|
67226
|
+
const timeoutMs = deadlineUnixMs === void 0 ? 5e3 : Math.max(1, Math.min(5e3, deadlineUnixMs - Date.now()));
|
|
67106
67227
|
try {
|
|
67107
67228
|
const resp = await fetch(`http://${hostPort}/json/activate/${targetId}`, {
|
|
67108
67229
|
method: "GET",
|
|
67109
|
-
signal: AbortSignal.timeout(
|
|
67230
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
67110
67231
|
});
|
|
67111
67232
|
if (!resp.ok) {
|
|
67112
67233
|
this.host.log.warn(`[bb-viewer] activate target ${targetId} returned ${resp.status} (continuing)`);
|
|
@@ -67115,6 +67236,11 @@ var BrowserViewerStreamer = class {
|
|
|
67115
67236
|
this.host.log.warn(`[bb-viewer] activate target ${targetId} failed (continuing): ${formatErrorForLog(err)}`);
|
|
67116
67237
|
}
|
|
67117
67238
|
}
|
|
67239
|
+
assertViewerExecutionDeadline(deadlineUnixMs, phase) {
|
|
67240
|
+
if (deadlineUnixMs !== void 0 && Date.now() >= deadlineUnixMs) {
|
|
67241
|
+
throw viewerReadinessDeadlineError(phase);
|
|
67242
|
+
}
|
|
67243
|
+
}
|
|
67118
67244
|
/**
|
|
67119
67245
|
* Forward a navigation command (tab_list/tab_new/open/reload/back/forward/
|
|
67120
67246
|
* close) to bb-browser scoped to the profile's account. This lets the human
|
|
@@ -67315,34 +67441,6 @@ var BrowserViewerStreamer = class {
|
|
|
67315
67441
|
killTimer.unref?.();
|
|
67316
67442
|
child.once("exit", () => clearTimeout(killTimer));
|
|
67317
67443
|
}
|
|
67318
|
-
/**
|
|
67319
|
-
* POST a command to the profile's bb-viewer `/command` endpoint. Parses the
|
|
67320
|
-
* `{result}` / `{error:{message}}` envelope (bb-viewer api.go) and returns
|
|
67321
|
-
* the inner result, throwing the error message on failure.
|
|
67322
|
-
*/
|
|
67323
|
-
async streamerCommand(streamer, method, params) {
|
|
67324
|
-
const controller = new AbortController();
|
|
67325
|
-
const timer = setTimeout(() => controller.abort(), BB_VIEWER_COMMAND_TIMEOUT_MS);
|
|
67326
|
-
try {
|
|
67327
|
-
const resp = await fetch(`http://127.0.0.1:${streamer.port}/command`, {
|
|
67328
|
-
method: "POST",
|
|
67329
|
-
headers: { "Content-Type": "application/json" },
|
|
67330
|
-
body: JSON.stringify({ method, params }),
|
|
67331
|
-
signal: controller.signal
|
|
67332
|
-
});
|
|
67333
|
-
const text = await resp.text();
|
|
67334
|
-
const parsed = text ? JSON.parse(text) : {};
|
|
67335
|
-
if (parsed.error) {
|
|
67336
|
-
throw new Error(parsed.error.message || `bb-viewer ${method} failed`);
|
|
67337
|
-
}
|
|
67338
|
-
if (!resp.ok) {
|
|
67339
|
-
throw new Error(`bb-viewer ${method} returned ${resp.status}: ${text}`);
|
|
67340
|
-
}
|
|
67341
|
-
return parsed.result ?? {};
|
|
67342
|
-
} finally {
|
|
67343
|
-
clearTimeout(timer);
|
|
67344
|
-
}
|
|
67345
|
-
}
|
|
67346
67444
|
/**
|
|
67347
67445
|
* Kill + remove the profile's bb-viewer streamer, if any. Idempotent. Also
|
|
67348
67446
|
* clears any kick marker for the profile: a (re)kill establishes a clean
|
|
@@ -67602,8 +67700,8 @@ var BrowserProfileManager = class {
|
|
|
67602
67700
|
* (browser-viewer-streamer.ts), which owns the per-profile bb-viewer
|
|
67603
67701
|
* subprocesses and viewer tab navigation.
|
|
67604
67702
|
*/
|
|
67605
|
-
async handleViewerCommand(profileId, sessionId, command, input, turn) {
|
|
67606
|
-
return this.viewer.handleViewerCommand(profileId, sessionId, command, input, turn);
|
|
67703
|
+
async handleViewerCommand(profileId, sessionId, command, input, turn, execution) {
|
|
67704
|
+
return this.viewer.handleViewerCommand(profileId, sessionId, command, input, turn, execution);
|
|
67607
67705
|
}
|
|
67608
67706
|
/** Read cdpHost/cdpPort from the bb-browser daemon `GET /status`. */
|
|
67609
67707
|
async cdpEndpoint() {
|
|
@@ -68453,6 +68551,46 @@ function errMsg(err) {
|
|
|
68453
68551
|
// ts/daemon/dist/clip-runtime/clip-provider.js
|
|
68454
68552
|
import * as http2 from "node:http2";
|
|
68455
68553
|
|
|
68554
|
+
// ts/daemon/dist/clip-runtime/browser-dependency.js
|
|
68555
|
+
var BROWSER_CAPABILITY_NAME = "browser";
|
|
68556
|
+
var BROWSER_CAPABILITY_PACKAGE = "@pinixai/bb-browser-pro";
|
|
68557
|
+
var BROWSER_DEPENDENCY_NAMES = /* @__PURE__ */ new Set([
|
|
68558
|
+
BROWSER_CAPABILITY_NAME,
|
|
68559
|
+
"@pinix/browser",
|
|
68560
|
+
"@pinixai/bb-browser-pro",
|
|
68561
|
+
"bb-browser"
|
|
68562
|
+
]);
|
|
68563
|
+
function isBrowserDependencyName(name) {
|
|
68564
|
+
return !!name && BROWSER_DEPENDENCY_NAMES.has(name);
|
|
68565
|
+
}
|
|
68566
|
+
function browserCapabilityCommands() {
|
|
68567
|
+
return [
|
|
68568
|
+
{ name: "eval", description: "Run JavaScript in a page context" },
|
|
68569
|
+
{ name: "open", description: "Open a URL in a new tab" },
|
|
68570
|
+
{ name: "snapshot", description: "Capture the accessibility/DOM snapshot of a page" },
|
|
68571
|
+
{ name: "click", description: "Click an element" },
|
|
68572
|
+
{ name: "type", description: "Type text into an element" }
|
|
68573
|
+
];
|
|
68574
|
+
}
|
|
68575
|
+
function manifestDeclaresBrowserDependency(manifest) {
|
|
68576
|
+
return dependencyMaps(manifest).some((deps) => Object.entries(deps).some(([name, dep]) => isBrowserDependencyName(name) || isBrowserDependencyName(dep?.package)));
|
|
68577
|
+
}
|
|
68578
|
+
function manifestMapsDependencyToBrowser(manifest, dependencyName) {
|
|
68579
|
+
const maps = dependencyMaps(manifest);
|
|
68580
|
+
if (maps.length === 0)
|
|
68581
|
+
return false;
|
|
68582
|
+
for (const deps of maps) {
|
|
68583
|
+
const declared = deps[dependencyName];
|
|
68584
|
+
if (declared) {
|
|
68585
|
+
return isBrowserDependencyName(dependencyName) || isBrowserDependencyName(declared.package);
|
|
68586
|
+
}
|
|
68587
|
+
}
|
|
68588
|
+
return isBrowserDependencyName(dependencyName) && manifestDeclaresBrowserDependency(manifest);
|
|
68589
|
+
}
|
|
68590
|
+
function dependencyMaps(manifest) {
|
|
68591
|
+
return [manifest?.dependencies, manifest?.dependencySlots].filter((deps) => !!deps);
|
|
68592
|
+
}
|
|
68593
|
+
|
|
68456
68594
|
// ts/daemon/dist/clip-runtime/process.js
|
|
68457
68595
|
import { spawn as spawn4 } from "node:child_process";
|
|
68458
68596
|
import { createInterface as createInterface2 } from "node:readline";
|
|
@@ -68709,46 +68847,6 @@ function finalizeManifest(manifest) {
|
|
|
68709
68847
|
return m3;
|
|
68710
68848
|
}
|
|
68711
68849
|
|
|
68712
|
-
// ts/daemon/dist/clip-runtime/browser-dependency.js
|
|
68713
|
-
var BROWSER_CAPABILITY_NAME = "browser";
|
|
68714
|
-
var BROWSER_CAPABILITY_PACKAGE = "@pinixai/bb-browser-pro";
|
|
68715
|
-
var BROWSER_DEPENDENCY_NAMES = /* @__PURE__ */ new Set([
|
|
68716
|
-
BROWSER_CAPABILITY_NAME,
|
|
68717
|
-
"@pinix/browser",
|
|
68718
|
-
"@pinixai/bb-browser-pro",
|
|
68719
|
-
"bb-browser"
|
|
68720
|
-
]);
|
|
68721
|
-
function isBrowserDependencyName(name) {
|
|
68722
|
-
return !!name && BROWSER_DEPENDENCY_NAMES.has(name);
|
|
68723
|
-
}
|
|
68724
|
-
function browserCapabilityCommands() {
|
|
68725
|
-
return [
|
|
68726
|
-
{ name: "eval", description: "Run JavaScript in a page context" },
|
|
68727
|
-
{ name: "open", description: "Open a URL in a new tab" },
|
|
68728
|
-
{ name: "snapshot", description: "Capture the accessibility/DOM snapshot of a page" },
|
|
68729
|
-
{ name: "click", description: "Click an element" },
|
|
68730
|
-
{ name: "type", description: "Type text into an element" }
|
|
68731
|
-
];
|
|
68732
|
-
}
|
|
68733
|
-
function manifestDeclaresBrowserDependency(manifest) {
|
|
68734
|
-
return dependencyMaps(manifest).some((deps) => Object.entries(deps).some(([name, dep]) => isBrowserDependencyName(name) || isBrowserDependencyName(dep?.package)));
|
|
68735
|
-
}
|
|
68736
|
-
function manifestMapsDependencyToBrowser(manifest, dependencyName) {
|
|
68737
|
-
const maps = dependencyMaps(manifest);
|
|
68738
|
-
if (maps.length === 0)
|
|
68739
|
-
return false;
|
|
68740
|
-
for (const deps of maps) {
|
|
68741
|
-
const declared = deps[dependencyName];
|
|
68742
|
-
if (declared) {
|
|
68743
|
-
return isBrowserDependencyName(dependencyName) || isBrowserDependencyName(declared.package);
|
|
68744
|
-
}
|
|
68745
|
-
}
|
|
68746
|
-
return isBrowserDependencyName(dependencyName) && manifestDeclaresBrowserDependency(manifest);
|
|
68747
|
-
}
|
|
68748
|
-
function dependencyMaps(manifest) {
|
|
68749
|
-
return [manifest?.dependencies, manifest?.dependencySlots].filter((deps) => !!deps);
|
|
68750
|
-
}
|
|
68751
|
-
|
|
68752
68850
|
// ts/daemon/dist/clip-runtime/process.js
|
|
68753
68851
|
var CLIP_REGISTER_TIMEOUT_MS = 1e4;
|
|
68754
68852
|
var CLIP_STOP_TIMEOUT_MS = 5e3;
|
|
@@ -69503,8 +69601,8 @@ var ClipProvider = class _ClipProvider {
|
|
|
69503
69601
|
/**
|
|
69504
69602
|
* Handle a hosted-browser live-viewer command relayed by clip-service over the
|
|
69505
69603
|
* stream and reply on the same stream (design §3.4). Always answers the
|
|
69506
|
-
* request/reply bridge exactly once — `{result}` on success,
|
|
69507
|
-
* on failure — and never throws into the message loop, mirroring the BYOC
|
|
69604
|
+
* request/reply bridge exactly once — `{result}` on success,
|
|
69605
|
+
* `{error:{code?,message}}` on failure — and never throws into the message loop, mirroring the BYOC
|
|
69508
69606
|
* daemon's handleBrowserProfileViewer (which replies over HTTP instead).
|
|
69509
69607
|
*/
|
|
69510
69608
|
async handleViewerCommand(cmd) {
|
|
@@ -69517,6 +69615,10 @@ var ClipProvider = class _ClipProvider {
|
|
|
69517
69615
|
return;
|
|
69518
69616
|
}
|
|
69519
69617
|
try {
|
|
69618
|
+
const dispatchDeadlineUnixMs = parseViewerDispatchDeadlineUnixMs(cmd.dispatchDeadlineUnixMs);
|
|
69619
|
+
if (isViewerReadinessCommand(cmd.command) && dispatchDeadlineUnixMs === void 0) {
|
|
69620
|
+
throw viewerReadinessDeadlineError("because the ProviderStream command has no authenticated deadline");
|
|
69621
|
+
}
|
|
69520
69622
|
let input;
|
|
69521
69623
|
if (cmd.input) {
|
|
69522
69624
|
const decoded = Buffer.from(cmd.input, "base64").toString("utf-8");
|
|
@@ -69531,13 +69633,14 @@ var ClipProvider = class _ClipProvider {
|
|
|
69531
69633
|
sessionId: cmd.sessionId,
|
|
69532
69634
|
command: cmd.command,
|
|
69533
69635
|
input,
|
|
69534
|
-
turn: cmd.turn
|
|
69636
|
+
turn: cmd.turn,
|
|
69637
|
+
dispatchDeadlineUnixMs
|
|
69535
69638
|
});
|
|
69536
69639
|
await this.sendViewerReply(requestId, { result });
|
|
69537
69640
|
} catch (err) {
|
|
69538
|
-
const
|
|
69539
|
-
this.opts.log.warn(`[clip-provider] viewer command failed (profile=${cmd.profileId}, command=${cmd.command}): ${message}`);
|
|
69540
|
-
await this.sendViewerReply(requestId, { error:
|
|
69641
|
+
const failure = viewerCommandFailure(err);
|
|
69642
|
+
this.opts.log.warn(`[clip-provider] viewer command failed (profile=${cmd.profileId}, command=${cmd.command}): ${failure.message}`);
|
|
69643
|
+
await this.sendViewerReply(requestId, { error: failure }).catch(() => {
|
|
69541
69644
|
});
|
|
69542
69645
|
}
|
|
69543
69646
|
}
|
|
@@ -69623,6 +69726,70 @@ var ClipProvider = class _ClipProvider {
|
|
|
69623
69726
|
}
|
|
69624
69727
|
};
|
|
69625
69728
|
|
|
69729
|
+
// ts/daemon/dist/clip-runtime/hosted-viewer-command-queue.js
|
|
69730
|
+
var HostedViewerCommandQueue = class {
|
|
69731
|
+
run;
|
|
69732
|
+
tail = Promise.resolve();
|
|
69733
|
+
now;
|
|
69734
|
+
readinessBudgetMs;
|
|
69735
|
+
replyReserveMs;
|
|
69736
|
+
schedule;
|
|
69737
|
+
constructor(run, opts = {}) {
|
|
69738
|
+
this.run = run;
|
|
69739
|
+
this.now = opts.now ?? Date.now;
|
|
69740
|
+
this.readinessBudgetMs = opts.readinessBudgetMs ?? HOSTED_VIEWER_READINESS_POD_BUDGET_MS;
|
|
69741
|
+
this.replyReserveMs = opts.replyReserveMs ?? HOSTED_VIEWER_REPLY_RESERVE_MS;
|
|
69742
|
+
this.schedule = opts.schedule ?? ((callback, delayMs) => {
|
|
69743
|
+
const handle = setTimeout(callback, delayMs);
|
|
69744
|
+
return {
|
|
69745
|
+
cancel: () => clearTimeout(handle),
|
|
69746
|
+
unref: () => handle.unref?.()
|
|
69747
|
+
};
|
|
69748
|
+
});
|
|
69749
|
+
}
|
|
69750
|
+
enqueue(command) {
|
|
69751
|
+
const receivedAt = this.now();
|
|
69752
|
+
const executionDeadline = this.executionDeadline(command, receivedAt);
|
|
69753
|
+
const run = this.tail.catch(() => {
|
|
69754
|
+
}).then(() => {
|
|
69755
|
+
if (executionDeadline !== void 0 && this.now() >= executionDeadline) {
|
|
69756
|
+
throw viewerReadinessDeadlineError("before hosted pod execution");
|
|
69757
|
+
}
|
|
69758
|
+
return this.run(command, executionDeadline);
|
|
69759
|
+
});
|
|
69760
|
+
this.tail = run.catch(() => {
|
|
69761
|
+
});
|
|
69762
|
+
if (executionDeadline === void 0) {
|
|
69763
|
+
return run;
|
|
69764
|
+
}
|
|
69765
|
+
let timer;
|
|
69766
|
+
const timeout = new Promise((_, reject) => {
|
|
69767
|
+
const delay = Math.max(0, executionDeadline - this.now());
|
|
69768
|
+
timer = this.schedule(() => {
|
|
69769
|
+
reject(viewerReadinessDeadlineError("inside the hosted pod queue"));
|
|
69770
|
+
}, delay);
|
|
69771
|
+
timer.unref?.();
|
|
69772
|
+
});
|
|
69773
|
+
return Promise.race([run, timeout]).finally(() => {
|
|
69774
|
+
timer?.cancel();
|
|
69775
|
+
});
|
|
69776
|
+
}
|
|
69777
|
+
executionDeadline(command, receivedAt) {
|
|
69778
|
+
if (!isViewerReadinessCommand(command.command)) {
|
|
69779
|
+
return void 0;
|
|
69780
|
+
}
|
|
69781
|
+
const dispatchDeadline = command.dispatchDeadlineUnixMs;
|
|
69782
|
+
if (!Number.isSafeInteger(dispatchDeadline) || dispatchDeadline === void 0) {
|
|
69783
|
+
throw viewerReadinessDeadlineError("because the authenticated deadline is missing");
|
|
69784
|
+
}
|
|
69785
|
+
const deadline = Math.min(receivedAt + this.readinessBudgetMs, dispatchDeadline - this.replyReserveMs);
|
|
69786
|
+
if (deadline <= receivedAt) {
|
|
69787
|
+
throw viewerReadinessDeadlineError("before hosted pod receipt");
|
|
69788
|
+
}
|
|
69789
|
+
return deadline;
|
|
69790
|
+
}
|
|
69791
|
+
};
|
|
69792
|
+
|
|
69626
69793
|
// ts/daemon/dist/clip-runtime/process-manager.js
|
|
69627
69794
|
import * as fs3 from "node:fs";
|
|
69628
69795
|
import * as path5 from "node:path";
|
|
@@ -70599,14 +70766,10 @@ async function preparePodRuntime(config, env, log) {
|
|
|
70599
70766
|
dataDir: path6.join(config.stateDir, "clip-data"),
|
|
70600
70767
|
browserProfileManager: browser
|
|
70601
70768
|
});
|
|
70602
|
-
|
|
70603
|
-
|
|
70604
|
-
|
|
70605
|
-
|
|
70606
|
-
viewerChain = run.catch(() => {
|
|
70607
|
-
});
|
|
70608
|
-
return run;
|
|
70609
|
-
};
|
|
70769
|
+
const viewerCommands = new HostedViewerCommandQueue((cmd, executionDeadlineUnixMs) => browser.handleViewerCommand(cmd.profileId, cmd.sessionId, cmd.command, cmd.input, cmd.turn, {
|
|
70770
|
+
deadlineUnixMs: executionDeadlineUnixMs
|
|
70771
|
+
}));
|
|
70772
|
+
const onViewerCommand = viewerCommands.enqueue.bind(viewerCommands);
|
|
70610
70773
|
const provider = new ClipProvider({
|
|
70611
70774
|
serviceUrl: config.clipRpcUrl,
|
|
70612
70775
|
authKey: config.assignmentToken,
|
|
@@ -51291,18 +51291,15 @@ function laneContextFilePath(contextDir, targetUri, threadRootId) {
|
|
|
51291
51291
|
return path.join(contextDir, `${laneKeyForTarget(targetUri, threadRootId)}.json`);
|
|
51292
51292
|
}
|
|
51293
51293
|
|
|
51294
|
-
// ts/sdk/dist/
|
|
51295
|
-
var
|
|
51296
|
-
|
|
51297
|
-
|
|
51298
|
-
|
|
51299
|
-
|
|
51300
|
-
|
|
51301
|
-
|
|
51302
|
-
|
|
51303
|
-
if (mentionUserId === MENTION_ALL_AGENTS_USER_ID)
|
|
51304
|
-
return userType === "agent";
|
|
51305
|
-
return false;
|
|
51294
|
+
// ts/sdk/dist/browser-viewer.js
|
|
51295
|
+
var BROWSER_VIEWER_READINESS_TIMEOUT_MS = 6e4;
|
|
51296
|
+
function browserViewerRequestOptions(command, opts) {
|
|
51297
|
+
if (command !== "stream.answer" && command !== "stream.switch")
|
|
51298
|
+
return opts;
|
|
51299
|
+
return {
|
|
51300
|
+
...opts,
|
|
51301
|
+
timeoutMs: Math.max(opts?.timeoutMs ?? 0, BROWSER_VIEWER_READINESS_TIMEOUT_MS)
|
|
51302
|
+
};
|
|
51306
51303
|
}
|
|
51307
51304
|
|
|
51308
51305
|
// ts/sdk/dist/constants.js
|
|
@@ -52734,11 +52731,7 @@ var ParallClient = class _ParallClient {
|
|
|
52734
52731
|
async postBrowseResponse(requestId, response) {
|
|
52735
52732
|
return this.request("POST", ENDPOINTS.MACHINES_ME_BROWSE_RESPONSE(requestId), response);
|
|
52736
52733
|
}
|
|
52737
|
-
/**
|
|
52738
|
-
* `POST /machines/me/browser-profiles/viewer-response/{requestId}` — daemon
|
|
52739
|
-
* reply to a `machine.browser_profile.viewer` control command. Wakes the
|
|
52740
|
-
* api-server request/reply bridge (mirrors {@link postBrowseResponse}).
|
|
52741
|
-
*/
|
|
52734
|
+
/** Reply to `machine.browser_profile.viewer` and wake its request/reply bridge. */
|
|
52742
52735
|
async postBrowserProfileViewerResponse(requestId, response) {
|
|
52743
52736
|
return this.request("POST", ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_VIEWER_RESPONSE(requestId), response);
|
|
52744
52737
|
}
|
|
@@ -52748,7 +52741,7 @@ var ParallClient = class _ParallClient {
|
|
|
52748
52741
|
* owner or org admin. api-server brokers the command to the host daemon.
|
|
52749
52742
|
*/
|
|
52750
52743
|
async browserViewerCommand(orgId, profileId, req, opts) {
|
|
52751
|
-
return this.request("POST", ENDPOINTS.BROWSER_PROFILE_VIEWER_COMMAND(orgId, profileId), req, void 0, false, opts);
|
|
52744
|
+
return this.request("POST", ENDPOINTS.BROWSER_PROFILE_VIEWER_COMMAND(orgId, profileId), req, void 0, false, browserViewerRequestOptions(req.command, opts));
|
|
52752
52745
|
}
|
|
52753
52746
|
/**
|
|
52754
52747
|
* Drive the Cloud Edge live viewer (V1b, design §6): WebRTC signaling + input +
|
|
@@ -53932,6 +53925,20 @@ function buildApiError(res, rawErrorBody) {
|
|
|
53932
53925
|
return apiError;
|
|
53933
53926
|
}
|
|
53934
53927
|
|
|
53928
|
+
// ts/sdk/dist/types.js
|
|
53929
|
+
var MENTION_ALL_USER_ID = "all";
|
|
53930
|
+
var MENTION_ALL_HUMANS_USER_ID = "allhuman";
|
|
53931
|
+
var MENTION_ALL_AGENTS_USER_ID = "allagent";
|
|
53932
|
+
function mentionTargetsUser(mentionUserId, userId, userType) {
|
|
53933
|
+
if (mentionUserId === userId || mentionUserId === MENTION_ALL_USER_ID)
|
|
53934
|
+
return true;
|
|
53935
|
+
if (mentionUserId === MENTION_ALL_HUMANS_USER_ID)
|
|
53936
|
+
return userType === "human";
|
|
53937
|
+
if (mentionUserId === MENTION_ALL_AGENTS_USER_ID)
|
|
53938
|
+
return userType === "agent";
|
|
53939
|
+
return false;
|
|
53940
|
+
}
|
|
53941
|
+
|
|
53935
53942
|
// ts/sdk/dist/ws.js
|
|
53936
53943
|
function isRetryableNetworkError(err) {
|
|
53937
53944
|
return err instanceof Error && err.name === "ApiError" && "status" in err && err.status === 0;
|