browser-pilot-cli 0.3.0-rc.2 → 0.3.0-rc.4
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 +2 -1
- package/dist/cli.js +10 -2
- package/dist/daemon.js +241 -251
- package/docs/architecture/browser-pilot-platform-spec.md +24 -15
- package/docs/integration/stdio-bridge.md +22 -11
- package/docs/plans/profile-context-routing.md +13 -13
- package/docs/plans/universal-agent-integration.md +8 -6
- package/docs/plans/v0.3.0-stabilization.md +51 -0
- package/docs/releases/v0.3.0-rc.1.md +9 -3
- package/docs/releases/v0.3.0-rc.2.md +17 -2
- package/docs/releases/v0.3.0-rc.3.md +56 -0
- package/docs/releases/v0.3.0-rc.4.md +28 -0
- package/package.json +5 -1
package/dist/daemon.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import http2 from "http";
|
|
3
3
|
import { unlinkSync as unlinkSync2, chmodSync as chmodSync2 } from "fs";
|
|
4
4
|
import { createHash as createHash5, randomUUID as randomUUID14 } from "crypto";
|
|
5
|
-
import { join as
|
|
5
|
+
import { join as join5 } from "path";
|
|
6
6
|
|
|
7
7
|
// src/paths.ts
|
|
8
8
|
import { createHash } from "crypto";
|
|
@@ -2344,10 +2344,10 @@ var MemoryCommandRuntime = class {
|
|
|
2344
2344
|
throw new BrowserPilotError("internal_error", "Invalid or duplicate Command ID");
|
|
2345
2345
|
}
|
|
2346
2346
|
const acceptedAt = this.now();
|
|
2347
|
-
let
|
|
2347
|
+
let resolve2;
|
|
2348
2348
|
let reject;
|
|
2349
2349
|
const completion = new Promise((resolvePromise, rejectPromise) => {
|
|
2350
|
-
|
|
2350
|
+
resolve2 = resolvePromise;
|
|
2351
2351
|
reject = rejectPromise;
|
|
2352
2352
|
});
|
|
2353
2353
|
const record = {
|
|
@@ -2368,7 +2368,7 @@ var MemoryCommandRuntime = class {
|
|
|
2368
2368
|
scopeKey,
|
|
2369
2369
|
cancellation: input.cancellation,
|
|
2370
2370
|
abortController: new AbortController(),
|
|
2371
|
-
resolve:
|
|
2371
|
+
resolve: resolve2,
|
|
2372
2372
|
reject,
|
|
2373
2373
|
completion,
|
|
2374
2374
|
settled: false
|
|
@@ -2988,8 +2988,8 @@ var MemoryBrokerRuntime = class {
|
|
|
2988
2988
|
if (connection.notificationWaiter) {
|
|
2989
2989
|
throw invalidArgument("Only one notification poll may be active per bridge Connection");
|
|
2990
2990
|
}
|
|
2991
|
-
return new Promise((
|
|
2992
|
-
const waiter = { resolve:
|
|
2991
|
+
return new Promise((resolve2) => {
|
|
2992
|
+
const waiter = { resolve: resolve2, signal: options.signal };
|
|
2993
2993
|
const finish = () => this.resolveNotificationWaiter(connection, void 0);
|
|
2994
2994
|
waiter.timer = setTimeout(finish, waitMs);
|
|
2995
2995
|
waiter.timer.unref();
|
|
@@ -5458,8 +5458,8 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
|
|
|
5458
5458
|
let interactiveSince = null;
|
|
5459
5459
|
const deadlineReached = /* @__PURE__ */ Symbol("page-load-deadline");
|
|
5460
5460
|
let deadlineTimer;
|
|
5461
|
-
const deadline = new Promise((
|
|
5462
|
-
deadlineTimer = setTimeout(() =>
|
|
5461
|
+
const deadline = new Promise((resolve2) => {
|
|
5462
|
+
deadlineTimer = setTimeout(() => resolve2(deadlineReached), timeout);
|
|
5463
5463
|
});
|
|
5464
5464
|
const injectBorder = async () => {
|
|
5465
5465
|
await Promise.race([
|
|
@@ -5491,7 +5491,7 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
|
|
|
5491
5491
|
}
|
|
5492
5492
|
}
|
|
5493
5493
|
const sleep = await Promise.race([
|
|
5494
|
-
new Promise((
|
|
5494
|
+
new Promise((resolve2) => setTimeout(() => resolve2("poll"), 200)),
|
|
5495
5495
|
deadline
|
|
5496
5496
|
]);
|
|
5497
5497
|
if (sleep === deadlineReached) throw new PageLoadTimeoutError(timeout);
|
|
@@ -5675,7 +5675,7 @@ var InputDispatcher = class {
|
|
|
5675
5675
|
}
|
|
5676
5676
|
hooks.afterCharacter?.({ character: char, index });
|
|
5677
5677
|
index += 1;
|
|
5678
|
-
if (delayMs > 0) await new Promise((
|
|
5678
|
+
if (delayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
5679
5679
|
}
|
|
5680
5680
|
}
|
|
5681
5681
|
async insertText(text) {
|
|
@@ -5719,7 +5719,7 @@ var ObservationService = class {
|
|
|
5719
5719
|
}
|
|
5720
5720
|
async observeAfterAction(limit = OBSERVATION_V1_LIMITS.defaultElements) {
|
|
5721
5721
|
if (this.settleDelayMs > 0) {
|
|
5722
|
-
await new Promise((
|
|
5722
|
+
await new Promise((resolve2) => setTimeout(resolve2, this.settleDelayMs));
|
|
5723
5723
|
}
|
|
5724
5724
|
await this.loadWaiter(this.transport, this.sessionId, this.loadTimeoutMs);
|
|
5725
5725
|
return this.observe(limit);
|
|
@@ -6076,7 +6076,7 @@ var ActionService = class {
|
|
|
6076
6076
|
await this.input.click(point.x, point.y, { button, clickCount });
|
|
6077
6077
|
this.markDispatched(run);
|
|
6078
6078
|
if (this.readbackDelayMs > 0) {
|
|
6079
|
-
await new Promise((
|
|
6079
|
+
await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
|
|
6080
6080
|
}
|
|
6081
6081
|
let after;
|
|
6082
6082
|
try {
|
|
@@ -6128,7 +6128,7 @@ var ActionService = class {
|
|
|
6128
6128
|
await this.input.press(key);
|
|
6129
6129
|
this.markDispatched(run);
|
|
6130
6130
|
if (this.readbackDelayMs > 0) {
|
|
6131
|
-
await new Promise((
|
|
6131
|
+
await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
|
|
6132
6132
|
}
|
|
6133
6133
|
let after;
|
|
6134
6134
|
try {
|
|
@@ -6194,7 +6194,7 @@ var ActionService = class {
|
|
|
6194
6194
|
this.markDispatched(run);
|
|
6195
6195
|
}
|
|
6196
6196
|
if (this.readbackDelayMs > 0) {
|
|
6197
|
-
await new Promise((
|
|
6197
|
+
await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
|
|
6198
6198
|
}
|
|
6199
6199
|
const after = await this.readElementState(objectId);
|
|
6200
6200
|
evidence = inputEvidence("type", before, after, text, !!options.clear);
|
|
@@ -6226,7 +6226,7 @@ var ActionService = class {
|
|
|
6226
6226
|
this.willDispatch();
|
|
6227
6227
|
await this.input.click(location.x, location.y);
|
|
6228
6228
|
this.markDispatched(run);
|
|
6229
|
-
if (this.focusDelayMs > 0) await new Promise((
|
|
6229
|
+
if (this.focusDelayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, this.focusDelayMs));
|
|
6230
6230
|
}
|
|
6231
6231
|
await this.captureFocus(run, "capture_input_focus");
|
|
6232
6232
|
const before = await this.readActiveState();
|
|
@@ -6248,7 +6248,7 @@ var ActionService = class {
|
|
|
6248
6248
|
},
|
|
6249
6249
|
afterCharacter: () => this.markDispatched(run)
|
|
6250
6250
|
});
|
|
6251
|
-
if (this.readbackDelayMs > 0) await new Promise((
|
|
6251
|
+
if (this.readbackDelayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
|
|
6252
6252
|
const after = await this.readActiveState();
|
|
6253
6253
|
const evidence = inputEvidence("keyboard", before, after, text, !!options.clear);
|
|
6254
6254
|
this.requireVerification(evidence, options.verification);
|
|
@@ -6608,8 +6608,8 @@ var CdpActionContinuityGuard = class _CdpActionContinuityGuard {
|
|
|
6608
6608
|
|
|
6609
6609
|
// src/services/download-controller.ts
|
|
6610
6610
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
6611
|
-
import {
|
|
6612
|
-
import {
|
|
6611
|
+
import { lstat } from "fs/promises";
|
|
6612
|
+
import { isAbsolute as isAbsolute3 } from "path";
|
|
6613
6613
|
|
|
6614
6614
|
// src/services/agent-hint-service.ts
|
|
6615
6615
|
var MAX_HINT_REFS = 32;
|
|
@@ -6710,6 +6710,15 @@ function repeatedActionAgentHint(streak, value) {
|
|
|
6710
6710
|
// src/services/download-controller.ts
|
|
6711
6711
|
var MAX_EVENT_URL_LENGTH = 16384;
|
|
6712
6712
|
var MAX_FILE_NAME_LENGTH = 4096;
|
|
6713
|
+
var MAX_FILE_PATH_LENGTH = 32768;
|
|
6714
|
+
var DownloadFinalizeError = class extends Error {
|
|
6715
|
+
code;
|
|
6716
|
+
constructor(code) {
|
|
6717
|
+
super(code);
|
|
6718
|
+
this.name = "DownloadFinalizeError";
|
|
6719
|
+
this.code = code;
|
|
6720
|
+
}
|
|
6721
|
+
};
|
|
6713
6722
|
function boundedString(value, maxLength) {
|
|
6714
6723
|
return typeof value === "string" ? value.slice(0, maxLength) : "";
|
|
6715
6724
|
}
|
|
@@ -6718,14 +6727,13 @@ function finiteBytes(value) {
|
|
|
6718
6727
|
}
|
|
6719
6728
|
function safeFileName(value) {
|
|
6720
6729
|
const candidate = boundedString(value, MAX_FILE_NAME_LENGTH).replace(/[\u0000-\u001f\u007f]/g, "").trim();
|
|
6721
|
-
const leaf =
|
|
6730
|
+
const leaf = candidate.split(/[\\/]/).at(-1) ?? "";
|
|
6722
6731
|
return leaf && leaf !== "." && leaf !== ".." ? leaf : "download";
|
|
6723
6732
|
}
|
|
6724
6733
|
var DownloadController = class {
|
|
6725
6734
|
constructor(transport, artifacts, options = {}) {
|
|
6726
6735
|
this.transport = transport;
|
|
6727
6736
|
this.artifacts = artifacts;
|
|
6728
|
-
this.directory = resolve(options.directory ?? DOWNLOAD_DIR);
|
|
6729
6737
|
this.maxDownloadBytes = options.maxDownloadBytes ?? artifacts.maxItemBytes;
|
|
6730
6738
|
this.maxActivePerSession = options.maxActivePerSession ?? 8;
|
|
6731
6739
|
this.maxActivePerWorkspace = options.maxActivePerWorkspace ?? 32;
|
|
@@ -6737,102 +6745,87 @@ var DownloadController = class {
|
|
|
6737
6745
|
this.maxActivePerWorkspace,
|
|
6738
6746
|
this.maxActiveTotal
|
|
6739
6747
|
].some((value) => !Number.isSafeInteger(value) || value <= 0) || this.maxDownloadBytes > artifacts.maxItemBytes) {
|
|
6740
|
-
throw new Error("Invalid
|
|
6748
|
+
throw new Error("Invalid DownloadController limit");
|
|
6741
6749
|
}
|
|
6742
6750
|
this.installHandlers();
|
|
6743
6751
|
}
|
|
6744
6752
|
sessions = /* @__PURE__ */ new Map();
|
|
6745
6753
|
attachments = /* @__PURE__ */ new Map();
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
directory;
|
|
6754
|
+
downloadsByGuid = /* @__PURE__ */ new Map();
|
|
6755
|
+
configurations = /* @__PURE__ */ new Map();
|
|
6749
6756
|
maxDownloadBytes;
|
|
6750
6757
|
maxActivePerSession;
|
|
6751
6758
|
maxActivePerWorkspace;
|
|
6752
6759
|
maxActiveTotal;
|
|
6753
6760
|
publishEvent;
|
|
6754
|
-
|
|
6761
|
+
latestConfigurationGeneration = 0;
|
|
6755
6762
|
attachSession(context) {
|
|
6756
|
-
if (this.sessions.has(context.sessionId)) {
|
|
6757
|
-
return Promise.resolve(this.sessions.get(context.sessionId).available);
|
|
6758
|
-
}
|
|
6759
6763
|
const pending = this.attachments.get(context.sessionId);
|
|
6760
6764
|
if (pending) return pending;
|
|
6765
|
+
const existing = this.sessions.get(context.sessionId);
|
|
6766
|
+
if (existing) return Promise.resolve(existing.available);
|
|
6761
6767
|
const attachment = this.configureSession(context).finally(() => {
|
|
6762
6768
|
if (this.attachments.get(context.sessionId) === attachment) {
|
|
6763
6769
|
this.attachments.delete(context.sessionId);
|
|
6764
6770
|
}
|
|
6765
|
-
this.attachmentContexts.delete(context.sessionId);
|
|
6766
|
-
this.cancelledAttachments.delete(context.sessionId);
|
|
6767
6771
|
});
|
|
6768
6772
|
this.attachments.set(context.sessionId, attachment);
|
|
6769
|
-
this.attachmentContexts.set(context.sessionId, { ...context });
|
|
6770
6773
|
return attachment;
|
|
6771
6774
|
}
|
|
6772
6775
|
async configureSession(context) {
|
|
6773
|
-
const directory = join4(this.directory, randomUUID6());
|
|
6774
6776
|
const session = {
|
|
6775
6777
|
context: { ...context },
|
|
6776
|
-
directory,
|
|
6777
6778
|
available: false,
|
|
6778
6779
|
closed: false,
|
|
6779
6780
|
downloads: /* @__PURE__ */ new Map()
|
|
6780
6781
|
};
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
});
|
|
6787
|
-
stagingReady = true;
|
|
6788
|
-
if (this.cancelledAttachments.has(context.sessionId)) {
|
|
6789
|
-
await rm(directory, { recursive: true, force: true }).catch(() => {
|
|
6790
|
-
});
|
|
6791
|
-
return false;
|
|
6792
|
-
}
|
|
6793
|
-
this.sessions.set(context.sessionId, session);
|
|
6794
|
-
await this.transport.send("Page.setDownloadBehavior", {
|
|
6795
|
-
behavior: "allowAndName",
|
|
6796
|
-
downloadPath: directory,
|
|
6797
|
-
eventsEnabled: true
|
|
6798
|
-
}, context.sessionId);
|
|
6799
|
-
if (session.closed || this.sessions.get(context.sessionId) !== session) {
|
|
6800
|
-
await rm(directory, { recursive: true, force: true }).catch(() => {
|
|
6801
|
-
});
|
|
6802
|
-
return false;
|
|
6803
|
-
}
|
|
6804
|
-
session.available = true;
|
|
6805
|
-
return true;
|
|
6806
|
-
} catch {
|
|
6807
|
-
this.sessions.delete(context.sessionId);
|
|
6808
|
-
session.closed = true;
|
|
6809
|
-
await rm(directory, { recursive: true, force: true }).catch(() => {
|
|
6810
|
-
});
|
|
6782
|
+
this.sessions.set(context.sessionId, session);
|
|
6783
|
+
const available = await this.enableBrowserDownloadEvents(context);
|
|
6784
|
+
if (session.closed || this.sessions.get(context.sessionId) !== session) return false;
|
|
6785
|
+
session.available = available;
|
|
6786
|
+
if (!available) {
|
|
6811
6787
|
this.publish(session, {
|
|
6812
6788
|
state: "capture_unavailable",
|
|
6813
|
-
reason:
|
|
6789
|
+
reason: "browser_download_events_unavailable"
|
|
6814
6790
|
});
|
|
6815
|
-
return false;
|
|
6816
6791
|
}
|
|
6792
|
+
return available;
|
|
6793
|
+
}
|
|
6794
|
+
enableBrowserDownloadEvents(context) {
|
|
6795
|
+
if (context.browserConnectionGeneration > this.latestConfigurationGeneration) {
|
|
6796
|
+
this.latestConfigurationGeneration = context.browserConnectionGeneration;
|
|
6797
|
+
for (const generation of this.configurations.keys()) {
|
|
6798
|
+
if (generation < context.browserConnectionGeneration) this.configurations.delete(generation);
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
let contexts = this.configurations.get(context.browserConnectionGeneration);
|
|
6802
|
+
if (!contexts) {
|
|
6803
|
+
contexts = /* @__PURE__ */ new Map();
|
|
6804
|
+
this.configurations.set(context.browserConnectionGeneration, contexts);
|
|
6805
|
+
}
|
|
6806
|
+
const key = context.cdpBrowserContextId ?? "";
|
|
6807
|
+
const existing = contexts.get(key);
|
|
6808
|
+
if (existing) return existing;
|
|
6809
|
+
const configured = this.transport.send("Browser.setDownloadBehavior", {
|
|
6810
|
+
behavior: "default",
|
|
6811
|
+
eventsEnabled: true,
|
|
6812
|
+
...context.cdpBrowserContextId ? { browserContextId: context.cdpBrowserContextId } : {}
|
|
6813
|
+
}).then(() => true, () => {
|
|
6814
|
+
if (contexts?.get(key) === configured) contexts.delete(key);
|
|
6815
|
+
return false;
|
|
6816
|
+
});
|
|
6817
|
+
contexts.set(key, configured);
|
|
6818
|
+
return configured;
|
|
6817
6819
|
}
|
|
6818
6820
|
detachSession(sessionId, reason2) {
|
|
6819
|
-
if (this.attachments.has(sessionId)) this.cancelledAttachments.add(sessionId);
|
|
6820
6821
|
const session = this.sessions.get(sessionId);
|
|
6821
6822
|
if (!session) return;
|
|
6822
6823
|
this.sessions.delete(sessionId);
|
|
6823
6824
|
session.closed = true;
|
|
6824
|
-
|
|
6825
|
-
void this.transport.send("Page.setDownloadBehavior", {
|
|
6826
|
-
behavior: "default",
|
|
6827
|
-
eventsEnabled: false
|
|
6828
|
-
}, sessionId).catch(() => {
|
|
6829
|
-
});
|
|
6830
|
-
}
|
|
6831
|
-
for (const download of session.downloads.values()) {
|
|
6825
|
+
for (const download of [...session.downloads.values()]) {
|
|
6832
6826
|
if (download.state === "terminal") continue;
|
|
6833
6827
|
download.state = "terminal";
|
|
6834
|
-
|
|
6835
|
-
});
|
|
6828
|
+
this.removeBinding(session, download);
|
|
6836
6829
|
this.publish(session, {
|
|
6837
6830
|
downloadId: download.id,
|
|
6838
6831
|
state: "cancelled",
|
|
@@ -6841,21 +6834,13 @@ var DownloadController = class {
|
|
|
6841
6834
|
...download.totalBytes !== void 0 ? { totalBytes: download.totalBytes } : {}
|
|
6842
6835
|
});
|
|
6843
6836
|
}
|
|
6844
|
-
void rm(session.directory, { recursive: true, force: true }).catch(() => {
|
|
6845
|
-
});
|
|
6846
6837
|
}
|
|
6847
6838
|
releaseLease(leaseId) {
|
|
6848
|
-
for (const context of [...this.attachmentContexts.values()]) {
|
|
6849
|
-
if (context.leaseId === leaseId) this.detachSession(context.sessionId, "lease_released");
|
|
6850
|
-
}
|
|
6851
6839
|
for (const session of [...this.sessions.values()]) {
|
|
6852
6840
|
if (session.context.leaseId === leaseId) this.detachSession(session.context.sessionId, "lease_released");
|
|
6853
6841
|
}
|
|
6854
6842
|
}
|
|
6855
6843
|
releaseWorkspace(workspaceId) {
|
|
6856
|
-
for (const context of [...this.attachmentContexts.values()]) {
|
|
6857
|
-
if (context.workspaceId === workspaceId) this.detachSession(context.sessionId, "workspace_released");
|
|
6858
|
-
}
|
|
6859
6844
|
for (const session of [...this.sessions.values()]) {
|
|
6860
6845
|
if (session.context.workspaceId === workspaceId) {
|
|
6861
6846
|
this.detachSession(session.context.sessionId, "workspace_released");
|
|
@@ -6868,13 +6853,20 @@ var DownloadController = class {
|
|
|
6868
6853
|
const session = this.sessions.get(sessionId);
|
|
6869
6854
|
const guid = boundedString(params?.guid, 512);
|
|
6870
6855
|
if (!session || !session.available || session.closed || !/^[A-Za-z0-9._-]{1,512}$/.test(guid)) return;
|
|
6871
|
-
|
|
6872
|
-
if (
|
|
6873
|
-
|
|
6874
|
-
|
|
6856
|
+
const collision = this.downloadsByGuid.get(guid);
|
|
6857
|
+
if (collision) {
|
|
6858
|
+
if (collision.session === session) return;
|
|
6859
|
+
this.failDownload(collision.session, collision.download, "download_identity_collision");
|
|
6860
|
+
this.publish(session, {
|
|
6861
|
+
downloadId: `download:${randomUUID6()}`,
|
|
6862
|
+
state: "failed",
|
|
6863
|
+
reason: "download_identity_collision"
|
|
6875
6864
|
});
|
|
6865
|
+
return;
|
|
6866
|
+
}
|
|
6867
|
+
if (!this.hasDownloadCapacity(session)) {
|
|
6876
6868
|
this.publish(session, {
|
|
6877
|
-
downloadId
|
|
6869
|
+
downloadId: `download:${randomUUID6()}`,
|
|
6878
6870
|
state: "failed",
|
|
6879
6871
|
reason: "concurrency_limit_exceeded"
|
|
6880
6872
|
});
|
|
@@ -6883,13 +6875,13 @@ var DownloadController = class {
|
|
|
6883
6875
|
const download = {
|
|
6884
6876
|
id: `download:${randomUUID6()}`,
|
|
6885
6877
|
guid,
|
|
6886
|
-
path: join4(session.directory, guid),
|
|
6887
6878
|
url: boundedString(params?.url, MAX_EVENT_URL_LENGTH),
|
|
6888
6879
|
fileName: safeFileName(params?.suggestedFilename),
|
|
6889
6880
|
receivedBytes: 0,
|
|
6890
6881
|
state: "active"
|
|
6891
6882
|
};
|
|
6892
6883
|
session.downloads.set(guid, download);
|
|
6884
|
+
this.downloadsByGuid.set(guid, { session, download });
|
|
6893
6885
|
this.publish(session, {
|
|
6894
6886
|
downloadId: download.id,
|
|
6895
6887
|
state: "started",
|
|
@@ -6899,63 +6891,49 @@ var DownloadController = class {
|
|
|
6899
6891
|
});
|
|
6900
6892
|
this.transport.on?.("Page.downloadProgress", (params, sessionId) => {
|
|
6901
6893
|
if (!sessionId) return;
|
|
6902
|
-
const
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
if (
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
if (totalBytes !== void 0) download.totalBytes = totalBytes;
|
|
6910
|
-
if (download.state === "terminal") {
|
|
6911
|
-
if (params?.state === "completed" || params?.state === "canceled") {
|
|
6912
|
-
session.downloads.delete(download.guid);
|
|
6913
|
-
void unlink(download.path).catch(() => {
|
|
6914
|
-
});
|
|
6915
|
-
}
|
|
6916
|
-
return;
|
|
6917
|
-
}
|
|
6918
|
-
if (download.receivedBytes > this.maxDownloadBytes || download.totalBytes !== void 0 && download.totalBytes > this.maxDownloadBytes) {
|
|
6919
|
-
this.failOversized(session, download);
|
|
6920
|
-
return;
|
|
6894
|
+
const binding = this.downloadsByGuid.get(boundedString(params?.guid, 512));
|
|
6895
|
+
if (!binding || binding.session.context.sessionId !== sessionId) return;
|
|
6896
|
+
this.updateBytes(binding.download, params);
|
|
6897
|
+
if (this.isOversized(binding.download)) {
|
|
6898
|
+
this.failOversized(binding.session, binding.download);
|
|
6899
|
+
} else if (params?.state === "canceled") {
|
|
6900
|
+
this.cancelDownload(binding.session, binding.download, "browser_cancelled");
|
|
6921
6901
|
}
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6902
|
+
});
|
|
6903
|
+
this.transport.on?.("Browser.downloadProgress", (params) => {
|
|
6904
|
+
const binding = this.downloadsByGuid.get(boundedString(params?.guid, 512));
|
|
6905
|
+
if (!binding) return;
|
|
6906
|
+
this.updateBytes(binding.download, params);
|
|
6907
|
+
if (this.isOversized(binding.download)) {
|
|
6908
|
+
this.failOversized(binding.session, binding.download);
|
|
6927
6909
|
return;
|
|
6928
6910
|
}
|
|
6929
|
-
if (params?.state === "completed" && download.state === "active") {
|
|
6930
|
-
download.state = "finalizing";
|
|
6931
|
-
void this.finalize(session, download);
|
|
6911
|
+
if (params?.state === "completed" && binding.download.state === "active") {
|
|
6912
|
+
binding.download.state = "finalizing";
|
|
6913
|
+
void this.finalize(binding.session, binding.download, params?.filePath);
|
|
6932
6914
|
} else if (params?.state === "canceled") {
|
|
6933
|
-
|
|
6934
|
-
this.publish(session, {
|
|
6935
|
-
downloadId: download.id,
|
|
6936
|
-
state: "cancelled",
|
|
6937
|
-
reason: "browser_cancelled",
|
|
6938
|
-
receivedBytes: download.receivedBytes,
|
|
6939
|
-
...download.totalBytes !== void 0 ? { totalBytes: download.totalBytes } : {}
|
|
6940
|
-
});
|
|
6941
|
-
void unlink(download.path).catch(() => {
|
|
6942
|
-
});
|
|
6943
|
-
session.downloads.delete(download.guid);
|
|
6915
|
+
this.cancelDownload(binding.session, binding.download, "browser_cancelled");
|
|
6944
6916
|
}
|
|
6945
6917
|
});
|
|
6946
6918
|
}
|
|
6919
|
+
updateBytes(download, params) {
|
|
6920
|
+
const receivedBytes = finiteBytes(params?.receivedBytes);
|
|
6921
|
+
const totalBytes = finiteBytes(params?.totalBytes);
|
|
6922
|
+
if (receivedBytes !== void 0) download.receivedBytes = receivedBytes;
|
|
6923
|
+
if (totalBytes !== void 0) download.totalBytes = totalBytes;
|
|
6924
|
+
}
|
|
6925
|
+
isOversized(download) {
|
|
6926
|
+
return download.receivedBytes > this.maxDownloadBytes || download.totalBytes !== void 0 && download.totalBytes > this.maxDownloadBytes;
|
|
6927
|
+
}
|
|
6947
6928
|
failOversized(session, download) {
|
|
6948
6929
|
this.failDownload(session, download, "size_limit_exceeded", {
|
|
6949
6930
|
maxDownloadBytes: this.maxDownloadBytes
|
|
6950
6931
|
});
|
|
6951
6932
|
}
|
|
6952
|
-
failDownload(session, download, reason2, limits) {
|
|
6933
|
+
failDownload(session, download, reason2, limits = {}) {
|
|
6953
6934
|
if (download.state === "terminal") return;
|
|
6954
6935
|
download.state = "terminal";
|
|
6955
|
-
|
|
6956
|
-
});
|
|
6957
|
-
void unlink(download.path).catch(() => {
|
|
6958
|
-
});
|
|
6936
|
+
this.removeBinding(session, download);
|
|
6959
6937
|
this.publish(session, {
|
|
6960
6938
|
downloadId: download.id,
|
|
6961
6939
|
state: "failed",
|
|
@@ -6965,48 +6943,77 @@ var DownloadController = class {
|
|
|
6965
6943
|
...limits
|
|
6966
6944
|
});
|
|
6967
6945
|
}
|
|
6968
|
-
|
|
6946
|
+
cancelDownload(session, download, reason2) {
|
|
6947
|
+
if (download.state === "terminal") return;
|
|
6948
|
+
download.state = "terminal";
|
|
6949
|
+
this.removeBinding(session, download);
|
|
6950
|
+
this.publish(session, {
|
|
6951
|
+
downloadId: download.id,
|
|
6952
|
+
state: "cancelled",
|
|
6953
|
+
reason: reason2,
|
|
6954
|
+
receivedBytes: download.receivedBytes,
|
|
6955
|
+
...download.totalBytes !== void 0 ? { totalBytes: download.totalBytes } : {}
|
|
6956
|
+
});
|
|
6957
|
+
}
|
|
6958
|
+
async finalize(session, download, rawFilePath) {
|
|
6969
6959
|
try {
|
|
6970
|
-
const
|
|
6971
|
-
if (
|
|
6972
|
-
|
|
6973
|
-
if (!info.isFile()) throw new Error("download_not_regular_file");
|
|
6974
|
-
if (info.size > this.maxDownloadBytes) {
|
|
6975
|
-
download.receivedBytes = info.size;
|
|
6960
|
+
const completedFile = await this.validateCompletedFile(rawFilePath);
|
|
6961
|
+
if (completedFile.size > this.maxDownloadBytes) {
|
|
6962
|
+
download.receivedBytes = completedFile.size;
|
|
6976
6963
|
this.failOversized(session, download);
|
|
6977
6964
|
return;
|
|
6978
6965
|
}
|
|
6979
|
-
|
|
6966
|
+
if (!this.isTracked(session, download)) return;
|
|
6967
|
+
const record = await this.artifacts.ingestDownloadCopy(
|
|
6980
6968
|
session.context.workspaceId,
|
|
6981
|
-
|
|
6969
|
+
completedFile.path,
|
|
6982
6970
|
download.fileName
|
|
6983
6971
|
);
|
|
6984
|
-
if (session
|
|
6972
|
+
if (!this.isTracked(session, download)) {
|
|
6985
6973
|
await this.artifacts.release(session.context.workspaceId, record.descriptor.id);
|
|
6986
6974
|
return;
|
|
6987
6975
|
}
|
|
6988
6976
|
download.state = "terminal";
|
|
6977
|
+
this.removeBinding(session, download);
|
|
6989
6978
|
this.publish(session, {
|
|
6990
6979
|
downloadId: download.id,
|
|
6991
6980
|
state: "completed",
|
|
6992
6981
|
artifact: record.descriptor
|
|
6993
6982
|
});
|
|
6994
|
-
session.downloads.delete(download.guid);
|
|
6995
6983
|
} catch (error) {
|
|
6996
6984
|
if (download.state === "terminal") return;
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
6985
|
+
const code = error instanceof DownloadFinalizeError ? error.code : error instanceof Error && "code" in error && error.code === "result_too_large" ? "artifact_quota_exceeded" : error instanceof Error && "code" in error && error.code === "invalid_argument" ? "download_file_unavailable" : "artifact_ingest_failed";
|
|
6986
|
+
this.failDownload(session, download, code);
|
|
6987
|
+
}
|
|
6988
|
+
}
|
|
6989
|
+
async validateCompletedFile(rawFilePath) {
|
|
6990
|
+
if (typeof rawFilePath !== "string" || rawFilePath.length === 0) {
|
|
6991
|
+
throw new DownloadFinalizeError("download_file_path_unavailable");
|
|
6992
|
+
}
|
|
6993
|
+
if (rawFilePath.length > MAX_FILE_PATH_LENGTH || rawFilePath.includes("\0") || !isAbsolute3(rawFilePath)) {
|
|
6994
|
+
throw new DownloadFinalizeError("download_file_path_invalid");
|
|
6995
|
+
}
|
|
6996
|
+
try {
|
|
6997
|
+
const info = await lstat(rawFilePath);
|
|
6998
|
+
if (info.isSymbolicLink() || !info.isFile()) {
|
|
6999
|
+
throw new DownloadFinalizeError("download_not_regular_file");
|
|
7000
|
+
}
|
|
7001
|
+
return { path: rawFilePath, size: info.size };
|
|
7002
|
+
} catch (error) {
|
|
7003
|
+
if (error instanceof DownloadFinalizeError) throw error;
|
|
7004
|
+
throw new DownloadFinalizeError("download_file_unavailable");
|
|
7005
|
+
}
|
|
7006
|
+
}
|
|
7007
|
+
removeBinding(session, download) {
|
|
7008
|
+
if (session.downloads.get(download.guid) === download) session.downloads.delete(download.guid);
|
|
7009
|
+
const binding = this.downloadsByGuid.get(download.guid);
|
|
7010
|
+
if (binding?.session === session && binding.download === download) {
|
|
7011
|
+
this.downloadsByGuid.delete(download.guid);
|
|
7008
7012
|
}
|
|
7009
7013
|
}
|
|
7014
|
+
isTracked(session, download) {
|
|
7015
|
+
return !session.closed && session.downloads.get(download.guid) === download && this.downloadsByGuid.get(download.guid)?.download === download;
|
|
7016
|
+
}
|
|
7010
7017
|
publish(session, payload) {
|
|
7011
7018
|
if (!this.publishEvent) return;
|
|
7012
7019
|
const hint = this.agentHint(payload);
|
|
@@ -7044,44 +7051,12 @@ var DownloadController = class {
|
|
|
7044
7051
|
return void 0;
|
|
7045
7052
|
}
|
|
7046
7053
|
hasDownloadCapacity(session) {
|
|
7047
|
-
const
|
|
7048
|
-
const sessionCount = [...session.downloads.values()].filter(active).length;
|
|
7054
|
+
const sessionCount = session.downloads.size;
|
|
7049
7055
|
let workspaceCount = 0;
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
for (const download of candidate.downloads.values()) {
|
|
7053
|
-
if (!active(download)) continue;
|
|
7054
|
-
totalCount += 1;
|
|
7055
|
-
if (candidate.context.workspaceId === session.context.workspaceId) workspaceCount += 1;
|
|
7056
|
-
}
|
|
7057
|
-
}
|
|
7058
|
-
return sessionCount < this.maxActivePerSession && workspaceCount < this.maxActivePerWorkspace && totalCount < this.maxActiveTotal;
|
|
7059
|
-
}
|
|
7060
|
-
exceedsStagingQuota(currentSession) {
|
|
7061
|
-
let workspaceBytes = 0;
|
|
7062
|
-
let totalBytes = 0;
|
|
7063
|
-
for (const session of this.sessions.values()) {
|
|
7064
|
-
for (const download of session.downloads.values()) {
|
|
7065
|
-
if (download.state === "terminal") continue;
|
|
7066
|
-
const bytes = Math.max(download.receivedBytes, download.totalBytes ?? 0);
|
|
7067
|
-
totalBytes += bytes;
|
|
7068
|
-
if (session.context.workspaceId === currentSession.context.workspaceId) workspaceBytes += bytes;
|
|
7069
|
-
}
|
|
7056
|
+
for (const binding of this.downloadsByGuid.values()) {
|
|
7057
|
+
if (binding.session.context.workspaceId === session.context.workspaceId) workspaceCount += 1;
|
|
7070
7058
|
}
|
|
7071
|
-
return
|
|
7072
|
-
}
|
|
7073
|
-
initialize() {
|
|
7074
|
-
if (!this.initializePromise) {
|
|
7075
|
-
this.initializePromise = (async () => {
|
|
7076
|
-
await mkdir(this.directory, { recursive: true, mode: 448 });
|
|
7077
|
-
await chmod(this.directory, 448).catch(() => {
|
|
7078
|
-
});
|
|
7079
|
-
const entries = await readdir(this.directory, { withFileTypes: true });
|
|
7080
|
-
await Promise.all(entries.map((entry) => rm(join4(this.directory, entry.name), { recursive: true, force: true }).catch(() => {
|
|
7081
|
-
})));
|
|
7082
|
-
})();
|
|
7083
|
-
}
|
|
7084
|
-
return this.initializePromise;
|
|
7059
|
+
return sessionCount < this.maxActivePerSession && workspaceCount < this.maxActivePerWorkspace && this.downloadsByGuid.size < this.maxActiveTotal;
|
|
7085
7060
|
}
|
|
7086
7061
|
};
|
|
7087
7062
|
|
|
@@ -9511,7 +9486,7 @@ var TargetInventoryService = class {
|
|
|
9511
9486
|
};
|
|
9512
9487
|
|
|
9513
9488
|
// src/services/upload-service.ts
|
|
9514
|
-
import { basename as
|
|
9489
|
+
import { basename as basename2 } from "path";
|
|
9515
9490
|
var FILE_INPUT_BLOCK_REASONS = /* @__PURE__ */ new Set([
|
|
9516
9491
|
"detached",
|
|
9517
9492
|
"disabled",
|
|
@@ -9634,7 +9609,7 @@ var UploadService = class {
|
|
|
9634
9609
|
backendNodeId
|
|
9635
9610
|
}, this.sessionId);
|
|
9636
9611
|
if (this.readbackDelayMs > 0) {
|
|
9637
|
-
await new Promise((
|
|
9612
|
+
await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
|
|
9638
9613
|
}
|
|
9639
9614
|
let after;
|
|
9640
9615
|
try {
|
|
@@ -9656,7 +9631,7 @@ var UploadService = class {
|
|
|
9656
9631
|
reason: "target_unavailable"
|
|
9657
9632
|
};
|
|
9658
9633
|
}
|
|
9659
|
-
const nameMatched = after.firstFileName ===
|
|
9634
|
+
const nameMatched = after.firstFileName === basename2(filePath);
|
|
9660
9635
|
const status = after.fileCount === 1 && nameMatched ? "verified" : "mismatch";
|
|
9661
9636
|
const reason2 = after.fileCount !== 1 ? "file_count_mismatch" : !nameMatched ? "file_name_mismatch" : void 0;
|
|
9662
9637
|
return {
|
|
@@ -11759,7 +11734,7 @@ var BrowserToolService = class {
|
|
|
11759
11734
|
if (Array.isArray(current?.targetInfos) && !current.targetInfos.some((target) => target?.targetId === targetId)) return true;
|
|
11760
11735
|
} catch {
|
|
11761
11736
|
}
|
|
11762
|
-
if (attempt < 19) await new Promise((
|
|
11737
|
+
if (attempt < 19) await new Promise((resolve2) => setTimeout(resolve2, 50));
|
|
11763
11738
|
}
|
|
11764
11739
|
return false;
|
|
11765
11740
|
}
|
|
@@ -11907,7 +11882,7 @@ var BrowserToolService = class {
|
|
|
11907
11882
|
candidateTargetId = candidate.targetId;
|
|
11908
11883
|
break;
|
|
11909
11884
|
}
|
|
11910
|
-
await new Promise((
|
|
11885
|
+
await new Promise((resolve2) => setTimeout(resolve2, 50));
|
|
11911
11886
|
}
|
|
11912
11887
|
if (!candidateTargetId) {
|
|
11913
11888
|
throw this.managedTargetUnknownOutcome(profile);
|
|
@@ -11946,7 +11921,7 @@ var BrowserToolService = class {
|
|
|
11946
11921
|
if (result?.result?.value !== void 0) return false;
|
|
11947
11922
|
} catch {
|
|
11948
11923
|
}
|
|
11949
|
-
if (attempt < 9) await new Promise((
|
|
11924
|
+
if (attempt < 9) await new Promise((resolve2) => setTimeout(resolve2, 25));
|
|
11950
11925
|
}
|
|
11951
11926
|
return false;
|
|
11952
11927
|
} catch {
|
|
@@ -11994,13 +11969,15 @@ var BrowserToolService = class {
|
|
|
11994
11969
|
if (typeof attached?.sessionId !== "string") {
|
|
11995
11970
|
throw new BrowserPilotError("internal_error", "Chrome returned an invalid CDP session");
|
|
11996
11971
|
}
|
|
11972
|
+
const profile = this.profileContexts.forTarget(cdpTargetId, context.browserConnectionGeneration);
|
|
11997
11973
|
const session = {
|
|
11998
11974
|
workspaceId: context.workspaceId,
|
|
11999
11975
|
leaseId: context.leaseId,
|
|
12000
11976
|
targetId,
|
|
12001
11977
|
browserConnectionGeneration: context.browserConnectionGeneration,
|
|
12002
11978
|
cdpTargetId,
|
|
12003
|
-
sessionId: attached.sessionId
|
|
11979
|
+
sessionId: attached.sessionId,
|
|
11980
|
+
...profile?.cdpBrowserContextId ? { cdpBrowserContextId: profile.cdpBrowserContextId } : {}
|
|
12004
11981
|
};
|
|
12005
11982
|
this.sessions.set(key, session);
|
|
12006
11983
|
this.ownedSessionIds.add(attached.sessionId);
|
|
@@ -13183,19 +13160,19 @@ var BrowserToolRouter = class {
|
|
|
13183
13160
|
// src/services/artifact-store.ts
|
|
13184
13161
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
13185
13162
|
import {
|
|
13186
|
-
chmod
|
|
13163
|
+
chmod,
|
|
13187
13164
|
copyFile,
|
|
13188
13165
|
lstat as lstat2,
|
|
13189
|
-
mkdir
|
|
13166
|
+
mkdir,
|
|
13190
13167
|
realpath,
|
|
13191
|
-
readdir
|
|
13192
|
-
rm
|
|
13193
|
-
stat
|
|
13194
|
-
unlink
|
|
13168
|
+
readdir,
|
|
13169
|
+
rm,
|
|
13170
|
+
stat,
|
|
13171
|
+
unlink,
|
|
13195
13172
|
writeFile
|
|
13196
13173
|
} from "fs/promises";
|
|
13197
13174
|
import { constants as fsConstants } from "fs";
|
|
13198
|
-
import { basename as
|
|
13175
|
+
import { basename as basename3, dirname as dirname2, extname, isAbsolute as isAbsolute4, join as join4, relative, resolve, sep } from "path";
|
|
13199
13176
|
var ARTIFACT_ID_PATTERN2 = /^artifact:[A-Za-z0-9][A-Za-z0-9._:-]{0,118}$/;
|
|
13200
13177
|
function clone2(record) {
|
|
13201
13178
|
return { descriptor: { ...record.descriptor }, path: record.path };
|
|
@@ -13233,7 +13210,7 @@ var ArtifactStore = class {
|
|
|
13233
13210
|
initialized = false;
|
|
13234
13211
|
mutationTail = Promise.resolve();
|
|
13235
13212
|
constructor(options = {}) {
|
|
13236
|
-
this.directory =
|
|
13213
|
+
this.directory = resolve(options.directory ?? ARTIFACT_DIR);
|
|
13237
13214
|
this.ttlMs = options.ttlMs ?? 15 * 6e4;
|
|
13238
13215
|
this.retainedTtlMs = options.retainedTtlMs ?? 24 * 60 * 6e4;
|
|
13239
13216
|
this.maxArtifactBytes = options.maxArtifactBytes ?? 100 * 1024 * 1024;
|
|
@@ -13269,10 +13246,10 @@ var ArtifactStore = class {
|
|
|
13269
13246
|
kind: "upload_input",
|
|
13270
13247
|
sensitivity: "user_file",
|
|
13271
13248
|
mimeType,
|
|
13272
|
-
|
|
13249
|
+
rejectSymbolicLink: false
|
|
13273
13250
|
});
|
|
13274
13251
|
}
|
|
13275
|
-
async
|
|
13252
|
+
async ingestDownloadCopy(workspaceId, sourcePath, fileName, mimeType) {
|
|
13276
13253
|
return this.ingestFile({
|
|
13277
13254
|
workspaceId,
|
|
13278
13255
|
sourcePath,
|
|
@@ -13280,7 +13257,7 @@ var ArtifactStore = class {
|
|
|
13280
13257
|
sensitivity: "user_file",
|
|
13281
13258
|
fileName,
|
|
13282
13259
|
mimeType,
|
|
13283
|
-
|
|
13260
|
+
rejectSymbolicLink: true
|
|
13284
13261
|
});
|
|
13285
13262
|
}
|
|
13286
13263
|
async initialize() {
|
|
@@ -13290,8 +13267,8 @@ var ArtifactStore = class {
|
|
|
13290
13267
|
return this.withLock(async () => clone2(await this.requireRecordUnlocked(workspaceId, artifactId)));
|
|
13291
13268
|
}
|
|
13292
13269
|
async export(workspaceId, artifactId, destination, overwrite = false) {
|
|
13293
|
-
if (!
|
|
13294
|
-
const target =
|
|
13270
|
+
if (!isAbsolute4(destination)) throw invalidArgument("Artifact export path must be absolute", "path");
|
|
13271
|
+
const target = resolve(destination);
|
|
13295
13272
|
if (this.isInsideStore(target)) {
|
|
13296
13273
|
throw invalidArgument("Artifact export path must be outside Broker storage", "path");
|
|
13297
13274
|
}
|
|
@@ -13308,7 +13285,7 @@ var ArtifactStore = class {
|
|
|
13308
13285
|
});
|
|
13309
13286
|
}
|
|
13310
13287
|
const canonicalStore = await realpath(this.directory);
|
|
13311
|
-
if (this.isWithin(canonicalStore,
|
|
13288
|
+
if (this.isWithin(canonicalStore, join4(canonicalParent, basename3(target)))) {
|
|
13312
13289
|
throw invalidArgument("Artifact export path must be outside Broker storage", "path");
|
|
13313
13290
|
}
|
|
13314
13291
|
if (overwrite) {
|
|
@@ -13328,7 +13305,7 @@ var ArtifactStore = class {
|
|
|
13328
13305
|
cause
|
|
13329
13306
|
});
|
|
13330
13307
|
});
|
|
13331
|
-
await
|
|
13308
|
+
await chmod(target, 384).catch(() => {
|
|
13332
13309
|
});
|
|
13333
13310
|
return { artifact: { ...record.descriptor }, path: target };
|
|
13334
13311
|
});
|
|
@@ -13385,8 +13362,8 @@ var ArtifactStore = class {
|
|
|
13385
13362
|
return this.maxTotalBytes;
|
|
13386
13363
|
}
|
|
13387
13364
|
async ingestFile(input) {
|
|
13388
|
-
if (!
|
|
13389
|
-
const source =
|
|
13365
|
+
if (!isAbsolute4(input.sourcePath)) throw invalidArgument("Artifact import path must be absolute", "path");
|
|
13366
|
+
const source = resolve(input.sourcePath);
|
|
13390
13367
|
if (this.isInsideStore(source)) {
|
|
13391
13368
|
throw invalidArgument("Artifact import path must be outside Broker storage", "path");
|
|
13392
13369
|
}
|
|
@@ -13394,6 +13371,21 @@ var ArtifactStore = class {
|
|
|
13394
13371
|
await this.sweepUnlocked();
|
|
13395
13372
|
await this.ensureDirectory();
|
|
13396
13373
|
const canonicalStore = await realpath(this.directory);
|
|
13374
|
+
if (input.rejectSymbolicLink) {
|
|
13375
|
+
let sourceLinkInfo;
|
|
13376
|
+
try {
|
|
13377
|
+
sourceLinkInfo = await lstat2(source);
|
|
13378
|
+
} catch (cause) {
|
|
13379
|
+
throw new BrowserPilotError("invalid_argument", "Artifact import file is not accessible", {
|
|
13380
|
+
context: { field: "path" },
|
|
13381
|
+
rpcCode: -32602,
|
|
13382
|
+
cause
|
|
13383
|
+
});
|
|
13384
|
+
}
|
|
13385
|
+
if (sourceLinkInfo.isSymbolicLink() || !sourceLinkInfo.isFile()) {
|
|
13386
|
+
throw invalidArgument("Artifact import path must identify a regular file", "path");
|
|
13387
|
+
}
|
|
13388
|
+
}
|
|
13397
13389
|
let canonicalSource;
|
|
13398
13390
|
try {
|
|
13399
13391
|
canonicalSource = await realpath(source);
|
|
@@ -13409,7 +13401,7 @@ var ArtifactStore = class {
|
|
|
13409
13401
|
}
|
|
13410
13402
|
let sourceInfo;
|
|
13411
13403
|
try {
|
|
13412
|
-
sourceInfo = await
|
|
13404
|
+
sourceInfo = await stat(canonicalSource);
|
|
13413
13405
|
} catch (cause) {
|
|
13414
13406
|
throw new BrowserPilotError("invalid_argument", "Artifact import file is not accessible", {
|
|
13415
13407
|
context: { field: "path" },
|
|
@@ -13419,8 +13411,8 @@ var ArtifactStore = class {
|
|
|
13419
13411
|
}
|
|
13420
13412
|
if (!sourceInfo.isFile()) throw invalidArgument("Artifact import path must identify a regular file", "path");
|
|
13421
13413
|
this.assertQuota(input.workspaceId, sourceInfo.size);
|
|
13422
|
-
const fileName = input.fileName ??
|
|
13423
|
-
if (!fileName || fileName === "." || fileName === ".." ||
|
|
13414
|
+
const fileName = input.fileName ?? basename3(source);
|
|
13415
|
+
if (!fileName || fileName === "." || fileName === ".." || basename3(fileName) !== fileName || fileName.length > 4096 || /[\u0000-\u001f\u007f]/.test(fileName)) {
|
|
13424
13416
|
throw invalidArgument("Artifact import filename is invalid or too long", "path");
|
|
13425
13417
|
}
|
|
13426
13418
|
const resolvedMimeType = input.mimeType ?? inferredMimeType(fileName);
|
|
@@ -13428,14 +13420,14 @@ var ArtifactStore = class {
|
|
|
13428
13420
|
throw invalidArgument("Artifact MIME type is invalid or too long", "mimeType");
|
|
13429
13421
|
}
|
|
13430
13422
|
const id = this.nextArtifactId();
|
|
13431
|
-
const storageDirectory =
|
|
13432
|
-
const destination =
|
|
13433
|
-
await
|
|
13423
|
+
const storageDirectory = join4(this.directory, randomUUID12());
|
|
13424
|
+
const destination = join4(storageDirectory, fileName);
|
|
13425
|
+
await mkdir(storageDirectory, { mode: 448 });
|
|
13434
13426
|
try {
|
|
13435
13427
|
await copyFile(canonicalSource, destination, fsConstants.COPYFILE_EXCL);
|
|
13436
|
-
await
|
|
13428
|
+
await chmod(destination, 384).catch(() => {
|
|
13437
13429
|
});
|
|
13438
|
-
const copied = await
|
|
13430
|
+
const copied = await stat(destination);
|
|
13439
13431
|
if (!copied.isFile() || copied.size !== sourceInfo.size) {
|
|
13440
13432
|
throw new BrowserPilotError("invalid_argument", "Artifact import file changed while it was being copied", {
|
|
13441
13433
|
context: { field: "path" },
|
|
@@ -13457,11 +13449,9 @@ var ArtifactStore = class {
|
|
|
13457
13449
|
};
|
|
13458
13450
|
const record = { descriptor: descriptor2, path: destination };
|
|
13459
13451
|
this.records.set(id, record);
|
|
13460
|
-
if (input.removeSource) await unlink2(canonicalSource).catch(() => {
|
|
13461
|
-
});
|
|
13462
13452
|
return clone2(record);
|
|
13463
13453
|
} catch (error) {
|
|
13464
|
-
await
|
|
13454
|
+
await rm(storageDirectory, { recursive: true, force: true }).catch(() => {
|
|
13465
13455
|
});
|
|
13466
13456
|
throw error;
|
|
13467
13457
|
}
|
|
@@ -13473,10 +13463,10 @@ var ArtifactStore = class {
|
|
|
13473
13463
|
this.assertQuota(input.workspaceId, byteSize);
|
|
13474
13464
|
await this.ensureDirectory();
|
|
13475
13465
|
const id = this.nextArtifactId();
|
|
13476
|
-
const file =
|
|
13466
|
+
const file = join4(this.directory, `${randomUUID12()}.bin`);
|
|
13477
13467
|
const createdAt = this.now();
|
|
13478
13468
|
await writeFile(file, input.bytes, { mode: 384, flag: "wx" });
|
|
13479
|
-
await
|
|
13469
|
+
await chmod(file, 384).catch(() => {
|
|
13480
13470
|
});
|
|
13481
13471
|
const descriptor2 = {
|
|
13482
13472
|
id,
|
|
@@ -13506,7 +13496,7 @@ var ArtifactStore = class {
|
|
|
13506
13496
|
throw this.notFound(artifactId);
|
|
13507
13497
|
}
|
|
13508
13498
|
try {
|
|
13509
|
-
const info = await
|
|
13499
|
+
const info = await stat(record.path);
|
|
13510
13500
|
if (!info.isFile() || info.size !== record.descriptor.byteSize) throw new Error("Artifact file mismatch");
|
|
13511
13501
|
} catch {
|
|
13512
13502
|
this.records.delete(artifactId);
|
|
@@ -13530,15 +13520,15 @@ var ArtifactStore = class {
|
|
|
13530
13520
|
}
|
|
13531
13521
|
async ensureDirectory() {
|
|
13532
13522
|
if (!this.initialized) {
|
|
13533
|
-
await
|
|
13523
|
+
await mkdir(this.directory, { recursive: true, mode: 448 });
|
|
13534
13524
|
if (this.purgeOrphansOnInitialize) {
|
|
13535
|
-
const entries = await
|
|
13536
|
-
await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".bin") || entry.isDirectory()).map((entry) =>
|
|
13525
|
+
const entries = await readdir(this.directory, { withFileTypes: true });
|
|
13526
|
+
await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".bin") || entry.isDirectory()).map((entry) => rm(join4(this.directory, entry.name), { recursive: true, force: true }).catch(() => {
|
|
13537
13527
|
})));
|
|
13538
13528
|
}
|
|
13539
13529
|
this.initialized = true;
|
|
13540
13530
|
}
|
|
13541
|
-
await
|
|
13531
|
+
await chmod(this.directory, 448).catch(() => {
|
|
13542
13532
|
});
|
|
13543
13533
|
}
|
|
13544
13534
|
notFound(artifactId) {
|
|
@@ -13553,11 +13543,11 @@ var ArtifactStore = class {
|
|
|
13553
13543
|
}
|
|
13554
13544
|
isInsideStore(path) {
|
|
13555
13545
|
const child = relative(this.directory, path);
|
|
13556
|
-
return child === "" || child !== ".." && !child.startsWith(`..${sep}`) && !
|
|
13546
|
+
return child === "" || child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute4(child);
|
|
13557
13547
|
}
|
|
13558
13548
|
isWithin(parent, path) {
|
|
13559
13549
|
const child = relative(parent, path);
|
|
13560
|
-
return child === "" || child !== ".." && !child.startsWith(`..${sep}`) && !
|
|
13550
|
+
return child === "" || child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute4(child);
|
|
13561
13551
|
}
|
|
13562
13552
|
nextArtifactId() {
|
|
13563
13553
|
const id = this.idFactory();
|
|
@@ -13585,11 +13575,11 @@ var ArtifactStore = class {
|
|
|
13585
13575
|
}
|
|
13586
13576
|
}
|
|
13587
13577
|
async removeStoredFile(path) {
|
|
13588
|
-
await
|
|
13578
|
+
await unlink(path).catch(() => {
|
|
13589
13579
|
});
|
|
13590
13580
|
const parent = dirname2(path);
|
|
13591
13581
|
if (parent !== this.directory && this.isInsideStore(parent)) {
|
|
13592
|
-
await
|
|
13582
|
+
await rm(parent, { recursive: true, force: true }).catch(() => {
|
|
13593
13583
|
});
|
|
13594
13584
|
}
|
|
13595
13585
|
}
|
|
@@ -13801,8 +13791,8 @@ var ManagedTargetJanitorClient = class {
|
|
|
13801
13791
|
});
|
|
13802
13792
|
this.worker = worker;
|
|
13803
13793
|
this.ready = false;
|
|
13804
|
-
const ready = new Promise((
|
|
13805
|
-
this.readyResolve =
|
|
13794
|
+
const ready = new Promise((resolve2, reject) => {
|
|
13795
|
+
this.readyResolve = resolve2;
|
|
13806
13796
|
this.readyReject = reject;
|
|
13807
13797
|
});
|
|
13808
13798
|
worker.on("message", (value) => this.handleWorkerMessage(worker, value));
|
|
@@ -13907,7 +13897,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
13907
13897
|
}
|
|
13908
13898
|
const id = this.nextRequestId++;
|
|
13909
13899
|
const request = { id, method, params };
|
|
13910
|
-
return new Promise((
|
|
13900
|
+
return new Promise((resolve2, reject) => {
|
|
13911
13901
|
const timer = setTimeout(() => {
|
|
13912
13902
|
this.pendingRequests.delete(id);
|
|
13913
13903
|
reject(method === "create" ? new BrowserPilotError(
|
|
@@ -13917,7 +13907,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
13917
13907
|
) : new Error(`Managed browser connection timeout: ${method}`));
|
|
13918
13908
|
}, REQUEST_TIMEOUT_MS);
|
|
13919
13909
|
timer.unref();
|
|
13920
|
-
this.pendingRequests.set(id, { resolve:
|
|
13910
|
+
this.pendingRequests.set(id, { resolve: resolve2, reject, timer, method });
|
|
13921
13911
|
worker.send?.(request, (error) => {
|
|
13922
13912
|
if (!error) return;
|
|
13923
13913
|
const pending = this.pendingRequests.get(id);
|
|
@@ -13947,19 +13937,19 @@ var ManagedTargetJanitorClient = class {
|
|
|
13947
13937
|
this.expectedExits.add(worker);
|
|
13948
13938
|
if (cleanup2 && worker.stdin && !worker.stdin.destroyed) worker.stdin.end();
|
|
13949
13939
|
else worker.kill("SIGTERM");
|
|
13950
|
-
await new Promise((
|
|
13940
|
+
await new Promise((resolve2) => {
|
|
13951
13941
|
if (worker.exitCode !== null || worker.signalCode !== null) {
|
|
13952
|
-
|
|
13942
|
+
resolve2();
|
|
13953
13943
|
return;
|
|
13954
13944
|
}
|
|
13955
13945
|
const timer = setTimeout(() => {
|
|
13956
13946
|
worker.kill("SIGKILL");
|
|
13957
|
-
|
|
13947
|
+
resolve2();
|
|
13958
13948
|
}, STOP_TIMEOUT_MS);
|
|
13959
13949
|
timer.unref();
|
|
13960
13950
|
worker.once("exit", () => {
|
|
13961
13951
|
clearTimeout(timer);
|
|
13962
|
-
|
|
13952
|
+
resolve2();
|
|
13963
13953
|
});
|
|
13964
13954
|
});
|
|
13965
13955
|
if (this.worker === worker) this.failWorker(worker, this.unavailableError());
|
|
@@ -13989,7 +13979,7 @@ var ManagedTargetJanitorClient = class {
|
|
|
13989
13979
|
};
|
|
13990
13980
|
|
|
13991
13981
|
// src/version.ts
|
|
13992
|
-
var BROWSER_PILOT_VERSION = "0.3.0-rc.
|
|
13982
|
+
var BROWSER_PILOT_VERSION = "0.3.0-rc.4";
|
|
13993
13983
|
|
|
13994
13984
|
// src/daemon.ts
|
|
13995
13985
|
var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
|
|
@@ -14003,7 +13993,7 @@ var browserProfile = process.argv[4] || "";
|
|
|
14003
13993
|
ensureBrokerDirectoriesSync();
|
|
14004
13994
|
for (const legacyFile of ["state.json", "refs.json"]) {
|
|
14005
13995
|
try {
|
|
14006
|
-
unlinkSync2(
|
|
13996
|
+
unlinkSync2(join5(STATE_DIR, legacyFile));
|
|
14007
13997
|
} catch {
|
|
14008
13998
|
}
|
|
14009
13999
|
}
|
|
@@ -14023,7 +14013,7 @@ function cleanup(brokerProcessIdentity) {
|
|
|
14023
14013
|
removeBrokerLocatorSync(brokerProcessIdentity);
|
|
14024
14014
|
}
|
|
14025
14015
|
function readBody(req, maxBytes = 5 * 1024 * 1024) {
|
|
14026
|
-
return new Promise((
|
|
14016
|
+
return new Promise((resolve2, reject) => {
|
|
14027
14017
|
const chunks = [];
|
|
14028
14018
|
let byteLength = 0;
|
|
14029
14019
|
req.on("data", (value) => {
|
|
@@ -14036,7 +14026,7 @@ function readBody(req, maxBytes = 5 * 1024 * 1024) {
|
|
|
14036
14026
|
}
|
|
14037
14027
|
chunks.push(chunk);
|
|
14038
14028
|
});
|
|
14039
|
-
req.on("end", () =>
|
|
14029
|
+
req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf8")));
|
|
14040
14030
|
req.on("error", reject);
|
|
14041
14031
|
});
|
|
14042
14032
|
}
|
|
@@ -14103,8 +14093,6 @@ async function syncFetchAll(cdp, currentSessionId) {
|
|
|
14103
14093
|
async function main() {
|
|
14104
14094
|
const startedAt = Date.now();
|
|
14105
14095
|
const brokerProcessIdentity = `${process.pid}:${startedAt}`;
|
|
14106
|
-
writeBrokerStartingSync({ pid: process.pid, startedAt, brokerProcessIdentity });
|
|
14107
|
-
process.once("exit", () => cleanup(brokerProcessIdentity));
|
|
14108
14096
|
const cdp = new ManagedTargetJanitorClient({
|
|
14109
14097
|
onLog: (message) => process.stderr.write(`Managed browser connection: ${message}
|
|
14110
14098
|
`)
|
|
@@ -14121,8 +14109,10 @@ async function main() {
|
|
|
14121
14109
|
const requestTermination = () => {
|
|
14122
14110
|
void terminate();
|
|
14123
14111
|
};
|
|
14112
|
+
process.once("exit", () => cleanup(brokerProcessIdentity));
|
|
14124
14113
|
process.on("SIGTERM", requestTermination);
|
|
14125
14114
|
process.on("SIGINT", requestTermination);
|
|
14115
|
+
writeBrokerStartingSync({ pid: process.pid, startedAt, brokerProcessIdentity });
|
|
14126
14116
|
if (initialWsUrl) await cdp.connect(initialWsUrl);
|
|
14127
14117
|
let activeSessionId;
|
|
14128
14118
|
let currentWsUrl = initialWsUrl;
|