@cmmd-center/forge 0.13.29 → 0.13.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BunHttpServer-C0oPP_3A.cjs → BunHttpServer-CXndtc1j.cjs} +2 -2
- package/dist/{BunHttpServer-FbbQmsl_.mjs → BunHttpServer-DjilmGOM.mjs} +2 -2
- package/dist/{BunPTY-uyreNRGp.cjs → BunPTY-BiTlUj63.cjs} +1 -1
- package/dist/{BunPTY-DkAIzLIx.mjs → BunPTY-Csgwhbi5.mjs} +1 -1
- package/dist/{BunServices-DC7nhA3w.mjs → BunServices-BZKzHdrG.mjs} +1 -1
- package/dist/{BunServices-DkenwD7S.cjs → BunServices-DsWG7VM1.cjs} +1 -1
- package/dist/{NodeHttpServer-Cjd3bFrD.cjs → NodeHttpServer-B4dCKqYY.cjs} +1 -1
- package/dist/{NodeHttpServer-BJ4l15f-.mjs → NodeHttpServer-Ds3Gp1Gp.mjs} +1 -1
- package/dist/{NodePTY-BehazUMi.cjs → NodePTY-BJ7874MO.cjs} +1 -1
- package/dist/{NodePTY-BhezYy9H.mjs → NodePTY-DgXWLtWp.mjs} +1 -1
- package/dist/{NodeSqliteClient-CfsdC3AB.cjs → NodeSqliteClient-CPsJxxdD.cjs} +1 -1
- package/dist/{NodeSqliteClient-md8xd9hY.mjs → NodeSqliteClient-D29w60zZ.mjs} +1 -1
- package/dist/{SqliteClient-BKB695RF.mjs → SqliteClient-Bgo_s5sA.mjs} +1 -1
- package/dist/{SqliteClient-O1ABIngw.cjs → SqliteClient-k0pbfeIC.cjs} +1 -1
- package/dist/bin.cjs +1879 -2378
- package/dist/bin.mjs +1876 -2375
- package/dist/client/assets/{DiffPanel-CsYeFcEd.js → DiffPanel-rxdARnAn.js} +2 -2
- package/dist/client/assets/{DiffPanel.logic-BhrQueD8.js → DiffPanel.logic-pioWZtAT.js} +2 -2
- package/dist/client/assets/{DiffPanelShell-CssAtWUC.js → DiffPanelShell-CJre8MyV.js} +3 -3
- package/dist/client/assets/{DiffWorkerPoolProvider-C0iUCn9m.js → DiffWorkerPoolProvider-BoinSMK1.js} +2 -2
- package/dist/client/assets/{PullRequestCodePanel-O-XZJVnK.js → PullRequestCodePanel-BR0xA-je.js} +2 -2
- package/dist/client/assets/{Virtualizer-Z2t67-w7.js → Virtualizer-H0mi-vRc.js} +2 -2
- package/dist/client/assets/{index-AMVPSS6N.js → index-DQtjHxYt.js} +6 -6
- package/dist/client/index.html +4 -4
- package/dist/{execAsync-BUcaFQ1D.cjs → execAsync--H9ZZOiC.cjs} +1 -1
- package/dist/{execAsync-BNNdR88o.mjs → execAsync-BJ4g1UuR.mjs} +1 -1
- package/dist/forge-build.json +1 -1
- package/dist/{getMachineId-bsd-3FzKrfVS.cjs → getMachineId-bsd-CJJWgsvW.cjs} +2 -2
- package/dist/{getMachineId-bsd-BuYwT36r.mjs → getMachineId-bsd-CvzjzhCJ.mjs} +2 -2
- package/dist/{getMachineId-darwin-RZKhGQ6C.mjs → getMachineId-darwin-BkjZ6NK7.mjs} +2 -2
- package/dist/{getMachineId-darwin-J2wNbebX.cjs → getMachineId-darwin-hZeIAq-t.cjs} +2 -2
- package/dist/{getMachineId-linux-BDLTrARB.cjs → getMachineId-linux-B4Pn5gp9.cjs} +1 -1
- package/dist/{getMachineId-linux-D5HSP0rH.mjs → getMachineId-linux-BaBxzaf3.mjs} +1 -1
- package/dist/{getMachineId-unsupported-DRh5MFwr.cjs → getMachineId-unsupported-B3jtoOL0.cjs} +1 -1
- package/dist/{getMachineId-unsupported-IaplPZnC.mjs → getMachineId-unsupported-EhCzrOph.mjs} +1 -1
- package/dist/{getMachineId-win-D220ursp.mjs → getMachineId-win-BFwtIsaZ.mjs} +2 -2
- package/dist/{getMachineId-win-BA8dHcjg.cjs → getMachineId-win-Bb4lJE8e.cjs} +2 -2
- package/dist/{open-D8mGyWmt.mjs → open-2-reBrpv.mjs} +1 -1
- package/dist/{open-BAsPVCWX.cjs → open-CghsF25U.cjs} +1 -1
- package/dist/playwright-backend-BONxTCSA.cjs +643 -0
- package/dist/playwright-backend-DVQOhqRO.mjs +642 -0
- package/dist/settle-B6Rn65UV.cjs +28 -0
- package/dist/settle-BJcmpbvq.mjs +23 -0
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -14,6 +14,7 @@ const require_Mime = require("./Mime-Bilnh8zE.cjs");
|
|
|
14
14
|
const require_esm = require("./esm-BZhXwX5p.cjs");
|
|
15
15
|
const require_runtimeControlAuthority = require("./runtimeControlAuthority-DCitZFTk.cjs");
|
|
16
16
|
const require_PTY = require("./PTY-B4ZctbwQ.cjs");
|
|
17
|
+
const require_settle = require("./settle-B6Rn65UV.cjs");
|
|
17
18
|
let node_child_process = require("node:child_process");
|
|
18
19
|
node_child_process = require_chunk.__toESM(node_child_process);
|
|
19
20
|
let node_stream = require("node:stream");
|
|
@@ -89,7 +90,6 @@ http2 = require_chunk.__toESM(http2);
|
|
|
89
90
|
let fs_promises = require("fs/promises");
|
|
90
91
|
let readline = require("readline");
|
|
91
92
|
let async_hooks = require("async_hooks");
|
|
92
|
-
let playwright = require("playwright");
|
|
93
93
|
let node_dgram = require("node:dgram");
|
|
94
94
|
let dgram = require("dgram");
|
|
95
95
|
let timers_promises = require("timers/promises");
|
|
@@ -53192,19 +53192,19 @@ var require_getMachineId = /* @__PURE__ */ require_chunk.__commonJSMin(((exports
|
|
|
53192
53192
|
async function getMachineId() {
|
|
53193
53193
|
if (!getMachineIdImpl) switch (process$3.platform) {
|
|
53194
53194
|
case "darwin":
|
|
53195
|
-
getMachineIdImpl = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./getMachineId-darwin-
|
|
53195
|
+
getMachineIdImpl = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./getMachineId-darwin-hZeIAq-t.cjs").default))).getMachineId;
|
|
53196
53196
|
break;
|
|
53197
53197
|
case "linux":
|
|
53198
|
-
getMachineIdImpl = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./getMachineId-linux-
|
|
53198
|
+
getMachineIdImpl = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./getMachineId-linux-B4Pn5gp9.cjs").default))).getMachineId;
|
|
53199
53199
|
break;
|
|
53200
53200
|
case "freebsd":
|
|
53201
|
-
getMachineIdImpl = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./getMachineId-bsd-
|
|
53201
|
+
getMachineIdImpl = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./getMachineId-bsd-CJJWgsvW.cjs").default))).getMachineId;
|
|
53202
53202
|
break;
|
|
53203
53203
|
case "win32":
|
|
53204
|
-
getMachineIdImpl = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./getMachineId-win-
|
|
53204
|
+
getMachineIdImpl = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./getMachineId-win-Bb4lJE8e.cjs").default))).getMachineId;
|
|
53205
53205
|
break;
|
|
53206
53206
|
default:
|
|
53207
|
-
getMachineIdImpl = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./getMachineId-unsupported-
|
|
53207
|
+
getMachineIdImpl = (await Promise.resolve().then(() => /* @__PURE__ */ require_chunk.__toESM(require("./getMachineId-unsupported-B3jtoOL0.cjs").default))).getMachineId;
|
|
53208
53208
|
break;
|
|
53209
53209
|
}
|
|
53210
53210
|
return getMachineIdImpl();
|
|
@@ -66232,7 +66232,7 @@ function normalizeNumberish(value) {
|
|
|
66232
66232
|
}
|
|
66233
66233
|
//#endregion
|
|
66234
66234
|
//#region package.json
|
|
66235
|
-
var version$1 = "0.13.
|
|
66235
|
+
var version$1 = "0.13.31";
|
|
66236
66236
|
//#endregion
|
|
66237
66237
|
//#region src/sentry.ts
|
|
66238
66238
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -93877,7 +93877,7 @@ const launchDetached = (launch) => require_Schema$1.gen(function* () {
|
|
|
93877
93877
|
});
|
|
93878
93878
|
const make$18 = require_Schema$1.gen(function* () {
|
|
93879
93879
|
const open = yield* require_Schema$1.tryPromise({
|
|
93880
|
-
try: () => Promise.resolve().then(() => require("./open-
|
|
93880
|
+
try: () => Promise.resolve().then(() => require("./open-CghsF25U.cjs")),
|
|
93881
93881
|
catch: (cause) => new OpenError$1({
|
|
93882
93882
|
message: "failed to load browser opener",
|
|
93883
93883
|
cause
|
|
@@ -101200,7 +101200,7 @@ var BrowserManager = class extends require_Schema$1.Service()("forge/browser/Ser
|
|
|
101200
101200
|
const DEFAULT_PORT_MIN = 5200;
|
|
101201
101201
|
const DEFAULT_PORT_MAX = 5999;
|
|
101202
101202
|
var ThreadDevRuntime = class extends require_Schema$1.Service()("forge/threadDevRuntime/ThreadDevRuntime") {};
|
|
101203
|
-
function nowIso$
|
|
101203
|
+
function nowIso$7() {
|
|
101204
101204
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
101205
101205
|
}
|
|
101206
101206
|
function previewUrlForPort(port) {
|
|
@@ -101227,7 +101227,7 @@ const ThreadDevRuntimeLive = require_Schema$1.effect(ThreadDevRuntime, require_S
|
|
|
101227
101227
|
allocatedPort: port,
|
|
101228
101228
|
status: "allocated",
|
|
101229
101229
|
previewUrl: previewUrlForPort(port),
|
|
101230
|
-
updatedAt: nowIso$
|
|
101230
|
+
updatedAt: nowIso$7()
|
|
101231
101231
|
};
|
|
101232
101232
|
yield* require_HttpServer.update(byThreadId, (current) => {
|
|
101233
101233
|
const next = new Map(current);
|
|
@@ -101249,7 +101249,7 @@ const ThreadDevRuntimeLive = require_Schema$1.effect(ThreadDevRuntime, require_S
|
|
|
101249
101249
|
next.set(input.threadId, {
|
|
101250
101250
|
...existing,
|
|
101251
101251
|
status: "released",
|
|
101252
|
-
updatedAt: nowIso$
|
|
101252
|
+
updatedAt: nowIso$7()
|
|
101253
101253
|
});
|
|
101254
101254
|
return next;
|
|
101255
101255
|
});
|
|
@@ -101275,7 +101275,7 @@ const ThreadDevRuntimeLive = require_Schema$1.effect(ThreadDevRuntime, require_S
|
|
|
101275
101275
|
...patch.terminalId !== void 0 ? { terminalId: patch.terminalId } : {},
|
|
101276
101276
|
...patch.previewUrl !== void 0 ? { previewUrl: patch.previewUrl } : {},
|
|
101277
101277
|
...patch.tunnelId !== void 0 ? { tunnelId: patch.tunnelId } : {},
|
|
101278
|
-
updatedAt: nowIso$
|
|
101278
|
+
updatedAt: nowIso$7()
|
|
101279
101279
|
};
|
|
101280
101280
|
yield* require_HttpServer.update(byThreadId, (current) => {
|
|
101281
101281
|
const next = new Map(current);
|
|
@@ -118470,8 +118470,8 @@ require_Schema$1.effectDiscard(runMigrations());
|
|
|
118470
118470
|
//#endregion
|
|
118471
118471
|
//#region src/persistence/Layers/Sqlite.ts
|
|
118472
118472
|
const defaultSqliteClientLoaders = {
|
|
118473
|
-
bun: () => Promise.resolve().then(() => require("./SqliteClient-
|
|
118474
|
-
node: () => Promise.resolve().then(() => require("./NodeSqliteClient-
|
|
118473
|
+
bun: () => Promise.resolve().then(() => require("./SqliteClient-k0pbfeIC.cjs")),
|
|
118474
|
+
node: () => Promise.resolve().then(() => require("./NodeSqliteClient-CPsJxxdD.cjs"))
|
|
118475
118475
|
};
|
|
118476
118476
|
const makeRuntimeSqliteLayer = require_Schema$1.fn("makeRuntimeSqliteLayer")(function* (config) {
|
|
118477
118477
|
const loader = defaultSqliteClientLoaders[process.versions.bun !== void 0 ? "bun" : "node"];
|
|
@@ -231299,7 +231299,7 @@ const OpenCodeRuntimeLive = require_Schema$1.effect(OpenCodeRuntime, makeOpenCod
|
|
|
231299
231299
|
//#endregion
|
|
231300
231300
|
//#region src/provider/Layers/OpenCodeAdapter.ts
|
|
231301
231301
|
const PROVIDER$5 = "opencode";
|
|
231302
|
-
const nowIso$
|
|
231302
|
+
const nowIso$6 = require_Schema$1.map(require_Schema$1.now, require_Schema$1.formatIso);
|
|
231303
231303
|
/**
|
|
231304
231304
|
* Map a tagged OpenCodeRuntimeError produced by {@link runOpenCodeSdk} into
|
|
231305
231305
|
* the adapter-boundary `ProviderAdapterRequestError`. SDK-method-level call
|
|
@@ -231326,7 +231326,7 @@ const toProcessError = (threadId, cause) => new ProviderAdapterProcessError({
|
|
|
231326
231326
|
});
|
|
231327
231327
|
const buildEventBase = (input) => require_Schema$1.gen(function* () {
|
|
231328
231328
|
const uuid = yield* nextUUIDv4;
|
|
231329
|
-
const createdAt = input.createdAt ?? (yield* nowIso$
|
|
231329
|
+
const createdAt = input.createdAt ?? (yield* nowIso$6);
|
|
231330
231330
|
return {
|
|
231331
231331
|
eventId: EventId.make(uuid),
|
|
231332
231332
|
provider: PROVIDER$5,
|
|
@@ -231485,7 +231485,7 @@ function sessionErrorMessage(error) {
|
|
|
231485
231485
|
}
|
|
231486
231486
|
function updateProviderSession(context, patch, options) {
|
|
231487
231487
|
return require_Schema$1.gen(function* () {
|
|
231488
|
-
const updatedAt = yield* nowIso$
|
|
231488
|
+
const updatedAt = yield* nowIso$6;
|
|
231489
231489
|
const nextSession = {
|
|
231490
231490
|
...context.session,
|
|
231491
231491
|
...patch,
|
|
@@ -231607,7 +231607,7 @@ function makeOpenCodeAdapter(openCodeSettings, options) {
|
|
|
231607
231607
|
if (("properties" in event ? event.properties.sessionID : void 0) !== context.openCodeSessionId) return;
|
|
231608
231608
|
const turnId = context.activeTurnId;
|
|
231609
231609
|
yield* writeNativeEventBestEffort(context.session.threadId, {
|
|
231610
|
-
observedAt: yield* nowIso$
|
|
231610
|
+
observedAt: yield* nowIso$6,
|
|
231611
231611
|
event: {
|
|
231612
231612
|
provider: PROVIDER$5,
|
|
231613
231613
|
threadId: context.session.threadId,
|
|
@@ -231923,7 +231923,7 @@ function makeOpenCodeAdapter(openCodeSettings, options) {
|
|
|
231923
231923
|
yield* require_Schema$1.close(started.sessionScope, require_Schema$1.void_$1).pipe(require_Schema$1.ignore);
|
|
231924
231924
|
return raceWinner.session;
|
|
231925
231925
|
}
|
|
231926
|
-
const createdAt = yield* nowIso$
|
|
231926
|
+
const createdAt = yield* nowIso$6;
|
|
231927
231927
|
const session = {
|
|
231928
231928
|
provider: PROVIDER$5,
|
|
231929
231929
|
status: "ready",
|
|
@@ -239130,7 +239130,7 @@ function filterEvents(events, options) {
|
|
|
239130
239130
|
});
|
|
239131
239131
|
return options.limit === void 0 ? filtered : filtered.slice(-options.limit);
|
|
239132
239132
|
}
|
|
239133
|
-
function nowIso$
|
|
239133
|
+
function nowIso$5() {
|
|
239134
239134
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
239135
239135
|
}
|
|
239136
239136
|
//#endregion
|
|
@@ -239145,7 +239145,7 @@ var BrowserActionEventRecorder = class {
|
|
|
239145
239145
|
type: "browser.action",
|
|
239146
239146
|
action,
|
|
239147
239147
|
...this.sessionFields(),
|
|
239148
|
-
timestamp: nowIso$
|
|
239148
|
+
timestamp: nowIso$5()
|
|
239149
239149
|
});
|
|
239150
239150
|
}
|
|
239151
239151
|
async result(action) {
|
|
@@ -239154,7 +239154,7 @@ var BrowserActionEventRecorder = class {
|
|
|
239154
239154
|
action,
|
|
239155
239155
|
ok: true,
|
|
239156
239156
|
...this.sessionFields(),
|
|
239157
|
-
timestamp: nowIso$
|
|
239157
|
+
timestamp: nowIso$5()
|
|
239158
239158
|
});
|
|
239159
239159
|
}
|
|
239160
239160
|
async error(action, error) {
|
|
@@ -239163,7 +239163,7 @@ var BrowserActionEventRecorder = class {
|
|
|
239163
239163
|
action,
|
|
239164
239164
|
error: error instanceof Error ? error.message : String(error),
|
|
239165
239165
|
...this.sessionFields(),
|
|
239166
|
-
timestamp: nowIso$
|
|
239166
|
+
timestamp: nowIso$5()
|
|
239167
239167
|
});
|
|
239168
239168
|
}
|
|
239169
239169
|
sessionFields() {
|
|
@@ -239171,24 +239171,6 @@ var BrowserActionEventRecorder = class {
|
|
|
239171
239171
|
}
|
|
239172
239172
|
};
|
|
239173
239173
|
//#endregion
|
|
239174
|
-
//#region ../../node_modules/.bun/@cmmd-center+agent-browser@0.6.2+69e331b0475d65dc/node_modules/@cmmd-center/agent-browser/dist/core/settle.js
|
|
239175
|
-
async function settleValueWithin(promise, timeoutMs) {
|
|
239176
|
-
let timer;
|
|
239177
|
-
try {
|
|
239178
|
-
return await Promise.race([promise.then((value) => ({
|
|
239179
|
-
status: "fulfilled",
|
|
239180
|
-
value
|
|
239181
|
-
}), (error) => ({
|
|
239182
|
-
error,
|
|
239183
|
-
status: "rejected"
|
|
239184
|
-
})), new Promise((resolve) => {
|
|
239185
|
-
timer = setTimeout(() => resolve({ status: "timed-out" }), timeoutMs);
|
|
239186
|
-
})]);
|
|
239187
|
-
} finally {
|
|
239188
|
-
clearTimeout(timer);
|
|
239189
|
-
}
|
|
239190
|
-
}
|
|
239191
|
-
//#endregion
|
|
239192
239174
|
//#region ../../node_modules/.bun/@cmmd-center+agent-browser@0.6.2+69e331b0475d65dc/node_modules/@cmmd-center/agent-browser/dist/safety/policy.js
|
|
239193
239175
|
var DefaultSafetyPolicy = class {
|
|
239194
239176
|
evaluate(action) {
|
|
@@ -239241,7 +239223,7 @@ function isBoundedControlAction(action) {
|
|
|
239241
239223
|
return action.kind === "cua" || action.kind === "dom_cua" || action.kind === "viewport";
|
|
239242
239224
|
}
|
|
239243
239225
|
async function settleControlOperation(promise, action) {
|
|
239244
|
-
const result = await settleValueWithin(promise, CONTROL_ACTION_TIMEOUT_MS);
|
|
239226
|
+
const result = await require_settle.settleValueWithin(promise, CONTROL_ACTION_TIMEOUT_MS);
|
|
239245
239227
|
if (result.status === "rejected") throw result.error;
|
|
239246
239228
|
if (result.status === "timed-out") console.warn(`[agent-browser] ${action.kind} action did not settle within ${CONTROL_ACTION_TIMEOUT_MS}ms; continuing so the shared browser control channel stays live.`);
|
|
239247
239229
|
}
|
|
@@ -239403,7 +239385,7 @@ var AgentBrowser = class {
|
|
|
239403
239385
|
async clickAction(action) {
|
|
239404
239386
|
await this.run({
|
|
239405
239387
|
kind: "click",
|
|
239406
|
-
selector: locatorActionLabel
|
|
239388
|
+
selector: locatorActionLabel(action)
|
|
239407
239389
|
}, async (tab) => {
|
|
239408
239390
|
await tab.click(action);
|
|
239409
239391
|
});
|
|
@@ -239415,7 +239397,7 @@ var AgentBrowser = class {
|
|
|
239415
239397
|
async doubleClickAction(action) {
|
|
239416
239398
|
await this.run({
|
|
239417
239399
|
kind: "click",
|
|
239418
|
-
selector: locatorActionLabel
|
|
239400
|
+
selector: locatorActionLabel(action)
|
|
239419
239401
|
}, async (tab) => {
|
|
239420
239402
|
await tab.doubleClick(action);
|
|
239421
239403
|
});
|
|
@@ -239427,7 +239409,7 @@ var AgentBrowser = class {
|
|
|
239427
239409
|
async fillAction(action, value) {
|
|
239428
239410
|
await this.run({
|
|
239429
239411
|
kind: "fill",
|
|
239430
|
-
selector: locatorActionLabel
|
|
239412
|
+
selector: locatorActionLabel(action),
|
|
239431
239413
|
value
|
|
239432
239414
|
}, async (tab) => {
|
|
239433
239415
|
await tab.fill(action, value);
|
|
@@ -239440,7 +239422,7 @@ var AgentBrowser = class {
|
|
|
239440
239422
|
async typeAction(action, value) {
|
|
239441
239423
|
await this.run({
|
|
239442
239424
|
kind: "fill",
|
|
239443
|
-
selector: locatorActionLabel
|
|
239425
|
+
selector: locatorActionLabel(action),
|
|
239444
239426
|
value
|
|
239445
239427
|
}, async (tab) => {
|
|
239446
239428
|
await tab.type(action, value);
|
|
@@ -239453,7 +239435,7 @@ var AgentBrowser = class {
|
|
|
239453
239435
|
async pressAction(action, key) {
|
|
239454
239436
|
await this.run({
|
|
239455
239437
|
kind: "press",
|
|
239456
|
-
selector: locatorActionLabel
|
|
239438
|
+
selector: locatorActionLabel(action),
|
|
239457
239439
|
key
|
|
239458
239440
|
}, async (tab) => {
|
|
239459
239441
|
await tab.press(action, key);
|
|
@@ -239466,7 +239448,7 @@ var AgentBrowser = class {
|
|
|
239466
239448
|
async selectAction(action, value) {
|
|
239467
239449
|
await this.run({
|
|
239468
239450
|
kind: "select",
|
|
239469
|
-
selector: locatorActionLabel
|
|
239451
|
+
selector: locatorActionLabel(action),
|
|
239470
239452
|
value
|
|
239471
239453
|
}, async (tab) => {
|
|
239472
239454
|
await tab.select(action, value);
|
|
@@ -239479,7 +239461,7 @@ var AgentBrowser = class {
|
|
|
239479
239461
|
async checkAction(action, checked) {
|
|
239480
239462
|
await this.run({
|
|
239481
239463
|
kind: "click",
|
|
239482
|
-
selector: locatorActionLabel
|
|
239464
|
+
selector: locatorActionLabel(action),
|
|
239483
239465
|
value: String(checked)
|
|
239484
239466
|
}, async (tab) => {
|
|
239485
239467
|
await tab.check(action, checked);
|
|
@@ -239655,7 +239637,7 @@ var AgentBrowser = class {
|
|
|
239655
239637
|
}
|
|
239656
239638
|
async observeCurrentTab() {
|
|
239657
239639
|
const tab = await this.requireTab();
|
|
239658
|
-
const [info, snapshot] = await Promise.all([settleValueWithin(Promise.resolve(tab.info()), POST_ACTION_OBSERVATION_TIMEOUT_MS), settleValueWithin(Promise.resolve(tab.domSnapshot()), POST_ACTION_OBSERVATION_TIMEOUT_MS)]);
|
|
239640
|
+
const [info, snapshot] = await Promise.all([require_settle.settleValueWithin(Promise.resolve(tab.info()), POST_ACTION_OBSERVATION_TIMEOUT_MS), require_settle.settleValueWithin(Promise.resolve(tab.domSnapshot()), POST_ACTION_OBSERVATION_TIMEOUT_MS)]);
|
|
239659
239641
|
return {
|
|
239660
239642
|
snapshot: snapshot.status === "fulfilled" ? snapshot.value : "",
|
|
239661
239643
|
tab: info.status === "fulfilled" ? info.value : { id: tab.id }
|
|
@@ -239684,7 +239666,7 @@ var AgentBrowser = class {
|
|
|
239684
239666
|
await this.options.events?.record({
|
|
239685
239667
|
type: "browser.safety.confirmation.requested",
|
|
239686
239668
|
...request,
|
|
239687
|
-
createdAt: nowIso$
|
|
239669
|
+
createdAt: nowIso$5()
|
|
239688
239670
|
});
|
|
239689
239671
|
const result = await this.resolveConfirmation(request);
|
|
239690
239672
|
const approved = typeof result === "boolean" ? result : result.approved;
|
|
@@ -239692,7 +239674,7 @@ var AgentBrowser = class {
|
|
|
239692
239674
|
type: "browser.safety.confirmation.resolved",
|
|
239693
239675
|
approved,
|
|
239694
239676
|
requestId: request.requestId,
|
|
239695
|
-
resolvedAt: nowIso$
|
|
239677
|
+
resolvedAt: nowIso$5(),
|
|
239696
239678
|
...this.events.sessionFields()
|
|
239697
239679
|
});
|
|
239698
239680
|
if (!approved) throw new BrowserSafetyError(decision.reason ?? "Browser action was not confirmed.", action, {
|
|
@@ -239715,7 +239697,7 @@ function locatorAction(selector, timeoutMs) {
|
|
|
239715
239697
|
timeoutMs
|
|
239716
239698
|
};
|
|
239717
239699
|
}
|
|
239718
|
-
function locatorActionLabel
|
|
239700
|
+
function locatorActionLabel(action) {
|
|
239719
239701
|
return action.selector ?? action.query?.kind ?? "locator";
|
|
239720
239702
|
}
|
|
239721
239703
|
//#endregion
|
|
@@ -240822,778 +240804,1392 @@ function isPromiseLike(value) {
|
|
|
240822
240804
|
return !!value && typeof value.then === "function";
|
|
240823
240805
|
}
|
|
240824
240806
|
//#endregion
|
|
240825
|
-
//#region
|
|
240826
|
-
|
|
240827
|
-
|
|
240828
|
-
|
|
240829
|
-
|
|
240830
|
-
|
|
240831
|
-
|
|
240832
|
-
|
|
240833
|
-
|
|
240834
|
-
|
|
240835
|
-
|
|
240836
|
-
|
|
240837
|
-
|
|
240838
|
-
|
|
240839
|
-
|
|
240840
|
-
|
|
240841
|
-
|
|
240842
|
-
|
|
240843
|
-
|
|
240844
|
-
|
|
240845
|
-
|
|
240846
|
-
|
|
240847
|
-
|
|
240848
|
-
|
|
240849
|
-
|
|
240850
|
-
|
|
240851
|
-
|
|
240852
|
-
|
|
240853
|
-
|
|
240854
|
-
|
|
240855
|
-
|
|
240856
|
-
|
|
240857
|
-
|
|
240858
|
-
|
|
240859
|
-
|
|
240860
|
-
|
|
240861
|
-
|
|
240862
|
-
|
|
240863
|
-
|
|
240864
|
-
|
|
240865
|
-
|
|
240866
|
-
|
|
240807
|
+
//#region src/sprite/spriteTaskHold.ts
|
|
240808
|
+
/**
|
|
240809
|
+
* Keeps a Fly.io Sprite awake for the duration of an agent turn.
|
|
240810
|
+
*
|
|
240811
|
+
* ── why this exists ─────────────────────────────────────────────────────────
|
|
240812
|
+
* A sprite's container cgroup is frozen within ~1 second of the last inbound
|
|
240813
|
+
* request. Processes are paused mid-instruction, not killed, and resume when
|
|
240814
|
+
* traffic returns. Measured on a throwaway sprite with a 1 Hz ticker
|
|
240815
|
+
* (`pocs/sprite-lifecycle-spike/`):
|
|
240816
|
+
*
|
|
240817
|
+
* detached, nothing connected 2 ticks / 121 s = 1.7%
|
|
240818
|
+
* with a task hold 90 ticks / 90 s = 100%
|
|
240819
|
+
*
|
|
240820
|
+
* So an agent turn that nobody is watching effectively stops. fly.io names our
|
|
240821
|
+
* previous approach — relying on a running process after disconnection — as an
|
|
240822
|
+
* anti-pattern, and points at the Tasks API instead. See
|
|
240823
|
+
* https://docs.sprites.dev/keeping-sprites-running/ and
|
|
240824
|
+
* `docs/specs/2026-07-30-sprites-platform-ground-truth.md` §1.
|
|
240825
|
+
*
|
|
240826
|
+
* Note that Services (the platform supervisor) do NOT solve this: "a Sprite with
|
|
240827
|
+
* ten services defined still pauses when it goes idle." Supervision and
|
|
240828
|
+
* wakefulness are separate primitives, and this module is only the second one.
|
|
240829
|
+
*
|
|
240830
|
+
* ── why it lives in the guest ───────────────────────────────────────────────
|
|
240831
|
+
* The Tasks API is guest-only. `GET https://api.sprites.dev/v1/sprites/{n}/tasks`
|
|
240832
|
+
* answers 404; the socket at /.sprite/api.sock is the only surface. CMMD cannot
|
|
240833
|
+
* take this hold on our behalf, which is why the env vars below are exported by
|
|
240834
|
+
* CMMD's provider and consumed here.
|
|
240835
|
+
*
|
|
240836
|
+
* ── the ordering rules ──────────────────────────────────────────────────────
|
|
240837
|
+
* 1. The refresher MUST be stopped before the DELETE is issued. A refresher that
|
|
240838
|
+
* outlives the delete re-acquires the hold on its next tick; that was
|
|
240839
|
+
* previously measured leaking 37 renewals over 18 minutes, pinning a sprite
|
|
240840
|
+
* running — and billing — 33 minutes after its work had ended.
|
|
240841
|
+
* 2. Stopping the interval only cancels FUTURE ticks. A PUT already on the wire
|
|
240842
|
+
* must be JOINED before the DELETE, or it lands afterwards and recreates the
|
|
240843
|
+
* hold for another full expiry window. fly.io's reference worker cancels and
|
|
240844
|
+
* awaits its heartbeat for the same reason.
|
|
240845
|
+
* 3. `release` can arrive while `acquire` is still awaiting its own PUT —
|
|
240846
|
+
* `releaseAll` runs from runtime drain and the shutdown finalizer,
|
|
240847
|
+
* concurrently with the provider event stream. The hold is registered before
|
|
240848
|
+
* that await (so a duplicate turn.started cannot double-acquire), so release
|
|
240849
|
+
* can remove the map entry first; `acquire` must then NOT install a
|
|
240850
|
+
* refresher. One attached to an unmapped hold is unreachable by release,
|
|
240851
|
+
* releaseThread and releaseAll alike, and re-PUTs every 60 s for the life of
|
|
240852
|
+
* the process — an unbounded pin.
|
|
240853
|
+
*
|
|
240854
|
+
* All three are pinned by tests.
|
|
240855
|
+
*
|
|
240856
|
+
* Pure and dependency-free on purpose, for the same reason as
|
|
240857
|
+
* `provider/claudeCredentialContainment.ts`: this rule is cost-critical and must
|
|
240858
|
+
* stay directly unit-testable without booting the Effect runtime.
|
|
240859
|
+
*/
|
|
240860
|
+
/** Set by CMMD's Sprites provider on every project sprite. Absent elsewhere. */
|
|
240861
|
+
const SPRITE_TASK_HOLD_SOCKET_ENV = "FORGE_CMMD_SPRITE_TASK_HOLD_SOCKET";
|
|
240862
|
+
const SPRITE_TASK_HOLD_REFRESH_MS = 6e4;
|
|
240863
|
+
/**
|
|
240864
|
+
* Per-request deadline for the guest-socket calls. Must stay strictly under
|
|
240865
|
+
* SPRITE_TASK_HOLD_REFRESH_MS: the manager serializes refreshes, so a PUT that
|
|
240866
|
+
* never settles turns every later tick into a no-op and the hold expires
|
|
240867
|
+
* mid-turn. Bounding the call means a hung socket settles as a rejection, the
|
|
240868
|
+
* next 60 s tick retries, and release is not wedged joining it. From the last
|
|
240869
|
+
* good refresh there are still four attempts (+60/+120/+180/+240 s) before the
|
|
240870
|
+
* 300 s expiry. 10 s matches the `-m 10` cap CMMD already puts on every curl in
|
|
240871
|
+
* its own hold preamble; the guest-side manager was the one path left unbounded.
|
|
240872
|
+
*/
|
|
240873
|
+
const SPRITE_TASK_HOLD_REQUEST_TIMEOUT_MS = 1e4;
|
|
240874
|
+
/**
|
|
240875
|
+
* Resolve the hold configuration from the process environment.
|
|
240876
|
+
*
|
|
240877
|
+
* Returns null on every runtime that is not a project sprite — desktop-local and
|
|
240878
|
+
* browser runtimes have no `/.sprite/api.sock`, so the whole feature no-ops
|
|
240879
|
+
* there rather than erroring.
|
|
240880
|
+
*/
|
|
240881
|
+
function resolveSpriteTaskHoldConfig(env = process.env) {
|
|
240882
|
+
const socketPath = env[SPRITE_TASK_HOLD_SOCKET_ENV]?.trim();
|
|
240883
|
+
if (socketPath === void 0 || socketPath.length === 0) return null;
|
|
240867
240884
|
return {
|
|
240868
|
-
|
|
240869
|
-
|
|
240870
|
-
...options.hasNotText === void 0 ? {} : { hasNotText: options.hasNotText },
|
|
240871
|
-
...options.hasText === void 0 ? {} : { hasText: options.hasText }
|
|
240885
|
+
socketPath,
|
|
240886
|
+
namePrefix: env["FORGE_CMMD_SPRITE_TASK_HOLD_PREFIX"]?.trim() ?? ""
|
|
240872
240887
|
};
|
|
240873
240888
|
}
|
|
240874
|
-
|
|
240875
|
-
|
|
240876
|
-
|
|
240877
|
-
|
|
240878
|
-
|
|
240879
|
-
|
|
240880
|
-
|
|
240881
|
-
|
|
240882
|
-
|
|
240883
|
-
|
|
240884
|
-
|
|
240885
|
-
constructor(options = {}) {
|
|
240886
|
-
this.options = options;
|
|
240887
|
-
}
|
|
240888
|
-
info() {
|
|
240889
|
-
return {
|
|
240890
|
-
id: "playwright",
|
|
240891
|
-
kind: "playwright",
|
|
240892
|
-
name: this.options.name ?? "Playwright Chromium"
|
|
240893
|
-
};
|
|
240894
|
-
}
|
|
240895
|
-
async close() {
|
|
240896
|
-
/* v8 ignore next -- defensive cleanup for already-closed Playwright contexts. */
|
|
240897
|
-
await this.context?.close().catch(() => void 0);
|
|
240898
|
-
await this.browser?.close();
|
|
240899
|
-
this.browser = void 0;
|
|
240900
|
-
this.context = void 0;
|
|
240889
|
+
/**
|
|
240890
|
+
* Hold names must be unique per turn: several git worktrees inside one sprite
|
|
240891
|
+
* can run turns concurrently, and each has to be released independently.
|
|
240892
|
+
*/
|
|
240893
|
+
function spriteTaskHoldName(namePrefix, turnId) {
|
|
240894
|
+
return `${namePrefix}${turnId}`;
|
|
240895
|
+
}
|
|
240896
|
+
const DEFAULT_TIMER = {
|
|
240897
|
+
setInterval: (handler, ms) => setInterval(handler, ms),
|
|
240898
|
+
clearInterval: (handle) => {
|
|
240899
|
+
clearInterval(handle);
|
|
240901
240900
|
}
|
|
240902
|
-
|
|
240903
|
-
|
|
240904
|
-
|
|
240901
|
+
};
|
|
240902
|
+
/**
|
|
240903
|
+
* Tracks one hold per active turn.
|
|
240904
|
+
*
|
|
240905
|
+
* Every method is failure-tolerant by design. Acquiring a hold is an
|
|
240906
|
+
* optimisation, not a precondition: if it fails the turn still runs, just at the
|
|
240907
|
+
* degraded speed it has today. Failing the turn instead would convert a billing
|
|
240908
|
+
* inefficiency into an outage.
|
|
240909
|
+
*/
|
|
240910
|
+
var SpriteTaskHoldManager = class {
|
|
240911
|
+
#config;
|
|
240912
|
+
#transport;
|
|
240913
|
+
#timer;
|
|
240914
|
+
#onError;
|
|
240915
|
+
#holds = /* @__PURE__ */ new Map();
|
|
240916
|
+
constructor(options) {
|
|
240917
|
+
this.#config = options.config;
|
|
240918
|
+
this.#transport = options.transport;
|
|
240919
|
+
this.#timer = options.timer ?? DEFAULT_TIMER;
|
|
240920
|
+
this.#onError = options.onError ?? (() => {});
|
|
240905
240921
|
}
|
|
240906
|
-
|
|
240907
|
-
|
|
240908
|
-
|
|
240909
|
-
return page ? this.toTab(page) : void 0;
|
|
240922
|
+
/** Turn ids that currently hold the sprite awake. Test/diagnostic surface. */
|
|
240923
|
+
activeTurnIds() {
|
|
240924
|
+
return [...this.#holds.keys()];
|
|
240910
240925
|
}
|
|
240911
|
-
async
|
|
240912
|
-
|
|
240913
|
-
|
|
240914
|
-
|
|
240915
|
-
|
|
240916
|
-
|
|
240917
|
-
|
|
240918
|
-
|
|
240919
|
-
|
|
240920
|
-
|
|
240921
|
-
|
|
240922
|
-
|
|
240923
|
-
|
|
240924
|
-
|
|
240925
|
-
if (this.options.userDataDir && !this.context) {
|
|
240926
|
-
this.context = await playwright.chromium.launchPersistentContext(this.options.userDataDir, {
|
|
240927
|
-
...this.options.launchOptions,
|
|
240928
|
-
...this.options.contextOptions,
|
|
240929
|
-
headless: this.options.headless ?? false
|
|
240930
|
-
});
|
|
240931
|
-
this.browser = this.context.browser() ?? void 0;
|
|
240932
|
-
return this.context;
|
|
240933
|
-
}
|
|
240934
|
-
/* v8 ignore next -- requires a live externally managed Chrome CDP endpoint */
|
|
240935
|
-
if (!this.browser && this.options.cdpEndpoint) {
|
|
240936
|
-
/* v8 ignore start -- requires a live externally managed Chrome CDP endpoint */
|
|
240937
|
-
this.browser = await playwright.chromium.connectOverCDP(this.options.cdpEndpoint);
|
|
240938
|
-
this.context = this.browser.contexts()[0] ?? await this.browser.newContext(this.options.contextOptions);
|
|
240939
|
-
return this.context;
|
|
240940
|
-
}
|
|
240941
|
-
/* v8 ignore next -- headed/default launch is covered by browser parity smoke. */
|
|
240942
|
-
if (!this.browser) this.browser = await playwright.chromium.launch({
|
|
240943
|
-
...this.options.launchOptions,
|
|
240944
|
-
headless: this.options.headless ?? false
|
|
240945
|
-
});
|
|
240946
|
-
if (!this.context) this.context = await this.browser.newContext(this.options.contextOptions);
|
|
240947
|
-
return this.context;
|
|
240948
|
-
}
|
|
240949
|
-
toTab(page) {
|
|
240950
|
-
this.registerPage(page);
|
|
240951
|
-
return new PlaywrightTabHandle(page, this.idFor(page), this.logsFor(page));
|
|
240952
|
-
}
|
|
240953
|
-
async preparePage(page) {
|
|
240954
|
-
this.registerPage(page);
|
|
240955
|
-
const bounds = this.options.windowBounds;
|
|
240956
|
-
if (!bounds) return;
|
|
240957
|
-
await settleValueWithin(page.setViewportSize({
|
|
240958
|
-
width: bounds.width,
|
|
240959
|
-
height: bounds.height
|
|
240960
|
-
}), TAB_CONTROL_TIMEOUT_MS);
|
|
240961
|
-
const sessionResult = await settleValueWithin(page.context().newCDPSession(page), TAB_CONTROL_TIMEOUT_MS);
|
|
240962
|
-
/* v8 ignore next -- Playwright CDP attachment failures are bounded and covered by the shared timeout primitive. */
|
|
240963
|
-
if (sessionResult.status !== "fulfilled") return;
|
|
240964
|
-
const session = sessionResult.value;
|
|
240926
|
+
async acquire(turnId, threadId = null) {
|
|
240927
|
+
if (this.#holds.has(turnId)) return;
|
|
240928
|
+
const name = spriteTaskHoldName(this.#config.namePrefix, turnId);
|
|
240929
|
+
const hold = {
|
|
240930
|
+
name,
|
|
240931
|
+
threadId,
|
|
240932
|
+
refresher: null,
|
|
240933
|
+
inFlight: /* @__PURE__ */ new Set(),
|
|
240934
|
+
released: false
|
|
240935
|
+
};
|
|
240936
|
+
this.#holds.set(turnId, hold);
|
|
240937
|
+
const put = this.#transport.put(name, 300);
|
|
240938
|
+
const tracked = put.then(() => {}, () => {});
|
|
240939
|
+
hold.inFlight.add(tracked);
|
|
240965
240940
|
try {
|
|
240966
|
-
|
|
240967
|
-
|
|
240968
|
-
|
|
240969
|
-
|
|
240970
|
-
|
|
240971
|
-
height: bounds.height,
|
|
240972
|
-
left: bounds.left ?? 0,
|
|
240973
|
-
top: bounds.top ?? 0,
|
|
240974
|
-
width: bounds.width,
|
|
240975
|
-
windowState: "normal"
|
|
240976
|
-
},
|
|
240977
|
-
windowId: windowResult.value.windowId
|
|
240978
|
-
}), TAB_CONTROL_TIMEOUT_MS);
|
|
240979
|
-
} finally {
|
|
240980
|
-
await settleValueWithin(session.detach(), TAB_CONTROL_TIMEOUT_MS);
|
|
240981
|
-
}
|
|
240982
|
-
}
|
|
240983
|
-
registerPage(page) {
|
|
240984
|
-
this.idFor(page);
|
|
240985
|
-
this.logsFor(page);
|
|
240986
|
-
if (this.registeredPages.has(page)) return;
|
|
240987
|
-
this.registeredPages.add(page);
|
|
240988
|
-
page.on("console", (message) => {
|
|
240989
|
-
this.logsFor(page).push(toConsoleLog(message, page.url()));
|
|
240990
|
-
});
|
|
240991
|
-
}
|
|
240992
|
-
idFor(page) {
|
|
240993
|
-
let id = this.pageIds.get(page);
|
|
240994
|
-
if (!id) {
|
|
240995
|
-
id = `tab_${(0, node_crypto.randomUUID)()}`;
|
|
240996
|
-
this.pageIds.set(page, id);
|
|
240941
|
+
await put;
|
|
240942
|
+
} catch (cause) {
|
|
240943
|
+
hold.inFlight.delete(tracked);
|
|
240944
|
+
this.#onError(`failed to acquire sprite task hold for turn ${turnId}`, cause);
|
|
240945
|
+
if (hold.released || this.#holds.get(turnId) !== hold) return;
|
|
240997
240946
|
}
|
|
240998
|
-
|
|
240947
|
+
hold.inFlight.delete(tracked);
|
|
240948
|
+
if (hold.released || this.#holds.get(turnId) !== hold) return;
|
|
240949
|
+
hold.refresher = this.#timer.setInterval(() => {
|
|
240950
|
+
if (hold.inFlight.size > 0) return;
|
|
240951
|
+
const refresh = this.#transport.put(name, 300).catch((cause) => this.#onError(`failed to refresh sprite task hold for turn ${turnId}`, cause));
|
|
240952
|
+
hold.inFlight.add(refresh);
|
|
240953
|
+
refresh.finally(() => hold.inFlight.delete(refresh));
|
|
240954
|
+
}, SPRITE_TASK_HOLD_REFRESH_MS);
|
|
240999
240955
|
}
|
|
241000
|
-
|
|
241001
|
-
|
|
241002
|
-
if (!
|
|
241003
|
-
|
|
241004
|
-
|
|
240956
|
+
async release(turnId) {
|
|
240957
|
+
const hold = this.#holds.get(turnId);
|
|
240958
|
+
if (!hold) return;
|
|
240959
|
+
hold.released = true;
|
|
240960
|
+
if (hold.refresher !== null) {
|
|
240961
|
+
this.#timer.clearInterval(hold.refresher);
|
|
240962
|
+
hold.refresher = null;
|
|
241005
240963
|
}
|
|
241006
|
-
|
|
241007
|
-
|
|
241008
|
-
};
|
|
241009
|
-
var PlaywrightTabHandle = class {
|
|
241010
|
-
page;
|
|
241011
|
-
id;
|
|
241012
|
-
logEntries;
|
|
241013
|
-
constructor(page, id, logEntries) {
|
|
241014
|
-
this.page = page;
|
|
241015
|
-
this.id = id;
|
|
241016
|
-
this.logEntries = logEntries;
|
|
241017
|
-
}
|
|
241018
|
-
async info() {
|
|
241019
|
-
const [title, history] = await Promise.all([settleValueWithin(this.page.title(), TAB_METADATA_TIMEOUT_MS), settleValueWithin(this.historyState(), TAB_METADATA_TIMEOUT_MS)]);
|
|
241020
|
-
const base = title.status !== "fulfilled" ? {
|
|
241021
|
-
id: this.id,
|
|
241022
|
-
url: this.page.url()
|
|
241023
|
-
} : {
|
|
241024
|
-
id: this.id,
|
|
241025
|
-
title: title.value,
|
|
241026
|
-
url: this.page.url()
|
|
241027
|
-
};
|
|
241028
|
-
/* v8 ignore next -- metadata timeout fallback is covered through AgentBrowser's bounded observation contract. */
|
|
241029
|
-
return history.status === "fulfilled" ? {
|
|
241030
|
-
...base,
|
|
241031
|
-
...history.value
|
|
241032
|
-
} : base;
|
|
241033
|
-
}
|
|
241034
|
-
async historyState() {
|
|
241035
|
-
const sessionResult = await settleValueWithin(this.page.context().newCDPSession(this.page), TAB_METADATA_TIMEOUT_MS);
|
|
241036
|
-
/* v8 ignore next -- Playwright CDP attachment failures are bounded and covered by the shared timeout primitive. */
|
|
241037
|
-
if (sessionResult.status !== "fulfilled") return {};
|
|
241038
|
-
const session = sessionResult.value;
|
|
240964
|
+
this.#holds.delete(turnId);
|
|
240965
|
+
if (hold.inFlight.size > 0) await Promise.all(hold.inFlight);
|
|
241039
240966
|
try {
|
|
241040
|
-
|
|
241041
|
-
|
|
241042
|
-
|
|
241043
|
-
const history = historyResult.value;
|
|
241044
|
-
return {
|
|
241045
|
-
canGoBack: history.currentIndex > 0,
|
|
241046
|
-
canGoForward: history.currentIndex < history.entries.length - 1
|
|
241047
|
-
};
|
|
241048
|
-
} finally {
|
|
241049
|
-
await settleValueWithin(session.detach(), TAB_METADATA_TIMEOUT_MS);
|
|
240967
|
+
await this.#transport.delete(hold.name);
|
|
240968
|
+
} catch (cause) {
|
|
240969
|
+
this.#onError(`failed to release sprite task hold for turn ${turnId}`, cause);
|
|
241050
240970
|
}
|
|
241051
240971
|
}
|
|
241052
|
-
|
|
241053
|
-
|
|
240972
|
+
/**
|
|
240973
|
+
* Release every hold belonging to one thread.
|
|
240974
|
+
*
|
|
240975
|
+
* `session.exited` names only the thread whose provider process died, and a
|
|
240976
|
+
* dead session's turn will never emit `turn.completed`. Releasing by thread —
|
|
240977
|
+
* rather than releasing everything — is what keeps a crash in one worktree
|
|
240978
|
+
* from silently dropping the holds of the other turns running beside it.
|
|
240979
|
+
*/
|
|
240980
|
+
async releaseThread(threadId) {
|
|
240981
|
+
const turnIds = [...this.#holds.entries()].filter(([, hold]) => hold.threadId === threadId).map(([turnId]) => turnId);
|
|
240982
|
+
await Promise.all(turnIds.map((turnId) => this.release(turnId)));
|
|
241054
240983
|
}
|
|
241055
|
-
|
|
241056
|
-
|
|
240984
|
+
/**
|
|
240985
|
+
* Release everything. Runtime drain and server shutdown must call this, or a
|
|
240986
|
+
* drained sprite keeps billing until each hold expires.
|
|
240987
|
+
*/
|
|
240988
|
+
async releaseAll() {
|
|
240989
|
+
await Promise.all([...this.#holds.keys()].map((turnId) => this.release(turnId)));
|
|
241057
240990
|
}
|
|
241058
|
-
|
|
241059
|
-
|
|
240991
|
+
};
|
|
240992
|
+
/**
|
|
240993
|
+
* HTTP-over-unix-socket transport for the guest API.
|
|
240994
|
+
*
|
|
240995
|
+
* Bun's fetch takes a `unix` option; the URL host is ignored by the socket but
|
|
240996
|
+
* still has to parse, which is why it is the literal `http://sprite` the
|
|
240997
|
+
* platform docs use.
|
|
240998
|
+
*/
|
|
240999
|
+
function makeSpriteTaskHoldTransport(config, fetchImpl = fetch) {
|
|
241000
|
+
const call = async (path, init) => {
|
|
241001
|
+
const controller = new AbortController();
|
|
241002
|
+
const deadline = setTimeout(() => controller.abort(), SPRITE_TASK_HOLD_REQUEST_TIMEOUT_MS);
|
|
241060
241003
|
try {
|
|
241061
|
-
await
|
|
241062
|
-
|
|
241063
|
-
|
|
241004
|
+
const response = await fetchImpl(`http://sprite/v1/tasks${path}`, {
|
|
241005
|
+
...init,
|
|
241006
|
+
signal: controller.signal,
|
|
241007
|
+
unix: config.socketPath
|
|
241008
|
+
});
|
|
241009
|
+
if (!response.ok) throw new Error(`sprite task API responded ${response.status}`);
|
|
241064
241010
|
} finally {
|
|
241065
|
-
|
|
241011
|
+
clearTimeout(deadline);
|
|
241066
241012
|
}
|
|
241013
|
+
};
|
|
241014
|
+
return {
|
|
241015
|
+
put: (name, expireSeconds) => call(`/${encodeURIComponent(name)}`, {
|
|
241016
|
+
method: "PUT",
|
|
241017
|
+
headers: { "content-type": "application/json" },
|
|
241018
|
+
body: JSON.stringify({ expire: `${expireSeconds}s` })
|
|
241019
|
+
}),
|
|
241020
|
+
delete: (name) => call(`/${encodeURIComponent(name)}`, { method: "DELETE" })
|
|
241021
|
+
};
|
|
241022
|
+
}
|
|
241023
|
+
/**
|
|
241024
|
+
* Build a manager for this process, or null when the runtime is not a sprite.
|
|
241025
|
+
* Callers treat null as "this feature does not apply here".
|
|
241026
|
+
*/
|
|
241027
|
+
function makeSpriteTaskHoldManager(env = process.env, onError) {
|
|
241028
|
+
const config = resolveSpriteTaskHoldConfig(env);
|
|
241029
|
+
if (config === null) return null;
|
|
241030
|
+
return new SpriteTaskHoldManager({
|
|
241031
|
+
config,
|
|
241032
|
+
transport: makeSpriteTaskHoldTransport(config),
|
|
241033
|
+
...onError ? { onError } : {}
|
|
241034
|
+
});
|
|
241035
|
+
}
|
|
241036
|
+
//#endregion
|
|
241037
|
+
//#region src/browser/AgentBrowserHostBridgeCommands.ts
|
|
241038
|
+
const DEFAULT_AGENT_BROWSER_VIEWPORT = {
|
|
241039
|
+
width: 1280,
|
|
241040
|
+
height: 720
|
|
241041
|
+
};
|
|
241042
|
+
const ABSOLUTE_WEB_URL_PATTERN = /^[a-z][a-z\d+\-.]*:\/\//i;
|
|
241043
|
+
const SPECIAL_URL_PATTERN = /^(about|blob|chrome|data|devtools|file):/i;
|
|
241044
|
+
const LOCAL_URL_LIKE_PATTERN = /^(localhost|127\.0\.0\.1|\[::1\]|::1|[a-z0-9.-]+\.localhost)(?::\d+)?(?:[/?#].*)?$/i;
|
|
241045
|
+
function nowIso$4() {
|
|
241046
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
241047
|
+
}
|
|
241048
|
+
function normalizeUserVisibleBrowserUrl(url) {
|
|
241049
|
+
const trimmed = url.trim();
|
|
241050
|
+
if (trimmed.length === 0) return trimmed;
|
|
241051
|
+
if (ABSOLUTE_WEB_URL_PATTERN.test(trimmed) || SPECIAL_URL_PATTERN.test(trimmed) || trimmed.startsWith("//")) return trimmed;
|
|
241052
|
+
if (LOCAL_URL_LIKE_PATTERN.test(trimmed)) return `http://${trimmed}`;
|
|
241053
|
+
return `https://${trimmed}`;
|
|
241054
|
+
}
|
|
241055
|
+
function isAgentBrowserBoundedControlCommand(command) {
|
|
241056
|
+
switch (command.type) {
|
|
241057
|
+
case "click":
|
|
241058
|
+
case "clickPoint":
|
|
241059
|
+
case "pressKey":
|
|
241060
|
+
case "resetViewport":
|
|
241061
|
+
case "scroll":
|
|
241062
|
+
case "setViewport":
|
|
241063
|
+
case "type":
|
|
241064
|
+
case "typeText":
|
|
241065
|
+
case "wheel": return true;
|
|
241066
|
+
default: return false;
|
|
241067
241067
|
}
|
|
241068
|
-
|
|
241069
|
-
|
|
241070
|
-
|
|
241071
|
-
|
|
241072
|
-
|
|
241073
|
-
|
|
241074
|
-
|
|
241075
|
-
|
|
241076
|
-
|
|
241077
|
-
|
|
241078
|
-
|
|
241079
|
-
|
|
241080
|
-
|
|
241081
|
-
|
|
241082
|
-
...options.quality === void 0 ? {} : { quality: options.quality }
|
|
241083
|
-
});
|
|
241084
|
-
if (options.maxBytes !== void 0 && bytes.byteLength > options.maxBytes) throw new Error(`Browser screenshot exceeded maxBytes=${options.maxBytes}. size=${bytes.byteLength}`);
|
|
241085
|
-
return bytes;
|
|
241086
|
-
}
|
|
241087
|
-
async setViewport(size) {
|
|
241088
|
-
await this.page.setViewportSize({
|
|
241089
|
-
width: Math.round(size.width),
|
|
241090
|
-
height: Math.round(size.height)
|
|
241091
|
-
});
|
|
241068
|
+
}
|
|
241069
|
+
function snapshotAfterTimedOutControl(snapshot, command) {
|
|
241070
|
+
switch (command.type) {
|
|
241071
|
+
case "setViewport": return {
|
|
241072
|
+
...snapshot,
|
|
241073
|
+
viewport: command.viewport,
|
|
241074
|
+
updatedAt: nowIso$4()
|
|
241075
|
+
};
|
|
241076
|
+
case "resetViewport": return {
|
|
241077
|
+
...snapshot,
|
|
241078
|
+
viewport: DEFAULT_AGENT_BROWSER_VIEWPORT,
|
|
241079
|
+
updatedAt: nowIso$4()
|
|
241080
|
+
};
|
|
241081
|
+
default: return snapshot;
|
|
241092
241082
|
}
|
|
241093
|
-
|
|
241094
|
-
|
|
241095
|
-
|
|
241096
|
-
|
|
241097
|
-
|
|
241083
|
+
}
|
|
241084
|
+
const HOST_COMMAND_MAPPERS = {
|
|
241085
|
+
activateTab: (command) => ({
|
|
241086
|
+
type: "activateTab",
|
|
241087
|
+
tabId: command.tabId
|
|
241088
|
+
}),
|
|
241089
|
+
click: (command) => ({
|
|
241090
|
+
type: "click",
|
|
241091
|
+
tabId: command.tabId,
|
|
241092
|
+
selector: command.selector
|
|
241093
|
+
}),
|
|
241094
|
+
clickPoint: (command) => ({
|
|
241095
|
+
type: "clickPoint",
|
|
241096
|
+
tabId: command.tabId,
|
|
241097
|
+
x: command.x,
|
|
241098
|
+
y: command.y,
|
|
241099
|
+
...command.button !== void 0 ? { button: command.button } : {},
|
|
241100
|
+
...command.clickCount !== void 0 ? { clickCount: command.clickCount } : {}
|
|
241101
|
+
}),
|
|
241102
|
+
closeTab: (command) => ({
|
|
241103
|
+
type: "closeTab",
|
|
241104
|
+
tabId: command.tabId
|
|
241105
|
+
}),
|
|
241106
|
+
consoleLogs: (command) => ({
|
|
241107
|
+
type: "consoleLogs",
|
|
241108
|
+
...command.tabId !== void 0 ? { tabId: command.tabId } : {},
|
|
241109
|
+
...command.limit !== void 0 ? { limit: command.limit } : {},
|
|
241110
|
+
...command.levels !== void 0 ? { levels: command.levels } : {}
|
|
241111
|
+
}),
|
|
241112
|
+
goBack: (command) => ({
|
|
241113
|
+
type: "goBack",
|
|
241114
|
+
tabId: command.tabId
|
|
241115
|
+
}),
|
|
241116
|
+
goForward: (command) => ({
|
|
241117
|
+
type: "goForward",
|
|
241118
|
+
tabId: command.tabId
|
|
241119
|
+
}),
|
|
241120
|
+
nameSession: (command) => ({
|
|
241121
|
+
type: "nameSession",
|
|
241122
|
+
name: command.name
|
|
241123
|
+
}),
|
|
241124
|
+
navigate: (command) => ({
|
|
241125
|
+
type: "navigate",
|
|
241126
|
+
url: normalizeUserVisibleBrowserUrl(command.url),
|
|
241127
|
+
...command.tabId !== void 0 ? { tabId: command.tabId } : {}
|
|
241128
|
+
}),
|
|
241129
|
+
newTab: (command) => ({
|
|
241130
|
+
type: "newTab",
|
|
241131
|
+
...command.url !== void 0 ? { url: normalizeUserVisibleBrowserUrl(command.url) } : {},
|
|
241132
|
+
...command.makeActive !== void 0 ? { makeActive: command.makeActive } : {}
|
|
241133
|
+
}),
|
|
241134
|
+
pressKey: (command) => ({
|
|
241135
|
+
type: "pressKey",
|
|
241136
|
+
tabId: command.tabId,
|
|
241137
|
+
key: command.key
|
|
241138
|
+
}),
|
|
241139
|
+
reload: (command) => ({
|
|
241140
|
+
type: "reload",
|
|
241141
|
+
tabId: command.tabId
|
|
241142
|
+
}),
|
|
241143
|
+
resetViewport: () => ({ type: "resetViewport" }),
|
|
241144
|
+
screenshot: (command) => ({
|
|
241145
|
+
type: "screenshot",
|
|
241146
|
+
tabId: command.tabId,
|
|
241147
|
+
...command.fullPage !== void 0 ? { fullPage: command.fullPage } : {}
|
|
241148
|
+
}),
|
|
241149
|
+
scroll: (command) => ({
|
|
241150
|
+
type: "scroll",
|
|
241151
|
+
tabId: command.tabId,
|
|
241152
|
+
scrollX: 0,
|
|
241153
|
+
scrollY: command.toTop === true ? -900 : 900,
|
|
241154
|
+
...command.x !== void 0 ? { x: command.x } : {},
|
|
241155
|
+
...command.y !== void 0 ? { y: command.y } : {}
|
|
241156
|
+
}),
|
|
241157
|
+
setViewport: (command) => ({
|
|
241158
|
+
type: "setViewport",
|
|
241159
|
+
viewport: command.viewport
|
|
241160
|
+
}),
|
|
241161
|
+
setVisibility: (command) => ({
|
|
241162
|
+
type: "setVisibility",
|
|
241163
|
+
visibility: command.visibility
|
|
241164
|
+
}),
|
|
241165
|
+
snapshot: (command) => ({
|
|
241166
|
+
type: "snapshot",
|
|
241167
|
+
tabId: command.tabId
|
|
241168
|
+
}),
|
|
241169
|
+
type: (command) => ({
|
|
241170
|
+
type: "type",
|
|
241171
|
+
tabId: command.tabId,
|
|
241172
|
+
selector: command.selector,
|
|
241173
|
+
text: command.text,
|
|
241174
|
+
...command.append !== void 0 ? { append: command.append } : {}
|
|
241175
|
+
}),
|
|
241176
|
+
typeText: (command) => ({
|
|
241177
|
+
type: "typeText",
|
|
241178
|
+
tabId: command.tabId,
|
|
241179
|
+
text: command.text,
|
|
241180
|
+
...command.pressEnter !== void 0 ? { pressEnter: command.pressEnter } : {}
|
|
241181
|
+
}),
|
|
241182
|
+
wheel: (command) => ({
|
|
241183
|
+
type: "wheel",
|
|
241184
|
+
tabId: command.tabId,
|
|
241185
|
+
deltaX: command.deltaX,
|
|
241186
|
+
deltaY: command.deltaY,
|
|
241187
|
+
...command.x !== void 0 ? { x: command.x } : {},
|
|
241188
|
+
...command.y !== void 0 ? { y: command.y } : {}
|
|
241189
|
+
})
|
|
241190
|
+
};
|
|
241191
|
+
function toHostCommand(command) {
|
|
241192
|
+
return HOST_COMMAND_MAPPERS[command.type](command);
|
|
241193
|
+
}
|
|
241194
|
+
function commandCaptureTabId(command) {
|
|
241195
|
+
switch (command.type) {
|
|
241196
|
+
case "snapshot":
|
|
241197
|
+
case "screenshot": return command.tabId;
|
|
241198
|
+
default: return;
|
|
241098
241199
|
}
|
|
241099
|
-
|
|
241100
|
-
|
|
241200
|
+
}
|
|
241201
|
+
//#endregion
|
|
241202
|
+
//#region src/browser/AgentBrowserHostBridgeDisplay.ts
|
|
241203
|
+
const REMOTE_WEBRTC_UNAVAILABLE_DETAIL = "Remote WebRTC browser display is not available: the current Agent Browser runtime exposes tab control but does not provide a media transport.";
|
|
241204
|
+
const REMOTE_WEBRTC_RELAY_REQUIRED_DETAIL = "Remote WebRTC browser display requires a TURN relay in cloud mode. Configure FORGE_BROWSER_WEBRTC_ICE_SERVERS with authenticated turn: or turns: URLs.";
|
|
241205
|
+
function remoteWebRtcUnavailableError(_input) {
|
|
241206
|
+
return new BrowserError({ detail: REMOTE_WEBRTC_UNAVAILABLE_DETAIL });
|
|
241207
|
+
}
|
|
241208
|
+
function remoteWebRtcRelayRequiredError(_input) {
|
|
241209
|
+
return new BrowserError({ detail: REMOTE_WEBRTC_RELAY_REQUIRED_DETAIL });
|
|
241210
|
+
}
|
|
241211
|
+
function agentBrowserDisplayForRuntimeMode(input) {
|
|
241212
|
+
if (input.mode !== "desktop") {
|
|
241213
|
+
const hasRelay = hasTurnRelay(input.iceServers ?? []);
|
|
241214
|
+
return {
|
|
241215
|
+
transport: "remote-webrtc",
|
|
241216
|
+
status: "degraded",
|
|
241217
|
+
reason: input.requiresRelay === true && !hasRelay ? "network_unreachable" : "transport_unavailable",
|
|
241218
|
+
detail: input.requiresRelay === true && !hasRelay ? REMOTE_WEBRTC_RELAY_REQUIRED_DETAIL : "Remote WebRTC browser display is not attached yet.",
|
|
241219
|
+
...input.viewport ? { viewport: input.viewport } : {},
|
|
241220
|
+
...input.iceServers && input.iceServers.length > 0 ? { iceServers: input.iceServers } : {},
|
|
241221
|
+
reconnect: {
|
|
241222
|
+
status: "ready",
|
|
241223
|
+
attempt: 0
|
|
241224
|
+
}
|
|
241225
|
+
};
|
|
241101
241226
|
}
|
|
241102
|
-
|
|
241103
|
-
|
|
241104
|
-
|
|
241105
|
-
|
|
241106
|
-
|
|
241107
|
-
|
|
241227
|
+
return {
|
|
241228
|
+
transport: "native-embedded",
|
|
241229
|
+
status: "connected",
|
|
241230
|
+
...input.viewport ? { viewport: input.viewport } : {},
|
|
241231
|
+
reconnect: {
|
|
241232
|
+
status: "ready",
|
|
241233
|
+
attempt: 0
|
|
241108
241234
|
}
|
|
241235
|
+
};
|
|
241236
|
+
}
|
|
241237
|
+
function hasTurnRelay(iceServers) {
|
|
241238
|
+
return browserWebRtcIceServersIncludeRelay(iceServers);
|
|
241239
|
+
}
|
|
241240
|
+
async function resolveDisplayIceServers(options) {
|
|
241241
|
+
return options?.resolveDisplayIceServers ? await options.resolveDisplayIceServers() : options?.displayIceServers ?? [];
|
|
241242
|
+
}
|
|
241243
|
+
function webRtcDisplayRequiresRelay(serverConfig) {
|
|
241244
|
+
return serverConfig.mode === "web" && serverConfig.devUrl === void 0;
|
|
241245
|
+
}
|
|
241246
|
+
//#endregion
|
|
241247
|
+
//#region src/browser/AgentBrowserHostBridgeProjection.ts
|
|
241248
|
+
const DEFAULT_URL$1 = "about:blank";
|
|
241249
|
+
const DEFAULT_RENDER_MODE = "interactive";
|
|
241250
|
+
const DEFAULT_AGENT_BROWSER_DISPLAY = {
|
|
241251
|
+
transport: "remote-webrtc",
|
|
241252
|
+
status: "connected",
|
|
241253
|
+
reconnect: {
|
|
241254
|
+
status: "ready",
|
|
241255
|
+
attempt: 0
|
|
241109
241256
|
}
|
|
241110
|
-
|
|
241111
|
-
|
|
241112
|
-
|
|
241113
|
-
|
|
241114
|
-
|
|
241115
|
-
|
|
241116
|
-
|
|
241117
|
-
|
|
241118
|
-
|
|
241119
|
-
|
|
241120
|
-
|
|
241121
|
-
|
|
241122
|
-
|
|
241123
|
-
|
|
241124
|
-
|
|
241125
|
-
|
|
241126
|
-
|
|
241127
|
-
|
|
241128
|
-
|
|
241129
|
-
|
|
241130
|
-
|
|
241131
|
-
|
|
241132
|
-
|
|
241133
|
-
|
|
241134
|
-
|
|
241135
|
-
|
|
241136
|
-
|
|
241137
|
-
|
|
241138
|
-
|
|
241139
|
-
|
|
241140
|
-
|
|
241141
|
-
|
|
241142
|
-
|
|
241143
|
-
|
|
241144
|
-
|
|
241145
|
-
|
|
241146
|
-
|
|
241147
|
-
|
|
241148
|
-
|
|
241149
|
-
|
|
241150
|
-
|
|
241151
|
-
|
|
241152
|
-
|
|
241153
|
-
|
|
241154
|
-
|
|
241155
|
-
|
|
241156
|
-
|
|
241157
|
-
|
|
241158
|
-
|
|
241159
|
-
|
|
241160
|
-
|
|
241161
|
-
|
|
241162
|
-
|
|
241163
|
-
|
|
241164
|
-
|
|
241165
|
-
|
|
241166
|
-
|
|
241167
|
-
|
|
241168
|
-
|
|
241169
|
-
|
|
241170
|
-
|
|
241171
|
-
|
|
241172
|
-
|
|
241173
|
-
|
|
241174
|
-
|
|
241175
|
-
|
|
241176
|
-
|
|
241177
|
-
|
|
241178
|
-
|
|
241179
|
-
|
|
241180
|
-
|
|
241181
|
-
|
|
241182
|
-
|
|
241183
|
-
|
|
241184
|
-
|
|
241185
|
-
|
|
241186
|
-
|
|
241187
|
-
|
|
241188
|
-
|
|
241189
|
-
|
|
241257
|
+
};
|
|
241258
|
+
function nowIso$3() {
|
|
241259
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
241260
|
+
}
|
|
241261
|
+
function toForgeTab(input) {
|
|
241262
|
+
return {
|
|
241263
|
+
tabId: BrowserTabId.make(input.tab.id),
|
|
241264
|
+
restoreKey: input.tab.id,
|
|
241265
|
+
...input.tab.title ? { title: input.tab.title } : {},
|
|
241266
|
+
url: input.tab.url ?? DEFAULT_URL$1,
|
|
241267
|
+
isActive: input.tab.isActive,
|
|
241268
|
+
canGoBack: input.tab.canGoBack ?? false,
|
|
241269
|
+
canGoForward: input.tab.canGoForward ?? false,
|
|
241270
|
+
renderMode: DEFAULT_RENDER_MODE,
|
|
241271
|
+
status: "live",
|
|
241272
|
+
...input.tab.navigationStatus !== void 0 ? { navigationStatus: input.tab.navigationStatus } : {},
|
|
241273
|
+
...input.tab.requestedUrl !== void 0 ? { requestedUrl: input.tab.requestedUrl } : {},
|
|
241274
|
+
...input.tab.navigationError !== void 0 ? { navigationError: input.tab.navigationError } : {},
|
|
241275
|
+
updatedAt: input.updatedAt
|
|
241276
|
+
};
|
|
241277
|
+
}
|
|
241278
|
+
function toForgeSession(input) {
|
|
241279
|
+
return {
|
|
241280
|
+
browserId: BrowserId.make(input.snapshot.browserId),
|
|
241281
|
+
threadId: input.threadId,
|
|
241282
|
+
environmentId: EnvironmentId.make(input.environmentId ?? DEFAULT_BROWSER_ENVIRONMENT_ID),
|
|
241283
|
+
userId: BrowserUserId.make(input.userId ?? "local-user"),
|
|
241284
|
+
agentId: input.agentId ?? null,
|
|
241285
|
+
ownerRole: input.ownerRole ?? "lead",
|
|
241286
|
+
name: input.snapshot.name,
|
|
241287
|
+
visibility: input.snapshot.visibility,
|
|
241288
|
+
viewport: input.snapshot.viewport,
|
|
241289
|
+
display: input.display ?? DEFAULT_AGENT_BROWSER_DISPLAY,
|
|
241290
|
+
sessionId: BrowserSessionId.make(input.snapshot.sessionId),
|
|
241291
|
+
...input.controlState ? {
|
|
241292
|
+
sessionEpoch: input.controlState.sessionEpoch,
|
|
241293
|
+
lastAppliedControlSequence: input.controlState.lastAppliedControlSequence
|
|
241294
|
+
} : {},
|
|
241295
|
+
activeTabId: input.snapshot.activeTabId ? BrowserTabId.make(input.snapshot.activeTabId) : null,
|
|
241296
|
+
tabs: input.snapshot.tabs.map((tab) => toForgeTab({
|
|
241297
|
+
tab,
|
|
241298
|
+
updatedAt: input.snapshot.updatedAt
|
|
241299
|
+
})),
|
|
241300
|
+
updatedAt: input.snapshot.updatedAt
|
|
241301
|
+
};
|
|
241302
|
+
}
|
|
241303
|
+
function toStreamEvent(input) {
|
|
241304
|
+
const base = {
|
|
241305
|
+
eventId: EventId.make(crypto.randomUUID()),
|
|
241306
|
+
browserId: input.browserId,
|
|
241307
|
+
threadId: input.threadId,
|
|
241308
|
+
environmentId: EnvironmentId.make(input.environmentId),
|
|
241309
|
+
userId: BrowserUserId.make(input.userId),
|
|
241310
|
+
agentId: input.agentId,
|
|
241311
|
+
...input.controlState ? { sessionEpoch: input.controlState.sessionEpoch } : {},
|
|
241312
|
+
createdAt: nowIso$3()
|
|
241313
|
+
};
|
|
241314
|
+
switch (input.event.type) {
|
|
241315
|
+
case "session.started": return {
|
|
241316
|
+
...base,
|
|
241317
|
+
type: "session.started",
|
|
241318
|
+
payload: { snapshot: toForgeSession({
|
|
241319
|
+
snapshot: input.event.payload.snapshot,
|
|
241320
|
+
threadId: input.threadId,
|
|
241321
|
+
environmentId: input.environmentId,
|
|
241322
|
+
userId: input.userId,
|
|
241323
|
+
agentId: input.agentId,
|
|
241324
|
+
ownerRole: input.ownerRole,
|
|
241325
|
+
display: input.display,
|
|
241326
|
+
controlState: input.controlState
|
|
241327
|
+
}) }
|
|
241328
|
+
};
|
|
241329
|
+
case "session.updated": return {
|
|
241330
|
+
...base,
|
|
241331
|
+
type: input.event.type,
|
|
241332
|
+
payload: { snapshot: toForgeSession({
|
|
241333
|
+
snapshot: input.event.payload.snapshot,
|
|
241334
|
+
threadId: input.threadId,
|
|
241335
|
+
environmentId: input.environmentId,
|
|
241336
|
+
userId: input.userId,
|
|
241337
|
+
agentId: input.agentId,
|
|
241338
|
+
ownerRole: input.ownerRole,
|
|
241339
|
+
display: input.display,
|
|
241340
|
+
controlState: input.controlState
|
|
241341
|
+
}) }
|
|
241342
|
+
};
|
|
241343
|
+
case "session.closed": return {
|
|
241344
|
+
...base,
|
|
241345
|
+
type: "session.closed",
|
|
241346
|
+
payload: { sessionId: BrowserSessionId.make(input.event.payload.sessionId) }
|
|
241347
|
+
};
|
|
241348
|
+
case "tab.closed": return {
|
|
241349
|
+
...base,
|
|
241350
|
+
type: "tab.closed",
|
|
241351
|
+
payload: {
|
|
241352
|
+
sessionId: BrowserSessionId.make(input.event.sessionId),
|
|
241353
|
+
tabId: BrowserTabId.make(input.event.payload.tabId)
|
|
241190
241354
|
}
|
|
241191
|
-
|
|
241192
|
-
|
|
241193
|
-
|
|
241194
|
-
|
|
241195
|
-
|
|
241196
|
-
|
|
241197
|
-
|
|
241198
|
-
|
|
241199
|
-
|
|
241200
|
-
|
|
241201
|
-
|
|
241202
|
-
|
|
241203
|
-
let current = node;
|
|
241204
|
-
while (current && current !== document.body && current !== document.documentElement && parts.length < 5) {
|
|
241205
|
-
let part = current.tagName.toLowerCase();
|
|
241206
|
-
const name = current.getAttribute("name");
|
|
241207
|
-
if (name) part += `[name="${cssString(name)}"]`;
|
|
241208
|
-
else {
|
|
241209
|
-
const className = Array.from(current.classList).find(Boolean);
|
|
241210
|
-
if (className) part += `.${cssEscape(className)}`;
|
|
241211
|
-
}
|
|
241212
|
-
const parent = current.parentElement;
|
|
241213
|
-
if (parent) {
|
|
241214
|
-
const siblings = Array.from(parent.children).filter((sibling) => sibling.tagName === current?.tagName);
|
|
241215
|
-
if (siblings.length > 1) part += `:nth-of-type(${siblings.indexOf(current) + 1})`;
|
|
241216
|
-
}
|
|
241217
|
-
parts.unshift(part);
|
|
241218
|
-
const candidate = parts.join(" > ");
|
|
241219
|
-
if (document.querySelectorAll(candidate).length === 1) {
|
|
241220
|
-
candidates.push(candidate);
|
|
241221
|
-
break;
|
|
241222
|
-
}
|
|
241223
|
-
current = parent;
|
|
241224
|
-
}
|
|
241225
|
-
const fallback = parts.join(" > ") || node.tagName.toLowerCase();
|
|
241226
|
-
candidates.push(fallback);
|
|
241227
|
-
return Array.from(new Set(candidates));
|
|
241355
|
+
};
|
|
241356
|
+
case "snapshot.ready": return {
|
|
241357
|
+
...base,
|
|
241358
|
+
type: "snapshot.ready",
|
|
241359
|
+
payload: {
|
|
241360
|
+
sessionId: BrowserSessionId.make(input.event.sessionId),
|
|
241361
|
+
tabId: BrowserTabId.make(input.event.payload.tabId),
|
|
241362
|
+
captureId: BrowserCaptureId.make(input.event.payload.captureId ?? crypto.randomUUID()),
|
|
241363
|
+
capturedAt: input.event.payload.capturedAt,
|
|
241364
|
+
url: input.event.payload.url ?? DEFAULT_URL$1,
|
|
241365
|
+
viewport: input.event.payload.viewport,
|
|
241366
|
+
content: input.event.payload.content
|
|
241228
241367
|
}
|
|
241229
|
-
|
|
241230
|
-
|
|
241368
|
+
};
|
|
241369
|
+
case "screenshot.ready": return {
|
|
241370
|
+
...base,
|
|
241371
|
+
type: "screenshot.ready",
|
|
241372
|
+
payload: {
|
|
241373
|
+
sessionId: BrowserSessionId.make(input.event.sessionId),
|
|
241374
|
+
tabId: BrowserTabId.make(input.event.payload.tabId),
|
|
241375
|
+
captureId: BrowserCaptureId.make(input.event.payload.captureId ?? crypto.randomUUID()),
|
|
241376
|
+
capturedAt: input.event.payload.capturedAt,
|
|
241377
|
+
url: input.event.payload.url ?? DEFAULT_URL$1,
|
|
241378
|
+
viewport: input.event.payload.viewport,
|
|
241379
|
+
imageDataUrl: input.event.payload.imageDataUrl
|
|
241231
241380
|
}
|
|
241232
|
-
|
|
241233
|
-
|
|
241381
|
+
};
|
|
241382
|
+
case "console.logs.ready": return {
|
|
241383
|
+
...base,
|
|
241384
|
+
type: "console.logs.ready",
|
|
241385
|
+
payload: {
|
|
241386
|
+
sessionId: BrowserSessionId.make(input.event.sessionId),
|
|
241387
|
+
tabId: BrowserTabId.make(input.event.payload.tabId),
|
|
241388
|
+
logs: input.event.payload.logs.map((log) => ({
|
|
241389
|
+
level: log.level === "warn" ? "warning" : log.level === "log" ? "info" : log.level,
|
|
241390
|
+
message: log.message,
|
|
241391
|
+
createdAt: log.timestamp
|
|
241392
|
+
}))
|
|
241234
241393
|
}
|
|
241235
|
-
}
|
|
241236
|
-
|
|
241237
|
-
|
|
241238
|
-
|
|
241239
|
-
|
|
241240
|
-
|
|
241241
|
-
|
|
241242
|
-
|
|
241243
|
-
|
|
241244
|
-
|
|
241245
|
-
|
|
241246
|
-
|
|
241247
|
-
|
|
241248
|
-
|
|
241249
|
-
const locator = this.locatorFor(action);
|
|
241250
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241251
|
-
await locator.dblclick(timeoutOptions(action.timeoutMs));
|
|
241252
|
-
}
|
|
241253
|
-
async fill(action, value) {
|
|
241254
|
-
const locator = this.locatorFor(action);
|
|
241255
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241256
|
-
await locator.fill(value, timeoutOptions(action.timeoutMs));
|
|
241257
|
-
}
|
|
241258
|
-
async type(action, value) {
|
|
241259
|
-
const locator = this.locatorFor(action);
|
|
241260
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241261
|
-
await locator.pressSequentially(value, timeoutOptions(action.timeoutMs));
|
|
241262
|
-
}
|
|
241263
|
-
async press(action, key) {
|
|
241264
|
-
const locator = this.locatorFor(action);
|
|
241265
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241266
|
-
await locator.press(key, timeoutOptions(action.timeoutMs));
|
|
241267
|
-
}
|
|
241268
|
-
async select(action, value) {
|
|
241269
|
-
const locator = this.locatorFor(action);
|
|
241270
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241271
|
-
await locator.selectOption(value, timeoutOptions(action.timeoutMs));
|
|
241272
|
-
}
|
|
241273
|
-
async check(action, checked) {
|
|
241274
|
-
const locator = this.locatorFor(action);
|
|
241275
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241276
|
-
await locator.setChecked(checked, timeoutOptions(action.timeoutMs));
|
|
241277
|
-
}
|
|
241278
|
-
async text(action) {
|
|
241279
|
-
const locator = this.locatorFor(action);
|
|
241280
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241281
|
-
return locator.textContent(timeoutOptions(action.timeoutMs));
|
|
241282
|
-
}
|
|
241283
|
-
async innerText(action) {
|
|
241284
|
-
const locator = this.locatorFor(action);
|
|
241285
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241286
|
-
return locator.innerText(timeoutOptions(action.timeoutMs));
|
|
241287
|
-
}
|
|
241288
|
-
async allTextContents(action) {
|
|
241289
|
-
return this.locatorFor(action).allTextContents();
|
|
241290
|
-
}
|
|
241291
|
-
async attribute(action, name) {
|
|
241292
|
-
const locator = this.locatorFor(action);
|
|
241293
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241294
|
-
return locator.getAttribute(name, timeoutOptions(action.timeoutMs));
|
|
241295
|
-
}
|
|
241296
|
-
async isVisible(action) {
|
|
241297
|
-
const locator = this.locatorFor(action);
|
|
241298
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241299
|
-
return locator.isVisible(timeoutOptions(action.timeoutMs));
|
|
241300
|
-
}
|
|
241301
|
-
async isEnabled(action) {
|
|
241302
|
-
const locator = this.locatorFor(action);
|
|
241303
|
-
await assertSingle(locator.count(), locatorActionLabel(action));
|
|
241304
|
-
return locator.isEnabled(timeoutOptions(action.timeoutMs));
|
|
241305
|
-
}
|
|
241306
|
-
async locatorCount(action) {
|
|
241307
|
-
return this.locatorFor(action).count();
|
|
241308
|
-
}
|
|
241309
|
-
async waitForLocator(action, state) {
|
|
241310
|
-
await this.locatorFor(action).waitFor({
|
|
241311
|
-
...state === void 0 ? {} : { state },
|
|
241312
|
-
...timeoutOptions(action.timeoutMs)
|
|
241313
|
-
});
|
|
241314
|
-
}
|
|
241315
|
-
async cuaMove(point) {
|
|
241316
|
-
await this.page.mouse.move(point.x, point.y);
|
|
241317
|
-
}
|
|
241318
|
-
async cuaClick(point) {
|
|
241319
|
-
await this.page.mouse.click(point.x, point.y, { button: mouseButton(point.button) });
|
|
241320
|
-
}
|
|
241321
|
-
async cuaDoubleClick(point) {
|
|
241322
|
-
await this.page.mouse.dblclick(point.x, point.y);
|
|
241323
|
-
}
|
|
241324
|
-
async cuaDrag(path) {
|
|
241325
|
-
const [start, ...rest] = path;
|
|
241326
|
-
if (!start) return;
|
|
241327
|
-
await this.page.mouse.move(start.x, start.y);
|
|
241328
|
-
await this.page.mouse.down();
|
|
241329
|
-
for (const point of rest) await this.page.mouse.move(point.x, point.y);
|
|
241330
|
-
await this.page.mouse.up();
|
|
241331
|
-
}
|
|
241332
|
-
async cuaScroll(input) {
|
|
241333
|
-
await this.page.mouse.move(input.x, input.y);
|
|
241334
|
-
await this.page.mouse.wheel(input.scrollX, input.scrollY);
|
|
241335
|
-
}
|
|
241336
|
-
async cuaType(text) {
|
|
241337
|
-
await this.page.keyboard.type(text);
|
|
241338
|
-
}
|
|
241339
|
-
async cuaKeypress(keys) {
|
|
241340
|
-
await this.page.keyboard.press(keys.join("+"));
|
|
241341
|
-
}
|
|
241342
|
-
async visibleDom() {
|
|
241343
|
-
return this.page.evaluate(`(() => {
|
|
241344
|
-
let nextId = Number(document.documentElement.getAttribute("data-agent-browser-next-node-id") || "1");
|
|
241345
|
-
function walk(element) {
|
|
241346
|
-
const rect = element.getBoundingClientRect();
|
|
241347
|
-
const style = window.getComputedStyle(element);
|
|
241348
|
-
if (rect.width <= 0 || rect.height <= 0 || style.display === "none" || style.visibility === "hidden") {
|
|
241349
|
-
return undefined;
|
|
241350
|
-
}
|
|
241351
|
-
let nodeId = element.getAttribute("data-agent-browser-node-id");
|
|
241352
|
-
if (!nodeId) {
|
|
241353
|
-
nodeId = String(nextId++);
|
|
241354
|
-
element.setAttribute("data-agent-browser-node-id", nodeId);
|
|
241355
|
-
}
|
|
241356
|
-
const tagName = element.tagName.toLowerCase();
|
|
241357
|
-
const visibleText = (element.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 120) || undefined;
|
|
241358
|
-
const role = element.getAttribute("role") ?? undefined;
|
|
241359
|
-
const ariaName = element.getAttribute("aria-label") ?? undefined;
|
|
241360
|
-
const children = Array.from(element.children).map(walk).filter(Boolean);
|
|
241361
|
-
return {
|
|
241362
|
-
...(ariaName ? { ariaName } : {}),
|
|
241363
|
-
boundingBox: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
241364
|
-
...(children.length ? { children } : {}),
|
|
241365
|
-
nodeId,
|
|
241366
|
-
preview: (tagName + (ariaName ? ' "' + ariaName + '"' : "") + (visibleText ? " " + visibleText : "")).slice(0, 180),
|
|
241367
|
-
...(role ? { role } : {}),
|
|
241368
|
-
tagName,
|
|
241369
|
-
...(visibleText ? { visibleText } : {})
|
|
241370
|
-
};
|
|
241371
|
-
}
|
|
241372
|
-
document.documentElement.setAttribute("data-agent-browser-next-node-id", String(nextId));
|
|
241373
|
-
return walk(document.body) ?? { nodeId: "body", preview: "body", tagName: "body" };
|
|
241374
|
-
})()`);
|
|
241375
|
-
}
|
|
241376
|
-
async domClick(nodeId) {
|
|
241377
|
-
await this.byNodeId(nodeId).click();
|
|
241378
|
-
}
|
|
241379
|
-
async domDoubleClick(nodeId) {
|
|
241380
|
-
await this.byNodeId(nodeId).dblclick();
|
|
241381
|
-
}
|
|
241382
|
-
async domScroll(input) {
|
|
241383
|
-
if (input.nodeId) {
|
|
241384
|
-
/* v8 ignore next -- browser-executed DOM callback runs outside Node coverage */
|
|
241385
|
-
await this.byNodeId(input.nodeId).evaluate((element, delta) => element.scrollBy(delta.x, delta.y), {
|
|
241386
|
-
x: input.x,
|
|
241387
|
-
y: input.y
|
|
241388
|
-
});
|
|
241389
|
-
return;
|
|
241390
|
-
}
|
|
241391
|
-
await this.page.mouse.wheel(input.x, input.y);
|
|
241392
|
-
}
|
|
241393
|
-
async domType(text) {
|
|
241394
|
-
await this.page.keyboard.type(text);
|
|
241395
|
-
}
|
|
241396
|
-
async domKeypress(keys) {
|
|
241397
|
-
await this.page.keyboard.press(keys.join("+"));
|
|
241398
|
-
}
|
|
241399
|
-
async clipboardReadText() {
|
|
241400
|
-
/* v8 ignore next -- browser-executed callback runs outside Node coverage */
|
|
241401
|
-
return this.page.evaluate(() => navigator.clipboard.readText());
|
|
241402
|
-
}
|
|
241403
|
-
async clipboardWriteText(text) {
|
|
241404
|
-
/* v8 ignore next -- browser-executed callback runs outside Node coverage */
|
|
241405
|
-
await this.page.evaluate((value) => navigator.clipboard.writeText(value), text);
|
|
241406
|
-
}
|
|
241407
|
-
async logs(limit = 100) {
|
|
241408
|
-
return this.logEntries.slice(-limit);
|
|
241394
|
+
};
|
|
241395
|
+
case "action.failed": return {
|
|
241396
|
+
...base,
|
|
241397
|
+
type: "action.failed",
|
|
241398
|
+
payload: {
|
|
241399
|
+
commandType: input.event.payload.commandType,
|
|
241400
|
+
message: input.event.payload.message,
|
|
241401
|
+
...input.event.payload.tabId ? { tabId: BrowserTabId.make(input.event.payload.tabId) } : {}
|
|
241402
|
+
}
|
|
241403
|
+
};
|
|
241404
|
+
case "dom.visible.ready":
|
|
241405
|
+
case "element.info.ready":
|
|
241406
|
+
case "element.screenshot.ready":
|
|
241407
|
+
case "clipboard.text.ready": return null;
|
|
241409
241408
|
}
|
|
241410
|
-
|
|
241411
|
-
|
|
241409
|
+
}
|
|
241410
|
+
function toCommandResult(input) {
|
|
241411
|
+
return {
|
|
241412
|
+
ok: input.result.ok,
|
|
241413
|
+
snapshot: toForgeSession({
|
|
241414
|
+
snapshot: input.result.snapshot,
|
|
241415
|
+
threadId: input.command.threadId,
|
|
241416
|
+
environmentId: input.command.environmentId,
|
|
241417
|
+
userId: input.command.userId,
|
|
241418
|
+
display: input.display,
|
|
241419
|
+
controlState: input.controlState
|
|
241420
|
+
}),
|
|
241421
|
+
sessionEpoch: input.controlState.sessionEpoch,
|
|
241422
|
+
acknowledgedControlSequence: input.acknowledgedControlSequence,
|
|
241423
|
+
...input.result.detail ? { detail: input.result.detail } : {},
|
|
241424
|
+
...input.latestCapture ? { latestCapture: input.latestCapture } : {}
|
|
241425
|
+
};
|
|
241426
|
+
}
|
|
241427
|
+
function toCommandLatestCapture(input) {
|
|
241428
|
+
const tabId = commandCaptureTabId(input.command.command);
|
|
241429
|
+
if (tabId === void 0 || input.event.type !== "snapshot.ready" && input.event.type !== "screenshot.ready" || input.event.sessionId !== BrowserSessionId.make(input.command.threadId) || input.event.payload.tabId !== tabId) return;
|
|
241430
|
+
const mapped = toStreamEvent({
|
|
241431
|
+
event: input.event,
|
|
241432
|
+
threadId: input.command.threadId,
|
|
241433
|
+
browserId: input.browserId,
|
|
241434
|
+
environmentId: input.command.environmentId ?? DEFAULT_BROWSER_ENVIRONMENT_ID,
|
|
241435
|
+
userId: input.command.userId ?? "local-user",
|
|
241436
|
+
agentId: null,
|
|
241437
|
+
ownerRole: "lead",
|
|
241438
|
+
display: input.display,
|
|
241439
|
+
controlState: input.controlState
|
|
241440
|
+
});
|
|
241441
|
+
return mapped?.type === "snapshot.ready" || mapped?.type === "screenshot.ready" ? mapped : void 0;
|
|
241442
|
+
}
|
|
241443
|
+
//#endregion
|
|
241444
|
+
//#region src/browser/AgentBrowserHostBridgeShared.ts
|
|
241445
|
+
const DEFAULT_AGENT_BROWSER_ID = "embedded";
|
|
241446
|
+
const DEFAULT_URL = "about:blank";
|
|
241447
|
+
const isBrowserError = require_Schema$1.is(BrowserError);
|
|
241448
|
+
function nowIso$2() {
|
|
241449
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
241450
|
+
}
|
|
241451
|
+
function sessionIdForThread(threadId) {
|
|
241452
|
+
return BrowserSessionId.make(threadId);
|
|
241453
|
+
}
|
|
241454
|
+
function browserIdFrom(input) {
|
|
241455
|
+
return BrowserId.make(input.browserId ?? "embedded");
|
|
241456
|
+
}
|
|
241457
|
+
function toBrowserError(error, fallback) {
|
|
241458
|
+
console.warn("[browser] Agent Browser host bridge operation failed", {
|
|
241459
|
+
operation: fallback,
|
|
241460
|
+
error
|
|
241461
|
+
});
|
|
241462
|
+
if (isBrowserError(error)) return error;
|
|
241463
|
+
if (error instanceof Error && error.message.trim().length > 0) return new BrowserError({ detail: error.message.trim() });
|
|
241464
|
+
return new BrowserError({ detail: fallback });
|
|
241465
|
+
}
|
|
241466
|
+
//#endregion
|
|
241467
|
+
//#region src/browser/AgentBrowserHostBridgeTimeouts.ts
|
|
241468
|
+
const DEFAULT_AGENT_BROWSER_CONTROL_COMMAND_TIMEOUT_MS = 1500;
|
|
241469
|
+
const DEFAULT_AGENT_BROWSER_FAST_COMMAND_TIMEOUT_MS = 1e4;
|
|
241470
|
+
const DEFAULT_AGENT_BROWSER_NAVIGATION_COMMAND_TIMEOUT_MS = 9e4;
|
|
241471
|
+
const DEFAULT_AGENT_BROWSER_CAPTURE_COMMAND_TIMEOUT_MS = 9e4;
|
|
241472
|
+
const DEFAULT_AGENT_BROWSER_BRIDGE_OPEN_TIMEOUT_MS = 9e4;
|
|
241473
|
+
const DEFAULT_AGENT_BROWSER_BRIDGE_CLOSE_TIMEOUT_MS = 3e4;
|
|
241474
|
+
const DEFAULT_AGENT_BROWSER_PLAYWRIGHT_BACKEND_CLOSE_TIMEOUT_MS = 1e4;
|
|
241475
|
+
function positiveTimeoutMs(value) {
|
|
241476
|
+
if (!value) return;
|
|
241477
|
+
const parsed = Number.parseInt(value, 10);
|
|
241478
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
241479
|
+
}
|
|
241480
|
+
function agentBrowserCommandTimeoutMs(command, env = process.env) {
|
|
241481
|
+
const explicitCommandTimeout = positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_COMMAND_TIMEOUT_MS);
|
|
241482
|
+
if (explicitCommandTimeout !== void 0) return explicitCommandTimeout;
|
|
241483
|
+
if (isAgentBrowserBoundedControlCommand(command)) return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_CONTROL_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_CONTROL_COMMAND_TIMEOUT_MS;
|
|
241484
|
+
switch (command.type) {
|
|
241485
|
+
case "screenshot":
|
|
241486
|
+
case "snapshot": return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_CAPTURE_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_CAPTURE_COMMAND_TIMEOUT_MS;
|
|
241487
|
+
case "newTab":
|
|
241488
|
+
case "navigate":
|
|
241489
|
+
case "goBack":
|
|
241490
|
+
case "goForward":
|
|
241491
|
+
case "reload": return DEFAULT_AGENT_BROWSER_NAVIGATION_COMMAND_TIMEOUT_MS;
|
|
241492
|
+
default: return DEFAULT_AGENT_BROWSER_FAST_COMMAND_TIMEOUT_MS;
|
|
241412
241493
|
}
|
|
241413
|
-
|
|
241414
|
-
|
|
241494
|
+
}
|
|
241495
|
+
function agentBrowserCloseTimeoutMs(env = process.env) {
|
|
241496
|
+
return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_CLOSE_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_BRIDGE_CLOSE_TIMEOUT_MS;
|
|
241497
|
+
}
|
|
241498
|
+
function agentBrowserOpenTimeoutMs(env = process.env) {
|
|
241499
|
+
return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_OPEN_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_BRIDGE_OPEN_TIMEOUT_MS;
|
|
241500
|
+
}
|
|
241501
|
+
function agentBrowserFastTimeoutMs(env = process.env) {
|
|
241502
|
+
return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_COMMAND_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_FAST_COMMAND_TIMEOUT_MS;
|
|
241503
|
+
}
|
|
241504
|
+
function agentBrowserPlaywrightBackendCloseTimeoutMs(env = process.env) {
|
|
241505
|
+
return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_PLAYWRIGHT_BACKEND_CLOSE_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_PLAYWRIGHT_BACKEND_CLOSE_TIMEOUT_MS;
|
|
241506
|
+
}
|
|
241507
|
+
async function withAgentBrowserBridgeTimeout(label, promise, timeoutMs) {
|
|
241508
|
+
let timeout;
|
|
241509
|
+
try {
|
|
241510
|
+
return await Promise.race([promise, new Promise((_, reject) => {
|
|
241511
|
+
timeout = setTimeout(() => {
|
|
241512
|
+
reject(/* @__PURE__ */ new Error(`${label} timed out after ${Math.ceil(timeoutMs / 1e3)}s.`));
|
|
241513
|
+
}, timeoutMs);
|
|
241514
|
+
})]);
|
|
241515
|
+
} finally {
|
|
241516
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
241415
241517
|
}
|
|
241416
|
-
};
|
|
241417
|
-
function locatorActionLabel(action) {
|
|
241418
|
-
/* v8 ignore next -- valid public calls always supply selector or query; malformed calls are rejected downstream. */
|
|
241419
|
-
return action.selector ?? action.query?.kind ?? "locator";
|
|
241420
241518
|
}
|
|
241421
|
-
function
|
|
241422
|
-
|
|
241423
|
-
|
|
241424
|
-
|
|
241519
|
+
async function openAgentBrowserSessionReady(bridge, input) {
|
|
241520
|
+
let resolveStarted;
|
|
241521
|
+
const started = new Promise((resolve) => {
|
|
241522
|
+
resolveStarted = resolve;
|
|
241523
|
+
});
|
|
241524
|
+
const unsubscribe = await bridge.subscribe({
|
|
241525
|
+
sessionId: input.sessionId,
|
|
241526
|
+
...input.browserId ? { browserId: input.browserId } : {}
|
|
241527
|
+
}, (event) => {
|
|
241528
|
+
if (event.type === "session.started" && event.sessionId === input.sessionId) resolveStarted(event.payload.snapshot);
|
|
241529
|
+
});
|
|
241530
|
+
try {
|
|
241531
|
+
return await Promise.race([bridge.openSession(input), started]);
|
|
241532
|
+
} finally {
|
|
241533
|
+
unsubscribe();
|
|
241534
|
+
}
|
|
241425
241535
|
}
|
|
241426
|
-
function
|
|
241427
|
-
|
|
241536
|
+
function isAgentBrowserBridgeTimeoutError(error) {
|
|
241537
|
+
const message = [
|
|
241538
|
+
isBrowserError(error) ? error.detail : void 0,
|
|
241539
|
+
error instanceof Error ? error.message : void 0,
|
|
241540
|
+
error instanceof Error ? error.stack : void 0,
|
|
241541
|
+
typeof error === "string" ? error : void 0,
|
|
241542
|
+
(() => {
|
|
241543
|
+
try {
|
|
241544
|
+
return JSON.stringify(error);
|
|
241545
|
+
} catch {
|
|
241546
|
+
return;
|
|
241547
|
+
}
|
|
241548
|
+
})(),
|
|
241549
|
+
String(error)
|
|
241550
|
+
].filter((part) => typeof part === "string" && part.length > 0).join("\n");
|
|
241551
|
+
return /timed out after \d+s\./.test(message);
|
|
241428
241552
|
}
|
|
241429
|
-
function
|
|
241430
|
-
|
|
241553
|
+
async function closeAgentBrowserBackendWithTimeout(closeBackend, env = process.env) {
|
|
241554
|
+
try {
|
|
241555
|
+
await withAgentBrowserBridgeTimeout("Agent Browser Playwright backend close", closeBackend(), agentBrowserPlaywrightBackendCloseTimeoutMs(env));
|
|
241556
|
+
} catch (error) {
|
|
241557
|
+
if (!isAgentBrowserBridgeTimeoutError(error)) throw error;
|
|
241558
|
+
console.warn("[browser] Agent Browser Playwright backend close timed out; continuing", { timeoutMs: agentBrowserPlaywrightBackendCloseTimeoutMs(env) });
|
|
241559
|
+
}
|
|
241431
241560
|
}
|
|
241432
|
-
|
|
241561
|
+
//#endregion
|
|
241562
|
+
//#region src/browser/browserControlProtocol.ts
|
|
241563
|
+
function resolveBrowserControlDecision(state, metadata) {
|
|
241564
|
+
const hasSessionEpoch = metadata.sessionEpoch !== void 0;
|
|
241565
|
+
const hasControlSequence = metadata.controlSequence !== void 0;
|
|
241566
|
+
const expectedControlSequence = state.lastAppliedControlSequence + 1;
|
|
241567
|
+
if (!hasSessionEpoch && !hasControlSequence) return {
|
|
241568
|
+
type: "apply",
|
|
241569
|
+
controlSequence: expectedControlSequence
|
|
241570
|
+
};
|
|
241571
|
+
if (!hasSessionEpoch || !hasControlSequence) return {
|
|
241572
|
+
type: "reject",
|
|
241573
|
+
reason: "incomplete_metadata",
|
|
241574
|
+
expectedSessionEpoch: state.sessionEpoch,
|
|
241575
|
+
expectedControlSequence,
|
|
241576
|
+
acknowledgedControlSequence: state.lastAppliedControlSequence
|
|
241577
|
+
};
|
|
241578
|
+
if (metadata.sessionEpoch !== state.sessionEpoch) return {
|
|
241579
|
+
type: "reject",
|
|
241580
|
+
reason: "stale_epoch",
|
|
241581
|
+
expectedSessionEpoch: state.sessionEpoch,
|
|
241582
|
+
acknowledgedControlSequence: state.lastAppliedControlSequence
|
|
241583
|
+
};
|
|
241584
|
+
if (metadata.controlSequence <= state.lastAppliedControlSequence) return {
|
|
241585
|
+
type: "duplicate",
|
|
241586
|
+
acknowledgedControlSequence: state.lastAppliedControlSequence
|
|
241587
|
+
};
|
|
241588
|
+
if (metadata.controlSequence !== expectedControlSequence) return {
|
|
241589
|
+
type: "reject",
|
|
241590
|
+
reason: "sequence_gap",
|
|
241591
|
+
expectedControlSequence,
|
|
241592
|
+
acknowledgedControlSequence: state.lastAppliedControlSequence
|
|
241593
|
+
};
|
|
241433
241594
|
return {
|
|
241434
|
-
|
|
241435
|
-
|
|
241436
|
-
saveAs: (path) => download.saveAs(path),
|
|
241437
|
-
suggestedFilename: () => download.suggestedFilename(),
|
|
241438
|
-
url: () => download.url()
|
|
241595
|
+
type: "apply",
|
|
241596
|
+
controlSequence: metadata.controlSequence
|
|
241439
241597
|
};
|
|
241440
241598
|
}
|
|
241441
|
-
|
|
241442
|
-
|
|
241443
|
-
|
|
241599
|
+
function browserControlRejectionDetail(decision) {
|
|
241600
|
+
switch (decision.reason) {
|
|
241601
|
+
case "stale_epoch": return `Browser session is stale. Expected epoch ${decision.expectedSessionEpoch}.`;
|
|
241602
|
+
case "sequence_gap": return `Browser control sequence is out of order. Expected ${decision.expectedControlSequence}.`;
|
|
241603
|
+
case "incomplete_metadata": return "Browser control ordering requires both sessionEpoch and controlSequence.";
|
|
241604
|
+
}
|
|
241444
241605
|
}
|
|
241445
|
-
|
|
241606
|
+
//#endregion
|
|
241607
|
+
//#region src/browser/AgentBrowserHostBridgeManagerCommands.ts
|
|
241608
|
+
async function duplicateControlResult(input) {
|
|
241609
|
+
const snapshot = await withAgentBrowserBridgeTimeout("Agent Browser duplicate command session read", input.bridge.getSession({
|
|
241610
|
+
sessionId: input.hostSessionId,
|
|
241611
|
+
...input.commandInput.browserId ? { browserId: input.commandInput.browserId } : {}
|
|
241612
|
+
}), agentBrowserFastTimeoutMs());
|
|
241613
|
+
if (!snapshot) throw new BrowserError({ detail: "Browser session not found." });
|
|
241614
|
+
const controlState = input.state.controlStateFor(input.hostSessionId);
|
|
241446
241615
|
return {
|
|
241447
|
-
|
|
241448
|
-
|
|
241449
|
-
|
|
241450
|
-
|
|
241616
|
+
ok: true,
|
|
241617
|
+
snapshot: toForgeSession({
|
|
241618
|
+
snapshot,
|
|
241619
|
+
threadId: input.commandInput.threadId,
|
|
241620
|
+
environmentId: input.commandInput.environmentId,
|
|
241621
|
+
userId: input.commandInput.userId,
|
|
241622
|
+
display: input.state.displayForSnapshot(snapshot),
|
|
241623
|
+
controlState
|
|
241624
|
+
}),
|
|
241625
|
+
sessionEpoch: controlState.sessionEpoch,
|
|
241626
|
+
acknowledgedControlSequence: input.acknowledgedControlSequence,
|
|
241627
|
+
detail: "Browser control was already applied"
|
|
241628
|
+
};
|
|
241629
|
+
}
|
|
241630
|
+
async function dispatchHostCommand(input) {
|
|
241631
|
+
try {
|
|
241632
|
+
return await withAgentBrowserBridgeTimeout(`Agent Browser command ${input.commandInput.command.type}`, input.bridge.command({
|
|
241633
|
+
...input.hostIdentity,
|
|
241634
|
+
command: toHostCommand(input.commandInput.command)
|
|
241635
|
+
}), agentBrowserCommandTimeoutMs(input.commandInput.command));
|
|
241636
|
+
} catch (error) {
|
|
241637
|
+
if (!(isAgentBrowserBridgeTimeoutError(error) && isAgentBrowserBoundedControlCommand(input.commandInput.command) && input.preCommandSnapshot !== null)) throw error;
|
|
241638
|
+
console.warn("[browser] Agent Browser control command timed out after dispatch", {
|
|
241639
|
+
commandType: input.commandInput.command.type,
|
|
241640
|
+
sessionId: input.hostIdentity.sessionId,
|
|
241641
|
+
...input.commandInput.browserId ? { browserId: input.commandInput.browserId } : {}
|
|
241642
|
+
});
|
|
241643
|
+
return {
|
|
241644
|
+
ok: true,
|
|
241645
|
+
snapshot: snapshotAfterTimedOutControl(input.preCommandSnapshot, input.commandInput.command),
|
|
241646
|
+
detail: "Agent Browser control command dispatch timed out before post-action observation completed."
|
|
241647
|
+
};
|
|
241648
|
+
}
|
|
241649
|
+
}
|
|
241650
|
+
async function executeHostCommand(input) {
|
|
241651
|
+
const captureTabId = commandCaptureTabId(input.commandInput.command);
|
|
241652
|
+
const hostIdentity = {
|
|
241653
|
+
sessionId: input.hostSessionId,
|
|
241654
|
+
...input.commandInput.browserId ? { browserId: input.commandInput.browserId } : {}
|
|
241451
241655
|
};
|
|
241656
|
+
const preCommandSnapshot = isAgentBrowserBoundedControlCommand(input.commandInput.command) ? await withAgentBrowserBridgeTimeout(`Agent Browser command ${input.commandInput.command.type} preflight session read`, input.bridge.getSession(hostIdentity), agentBrowserCommandTimeoutMs(input.commandInput.command)).catch(() => null) : null;
|
|
241657
|
+
let latestCaptureEvent;
|
|
241658
|
+
const unsubscribeCapture = captureTabId ? await input.bridge.subscribe(hostIdentity, (event) => {
|
|
241659
|
+
if ((event.type === "snapshot.ready" || event.type === "screenshot.ready") && event.sessionId === input.hostSessionId && event.payload.tabId === captureTabId) latestCaptureEvent = event;
|
|
241660
|
+
}) : void 0;
|
|
241661
|
+
let result;
|
|
241662
|
+
try {
|
|
241663
|
+
result = await dispatchHostCommand({
|
|
241664
|
+
...input,
|
|
241665
|
+
hostIdentity,
|
|
241666
|
+
preCommandSnapshot
|
|
241667
|
+
});
|
|
241668
|
+
} finally {
|
|
241669
|
+
unsubscribeCapture?.();
|
|
241670
|
+
}
|
|
241671
|
+
if (input.commandInput.command.type === "closeTab") {
|
|
241672
|
+
await require_Schema$1.runPromise(input.options?.displayPeers?.closeForSession({
|
|
241673
|
+
sessionId: input.hostSessionId,
|
|
241674
|
+
tabId: input.commandInput.command.tabId
|
|
241675
|
+
}) ?? require_Schema$1.void_);
|
|
241676
|
+
input.state.clearConnectedDisplay({
|
|
241677
|
+
sessionId: input.hostSessionId,
|
|
241678
|
+
tabId: input.commandInput.command.tabId
|
|
241679
|
+
});
|
|
241680
|
+
}
|
|
241681
|
+
const display = input.state.displayForSnapshot(result.snapshot);
|
|
241682
|
+
const controlState = input.state.controlStateFor(input.hostSessionId);
|
|
241683
|
+
controlState.lastAppliedControlSequence = input.controlSequence;
|
|
241684
|
+
return toCommandResult({
|
|
241685
|
+
result,
|
|
241686
|
+
command: input.commandInput,
|
|
241687
|
+
display,
|
|
241688
|
+
controlState,
|
|
241689
|
+
acknowledgedControlSequence: input.controlSequence,
|
|
241690
|
+
latestCapture: latestCaptureEvent ? toCommandLatestCapture({
|
|
241691
|
+
event: latestCaptureEvent,
|
|
241692
|
+
command: input.commandInput,
|
|
241693
|
+
browserId: BrowserId.make(result.snapshot.browserId),
|
|
241694
|
+
display,
|
|
241695
|
+
controlState
|
|
241696
|
+
}) : void 0
|
|
241697
|
+
});
|
|
241452
241698
|
}
|
|
241453
|
-
function
|
|
241454
|
-
|
|
241455
|
-
|
|
241456
|
-
|
|
241699
|
+
async function executeOrderedHostCommand(input) {
|
|
241700
|
+
const hostSessionId = sessionIdForThread(input.commandInput.threadId);
|
|
241701
|
+
return input.state.runSerializedCommand(hostSessionId, async () => {
|
|
241702
|
+
const controlDecision = resolveBrowserControlDecision(input.state.controlStateFor(hostSessionId), input.commandInput);
|
|
241703
|
+
if (controlDecision.type === "reject") throw new BrowserError({ detail: browserControlRejectionDetail(controlDecision) });
|
|
241704
|
+
if (controlDecision.type === "duplicate") return duplicateControlResult({
|
|
241705
|
+
...input,
|
|
241706
|
+
hostSessionId,
|
|
241707
|
+
acknowledgedControlSequence: controlDecision.acknowledgedControlSequence
|
|
241708
|
+
});
|
|
241709
|
+
return executeHostCommand({
|
|
241710
|
+
...input,
|
|
241711
|
+
hostSessionId,
|
|
241712
|
+
controlSequence: controlDecision.controlSequence
|
|
241713
|
+
});
|
|
241714
|
+
});
|
|
241457
241715
|
}
|
|
241458
241716
|
//#endregion
|
|
241459
|
-
//#region
|
|
241460
|
-
|
|
241461
|
-
|
|
241462
|
-
|
|
241463
|
-
|
|
241464
|
-
|
|
241465
|
-
|
|
241466
|
-
|
|
241467
|
-
|
|
241468
|
-
|
|
241469
|
-
|
|
241470
|
-
|
|
241471
|
-
|
|
241472
|
-
|
|
241473
|
-
|
|
241474
|
-
|
|
241475
|
-
|
|
241717
|
+
//#region src/browser/AgentBrowserHostBridgeManagerState.ts
|
|
241718
|
+
function resolveDisplay(snapshot, options) {
|
|
241719
|
+
if (typeof options?.display === "function") return options.display(snapshot);
|
|
241720
|
+
return options?.display ?? DEFAULT_AGENT_BROWSER_DISPLAY;
|
|
241721
|
+
}
|
|
241722
|
+
function subscriptionMatchesEvent(subscription, event) {
|
|
241723
|
+
if (subscription.input.threadId && subscription.input.threadId !== event.threadId) return false;
|
|
241724
|
+
if (subscription.input.browserId && subscription.input.browserId !== event.browserId) return false;
|
|
241725
|
+
if (subscription.input.environmentId && subscription.input.environmentId !== event.environmentId) return false;
|
|
241726
|
+
if (subscription.input.userId && subscription.input.userId !== event.userId) return false;
|
|
241727
|
+
return true;
|
|
241728
|
+
}
|
|
241729
|
+
function createAgentBrowserHostBridgeState(options) {
|
|
241730
|
+
const localSubscriptions = /* @__PURE__ */ new Set();
|
|
241731
|
+
const connectedDisplays = /* @__PURE__ */ new Map();
|
|
241732
|
+
const controlStates = /* @__PURE__ */ new Map();
|
|
241733
|
+
const commandQueues = /* @__PURE__ */ new Map();
|
|
241734
|
+
const controlStateFor = (sessionId) => {
|
|
241735
|
+
const existing = controlStates.get(sessionId);
|
|
241736
|
+
if (existing) return existing;
|
|
241737
|
+
const created = {
|
|
241738
|
+
sessionEpoch: crypto.randomUUID(),
|
|
241739
|
+
lastAppliedControlSequence: 0
|
|
241740
|
+
};
|
|
241741
|
+
controlStates.set(sessionId, created);
|
|
241742
|
+
return created;
|
|
241743
|
+
};
|
|
241744
|
+
const runSerializedCommand = async (sessionId, task) => {
|
|
241745
|
+
const previous = commandQueues.get(sessionId) ?? Promise.resolve();
|
|
241746
|
+
let releaseCurrent;
|
|
241747
|
+
const current = new Promise((resolve) => {
|
|
241748
|
+
releaseCurrent = resolve;
|
|
241476
241749
|
});
|
|
241477
|
-
|
|
241478
|
-
|
|
241479
|
-
|
|
241480
|
-
|
|
241481
|
-
|
|
241482
|
-
|
|
241483
|
-
|
|
241484
|
-
|
|
241485
|
-
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
|
|
241486
|
-
*/
|
|
241487
|
-
createDebug.formatters = {};
|
|
241488
|
-
/**
|
|
241489
|
-
* Selects a color for a debug namespace
|
|
241490
|
-
* @param {String} namespace The namespace string for the debug instance to be colored
|
|
241491
|
-
* @return {Number|String} An ANSI color code for the given namespace
|
|
241492
|
-
* @api private
|
|
241493
|
-
*/
|
|
241494
|
-
function selectColor(namespace) {
|
|
241495
|
-
let hash = 0;
|
|
241496
|
-
for (let i = 0; i < namespace.length; i++) {
|
|
241497
|
-
hash = (hash << 5) - hash + namespace.charCodeAt(i);
|
|
241498
|
-
hash |= 0;
|
|
241499
|
-
}
|
|
241500
|
-
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
|
|
241501
|
-
}
|
|
241502
|
-
createDebug.selectColor = selectColor;
|
|
241503
|
-
/**
|
|
241504
|
-
* Create a debugger with the given `namespace`.
|
|
241505
|
-
*
|
|
241506
|
-
* @param {String} namespace
|
|
241507
|
-
* @return {Function}
|
|
241508
|
-
* @api public
|
|
241509
|
-
*/
|
|
241510
|
-
function createDebug(namespace) {
|
|
241511
|
-
let prevTime;
|
|
241512
|
-
let enableOverride = null;
|
|
241513
|
-
let namespacesCache;
|
|
241514
|
-
let enabledCache;
|
|
241515
|
-
function debug(...args) {
|
|
241516
|
-
if (!debug.enabled) return;
|
|
241517
|
-
const self = debug;
|
|
241518
|
-
const curr = Number(/* @__PURE__ */ new Date());
|
|
241519
|
-
self.diff = curr - (prevTime || curr);
|
|
241520
|
-
self.prev = prevTime;
|
|
241521
|
-
self.curr = curr;
|
|
241522
|
-
prevTime = curr;
|
|
241523
|
-
args[0] = createDebug.coerce(args[0]);
|
|
241524
|
-
if (typeof args[0] !== "string") args.unshift("%O");
|
|
241525
|
-
let index = 0;
|
|
241526
|
-
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
|
|
241527
|
-
if (match === "%%") return "%";
|
|
241528
|
-
index++;
|
|
241529
|
-
const formatter = createDebug.formatters[format];
|
|
241530
|
-
if (typeof formatter === "function") {
|
|
241531
|
-
const val = args[index];
|
|
241532
|
-
match = formatter.call(self, val);
|
|
241533
|
-
args.splice(index, 1);
|
|
241534
|
-
index--;
|
|
241535
|
-
}
|
|
241536
|
-
return match;
|
|
241537
|
-
});
|
|
241538
|
-
createDebug.formatArgs.call(self, args);
|
|
241539
|
-
(self.log || createDebug.log).apply(self, args);
|
|
241540
|
-
}
|
|
241541
|
-
debug.namespace = namespace;
|
|
241542
|
-
debug.useColors = createDebug.useColors();
|
|
241543
|
-
debug.color = createDebug.selectColor(namespace);
|
|
241544
|
-
debug.extend = extend;
|
|
241545
|
-
debug.destroy = createDebug.destroy;
|
|
241546
|
-
Object.defineProperty(debug, "enabled", {
|
|
241547
|
-
enumerable: true,
|
|
241548
|
-
configurable: false,
|
|
241549
|
-
get: () => {
|
|
241550
|
-
if (enableOverride !== null) return enableOverride;
|
|
241551
|
-
if (namespacesCache !== createDebug.namespaces) {
|
|
241552
|
-
namespacesCache = createDebug.namespaces;
|
|
241553
|
-
enabledCache = createDebug.enabled(namespace);
|
|
241554
|
-
}
|
|
241555
|
-
return enabledCache;
|
|
241556
|
-
},
|
|
241557
|
-
set: (v) => {
|
|
241558
|
-
enableOverride = v;
|
|
241559
|
-
}
|
|
241560
|
-
});
|
|
241561
|
-
if (typeof createDebug.init === "function") createDebug.init(debug);
|
|
241562
|
-
return debug;
|
|
241750
|
+
const queued = previous.catch(() => void 0).then(() => current);
|
|
241751
|
+
commandQueues.set(sessionId, queued);
|
|
241752
|
+
await previous.catch(() => void 0);
|
|
241753
|
+
try {
|
|
241754
|
+
return await task();
|
|
241755
|
+
} finally {
|
|
241756
|
+
releaseCurrent();
|
|
241757
|
+
if (commandQueues.get(sessionId) === queued) commandQueues.delete(sessionId);
|
|
241563
241758
|
}
|
|
241564
|
-
|
|
241565
|
-
|
|
241566
|
-
|
|
241567
|
-
|
|
241759
|
+
};
|
|
241760
|
+
const clearConnectedDisplay = (input) => {
|
|
241761
|
+
const active = connectedDisplays.get(input.sessionId);
|
|
241762
|
+
if (!active) return;
|
|
241763
|
+
if (input.tabId === void 0 || active.tabId === input.tabId) connectedDisplays.delete(input.sessionId);
|
|
241764
|
+
};
|
|
241765
|
+
const displayForSnapshot = (snapshot) => {
|
|
241766
|
+
const fallback = resolveDisplay(snapshot, options);
|
|
241767
|
+
const active = connectedDisplays.get(snapshot.sessionId);
|
|
241768
|
+
if (!active) return fallback;
|
|
241769
|
+
if (!snapshot.tabs.some((tab) => tab.id === active.tabId)) {
|
|
241770
|
+
connectedDisplays.delete(snapshot.sessionId);
|
|
241771
|
+
return fallback;
|
|
241568
241772
|
}
|
|
241569
|
-
|
|
241570
|
-
|
|
241571
|
-
|
|
241572
|
-
|
|
241573
|
-
|
|
241574
|
-
|
|
241575
|
-
|
|
241576
|
-
|
|
241577
|
-
|
|
241578
|
-
|
|
241579
|
-
|
|
241580
|
-
|
|
241581
|
-
|
|
241582
|
-
|
|
241583
|
-
else createDebug.names.push(ns);
|
|
241773
|
+
if (snapshot.activeTabId !== active.tabId) return fallback;
|
|
241774
|
+
const { reason: _reason, detail: _detail, ...fallbackWithoutDegradedReason } = fallback;
|
|
241775
|
+
return {
|
|
241776
|
+
...fallbackWithoutDegradedReason,
|
|
241777
|
+
...active.display,
|
|
241778
|
+
reconnect: active.display.reconnect ?? fallback.reconnect
|
|
241779
|
+
};
|
|
241780
|
+
};
|
|
241781
|
+
const publishLocalEvent = (event) => {
|
|
241782
|
+
for (const subscription of localSubscriptions) {
|
|
241783
|
+
if (!subscriptionMatchesEvent(subscription, event)) continue;
|
|
241784
|
+
require_Schema$1.runPromise(subscription.listener(event)).catch((error) => {
|
|
241785
|
+
console.warn("[browser] Failed to publish local browser event", { error });
|
|
241786
|
+
});
|
|
241584
241787
|
}
|
|
241585
|
-
|
|
241586
|
-
|
|
241587
|
-
|
|
241588
|
-
|
|
241589
|
-
|
|
241590
|
-
|
|
241591
|
-
|
|
241592
|
-
|
|
241593
|
-
|
|
241594
|
-
|
|
241595
|
-
|
|
241596
|
-
|
|
241788
|
+
};
|
|
241789
|
+
const publishWebRtcIceCandidate = (event) => {
|
|
241790
|
+
publishLocalEvent({
|
|
241791
|
+
eventId: EventId.make(crypto.randomUUID()),
|
|
241792
|
+
browserId: event.input.browserId ?? browserIdFrom(event.input),
|
|
241793
|
+
threadId: event.input.threadId,
|
|
241794
|
+
environmentId: event.input.environmentId ?? DEFAULT_BROWSER_ENVIRONMENT_ID,
|
|
241795
|
+
userId: event.input.userId ?? "local-user",
|
|
241796
|
+
agentId: null,
|
|
241797
|
+
createdAt: nowIso$2(),
|
|
241798
|
+
type: "display.iceCandidate",
|
|
241799
|
+
payload: {
|
|
241800
|
+
sessionId: event.input.sessionId,
|
|
241801
|
+
tabId: event.input.tabId,
|
|
241802
|
+
clientId: event.input.clientId,
|
|
241803
|
+
candidate: event.candidate
|
|
241804
|
+
}
|
|
241805
|
+
});
|
|
241806
|
+
};
|
|
241807
|
+
return {
|
|
241808
|
+
localSubscriptions,
|
|
241809
|
+
connectedDisplays,
|
|
241810
|
+
controlStates,
|
|
241811
|
+
controlStateFor,
|
|
241812
|
+
runSerializedCommand,
|
|
241813
|
+
clearConnectedDisplay,
|
|
241814
|
+
displayForSnapshot,
|
|
241815
|
+
publishWebRtcIceCandidate
|
|
241816
|
+
};
|
|
241817
|
+
}
|
|
241818
|
+
//#endregion
|
|
241819
|
+
//#region src/browser/AgentBrowserHostBridgeManager.ts
|
|
241820
|
+
function openInputForSession(input) {
|
|
241821
|
+
return {
|
|
241822
|
+
sessionId: input.hostSessionId,
|
|
241823
|
+
...input.sessionInput.browserId ? { browserId: input.sessionInput.browserId } : {},
|
|
241824
|
+
...input.initialUrl ? { initialUrl: input.initialUrl } : {},
|
|
241825
|
+
...input.sessionInput.name ? { name: input.sessionInput.name } : {},
|
|
241826
|
+
...input.sessionInput.visibility ? { visibility: input.sessionInput.visibility } : {},
|
|
241827
|
+
...input.sessionInput.viewport ? { viewport: input.sessionInput.viewport } : {}
|
|
241828
|
+
};
|
|
241829
|
+
}
|
|
241830
|
+
function forgetHostSession(input) {
|
|
241831
|
+
input.state.clearConnectedDisplay({ sessionId: input.hostSessionId });
|
|
241832
|
+
input.state.controlStates.delete(input.hostSessionId);
|
|
241833
|
+
input.options?.forgetRuntimeSession?.({ sessionId: input.hostSessionId });
|
|
241834
|
+
}
|
|
241835
|
+
async function quarantineTimedOutOpenSession(input) {
|
|
241836
|
+
console.warn("[browser] Agent Browser openSession timed out; quarantining session", {
|
|
241837
|
+
sessionId: input.hostSessionId,
|
|
241838
|
+
...input.sessionInput.browserId ? { browserId: input.sessionInput.browserId } : {}
|
|
241839
|
+
});
|
|
241840
|
+
await withAgentBrowserBridgeTimeout("Agent Browser openSession quarantine close", input.bridge.closeSession({
|
|
241841
|
+
sessionId: input.hostSessionId,
|
|
241842
|
+
...input.sessionInput.browserId ? { browserId: input.sessionInput.browserId } : {}
|
|
241843
|
+
}), agentBrowserCloseTimeoutMs()).catch(() => void 0);
|
|
241844
|
+
forgetHostSession(input);
|
|
241845
|
+
throw new Error(`${input.error instanceof Error ? input.error.message : String(input.error)} The pending browser session was quarantined and closed.`, { cause: input.error });
|
|
241846
|
+
}
|
|
241847
|
+
async function openHostBridgeSession(input) {
|
|
241848
|
+
const hostSessionId = sessionIdForThread(input.sessionInput.threadId);
|
|
241849
|
+
const initialUrl = input.sessionInput.initialUrl ? normalizeUserVisibleBrowserUrl(input.sessionInput.initialUrl) : void 0;
|
|
241850
|
+
input.options?.prepareRuntimeSession?.({
|
|
241851
|
+
sessionId: hostSessionId,
|
|
241852
|
+
initialUrl: initialUrl ?? "about:blank"
|
|
241853
|
+
});
|
|
241854
|
+
try {
|
|
241855
|
+
const snapshot = await withAgentBrowserBridgeTimeout("Agent Browser openSession", openAgentBrowserSessionReady(input.bridge, openInputForSession({
|
|
241856
|
+
sessionInput: input.sessionInput,
|
|
241857
|
+
hostSessionId,
|
|
241858
|
+
initialUrl
|
|
241859
|
+
})), agentBrowserOpenTimeoutMs());
|
|
241860
|
+
return toForgeSession({
|
|
241861
|
+
snapshot,
|
|
241862
|
+
threadId: input.sessionInput.threadId,
|
|
241863
|
+
environmentId: input.sessionInput.environmentId,
|
|
241864
|
+
userId: input.sessionInput.userId,
|
|
241865
|
+
agentId: input.sessionInput.agentId,
|
|
241866
|
+
ownerRole: input.sessionInput.ownerRole,
|
|
241867
|
+
display: input.state.displayForSnapshot(snapshot),
|
|
241868
|
+
controlState: input.state.controlStateFor(snapshot.sessionId)
|
|
241869
|
+
});
|
|
241870
|
+
} catch (error) {
|
|
241871
|
+
if (!isAgentBrowserBridgeTimeoutError(error)) {
|
|
241872
|
+
input.options?.forgetRuntimeSession?.({ sessionId: hostSessionId });
|
|
241873
|
+
throw error;
|
|
241874
|
+
}
|
|
241875
|
+
return quarantineTimedOutOpenSession({
|
|
241876
|
+
...input,
|
|
241877
|
+
error,
|
|
241878
|
+
hostSessionId
|
|
241879
|
+
});
|
|
241880
|
+
}
|
|
241881
|
+
}
|
|
241882
|
+
async function verifyTimedOutClose(input) {
|
|
241883
|
+
const remaining = await withAgentBrowserBridgeTimeout("Agent Browser closeSession verification", input.bridge.getSession(input.hostIdentity), 2e3).catch(() => void 0);
|
|
241884
|
+
console.warn(remaining === null ? "[browser] Agent Browser closeSession timed out after session removal" : "[browser] Agent Browser closeSession timed out; forgetting session", {
|
|
241885
|
+
sessionId: input.hostIdentity.sessionId,
|
|
241886
|
+
...input.sessionInput.browserId ? { browserId: input.sessionInput.browserId } : {}
|
|
241887
|
+
});
|
|
241888
|
+
}
|
|
241889
|
+
async function closeHostBridgeSession(input) {
|
|
241890
|
+
const hostSessionId = sessionIdForThread(input.sessionInput.threadId);
|
|
241891
|
+
await require_Schema$1.runPromise(input.options?.displayPeers?.closeForSession({ sessionId: hostSessionId }) ?? require_Schema$1.void_);
|
|
241892
|
+
const hostIdentity = {
|
|
241893
|
+
sessionId: hostSessionId,
|
|
241894
|
+
...input.sessionInput.browserId ? { browserId: input.sessionInput.browserId } : {}
|
|
241895
|
+
};
|
|
241896
|
+
try {
|
|
241897
|
+
await withAgentBrowserBridgeTimeout("Agent Browser closeSession", input.bridge.closeSession(hostIdentity), agentBrowserCloseTimeoutMs());
|
|
241898
|
+
} catch (error) {
|
|
241899
|
+
if (!isAgentBrowserBridgeTimeoutError(error)) throw error;
|
|
241900
|
+
await verifyTimedOutClose({
|
|
241901
|
+
...input,
|
|
241902
|
+
hostIdentity
|
|
241903
|
+
});
|
|
241904
|
+
}
|
|
241905
|
+
forgetHostSession({
|
|
241906
|
+
...input,
|
|
241907
|
+
hostSessionId
|
|
241908
|
+
});
|
|
241909
|
+
}
|
|
241910
|
+
function createAgentBrowserHostBridgeManager(bridge, options) {
|
|
241911
|
+
const state = createAgentBrowserHostBridgeState(options);
|
|
241912
|
+
return {
|
|
241913
|
+
openSession: (input) => require_Schema$1.tryPromise({
|
|
241914
|
+
try: () => openHostBridgeSession({
|
|
241915
|
+
bridge,
|
|
241916
|
+
options,
|
|
241917
|
+
state,
|
|
241918
|
+
sessionInput: input
|
|
241919
|
+
}),
|
|
241920
|
+
catch: (error) => toBrowserError(error, "Failed to open Agent Browser session.")
|
|
241921
|
+
}),
|
|
241922
|
+
closeSession: (input) => require_Schema$1.tryPromise({
|
|
241923
|
+
try: () => closeHostBridgeSession({
|
|
241924
|
+
bridge,
|
|
241925
|
+
options,
|
|
241926
|
+
state,
|
|
241927
|
+
sessionInput: input
|
|
241928
|
+
}),
|
|
241929
|
+
catch: (error) => toBrowserError(error, "Failed to close Agent Browser session.")
|
|
241930
|
+
}),
|
|
241931
|
+
clearBrowsingData: () => require_Schema$1.succeed({
|
|
241932
|
+
closedSessionCount: 0,
|
|
241933
|
+
deletedSessionCount: 0,
|
|
241934
|
+
deletedCaptureCount: 0
|
|
241935
|
+
}),
|
|
241936
|
+
getSession: (input) => require_Schema$1.tryPromise({
|
|
241937
|
+
try: async () => {
|
|
241938
|
+
const snapshot = await withAgentBrowserBridgeTimeout("Agent Browser getSession", bridge.getSession({
|
|
241939
|
+
sessionId: sessionIdForThread(input.threadId),
|
|
241940
|
+
...input.browserId ? { browserId: input.browserId } : {}
|
|
241941
|
+
}), agentBrowserFastTimeoutMs());
|
|
241942
|
+
return snapshot ? toForgeSession({
|
|
241943
|
+
snapshot,
|
|
241944
|
+
threadId: input.threadId,
|
|
241945
|
+
environmentId: input.environmentId,
|
|
241946
|
+
userId: input.userId,
|
|
241947
|
+
display: state.displayForSnapshot(snapshot),
|
|
241948
|
+
controlState: state.controlStateFor(snapshot.sessionId)
|
|
241949
|
+
}) : null;
|
|
241950
|
+
},
|
|
241951
|
+
catch: (error) => toBrowserError(error, "Failed to read Agent Browser session.")
|
|
241952
|
+
}),
|
|
241953
|
+
listSessions: (input) => require_Schema$1.tryPromise({
|
|
241954
|
+
try: async () => {
|
|
241955
|
+
return (await withAgentBrowserBridgeTimeout("Agent Browser listSessions", bridge.listSessions(input.browserId ? { browserId: input.browserId } : void 0), agentBrowserFastTimeoutMs())).map((snapshot) => toForgeSession({
|
|
241956
|
+
snapshot,
|
|
241957
|
+
threadId: input.threadId ?? ThreadId.make(snapshot.sessionId),
|
|
241958
|
+
environmentId: input.environmentId,
|
|
241959
|
+
userId: input.userId,
|
|
241960
|
+
display: state.displayForSnapshot(snapshot),
|
|
241961
|
+
controlState: state.controlStateFor(snapshot.sessionId)
|
|
241962
|
+
}));
|
|
241963
|
+
},
|
|
241964
|
+
catch: (error) => toBrowserError(error, "Failed to list Agent Browser sessions.")
|
|
241965
|
+
}),
|
|
241966
|
+
command: (input) => require_Schema$1.tryPromise({
|
|
241967
|
+
try: () => executeOrderedHostCommand({
|
|
241968
|
+
bridge,
|
|
241969
|
+
options,
|
|
241970
|
+
state,
|
|
241971
|
+
commandInput: input
|
|
241972
|
+
}),
|
|
241973
|
+
catch: (error) => toBrowserError(error, "Failed to run Agent Browser command.")
|
|
241974
|
+
}),
|
|
241975
|
+
attachDisplay: (input) => {
|
|
241976
|
+
const displayPeers = options?.displayPeers;
|
|
241977
|
+
if (!displayPeers) return require_Schema$1.fail(remoteWebRtcUnavailableError(input));
|
|
241978
|
+
return require_Schema$1.tryPromise({
|
|
241979
|
+
try: async () => {
|
|
241980
|
+
const iceServers = await resolveDisplayIceServers(options);
|
|
241981
|
+
if (options?.requiresWebRtcRelay === true && !hasTurnRelay(iceServers)) throw remoteWebRtcRelayRequiredError(input);
|
|
241982
|
+
const result = await require_Schema$1.runPromise(displayPeers.attach(input, {
|
|
241983
|
+
display: {
|
|
241984
|
+
transport: "remote-webrtc",
|
|
241985
|
+
status: "connecting",
|
|
241986
|
+
...input.viewport ? { viewport: input.viewport } : {},
|
|
241987
|
+
devicePixelRatio: 1,
|
|
241988
|
+
...iceServers.length > 0 ? { iceServers } : {},
|
|
241989
|
+
reconnect: {
|
|
241990
|
+
status: "ready",
|
|
241991
|
+
attempt: 0
|
|
241992
|
+
}
|
|
241993
|
+
},
|
|
241994
|
+
onIceCandidate: state.publishWebRtcIceCandidate
|
|
241995
|
+
}));
|
|
241996
|
+
state.connectedDisplays.set(input.sessionId, {
|
|
241997
|
+
tabId: input.tabId,
|
|
241998
|
+
display: result.display
|
|
241999
|
+
});
|
|
242000
|
+
return result;
|
|
242001
|
+
},
|
|
242002
|
+
catch: (error) => {
|
|
242003
|
+
if (isBrowserError(error)) return error;
|
|
242004
|
+
return toBrowserError(error, "Failed to attach WebRTC browser display.");
|
|
242005
|
+
}
|
|
242006
|
+
});
|
|
242007
|
+
},
|
|
242008
|
+
addDisplayIceCandidate: (input) => {
|
|
242009
|
+
const displayPeers = options?.displayPeers;
|
|
242010
|
+
if (!displayPeers) return require_Schema$1.fail(remoteWebRtcUnavailableError(input));
|
|
242011
|
+
return displayPeers.addIceCandidate(input);
|
|
242012
|
+
},
|
|
242013
|
+
subscribe: (input, listener) => require_Schema$1.promise(async () => {
|
|
242014
|
+
const localSubscription = {
|
|
242015
|
+
input,
|
|
242016
|
+
listener
|
|
242017
|
+
};
|
|
242018
|
+
state.localSubscriptions.add(localSubscription);
|
|
242019
|
+
const threadId = input.threadId ?? ThreadId.make("unknown-thread");
|
|
242020
|
+
const browserId = browserIdFrom(input);
|
|
242021
|
+
const environmentId = input.environmentId ?? DEFAULT_BROWSER_ENVIRONMENT_ID;
|
|
242022
|
+
const userId = input.userId ?? "local-user";
|
|
242023
|
+
const subscribeInput = {
|
|
242024
|
+
...input.threadId ? { sessionId: sessionIdForThread(input.threadId) } : {},
|
|
242025
|
+
...input.browserId ? { browserId: input.browserId } : {}
|
|
242026
|
+
};
|
|
242027
|
+
const unsubscribeBridge = await bridge.subscribe(subscribeInput, (event) => {
|
|
242028
|
+
if (event.type === "session.closed") state.clearConnectedDisplay({ sessionId: event.payload.sessionId });
|
|
242029
|
+
if (event.type === "tab.closed") state.clearConnectedDisplay({
|
|
242030
|
+
sessionId: event.sessionId,
|
|
242031
|
+
tabId: event.payload.tabId
|
|
242032
|
+
});
|
|
242033
|
+
const mapped = toStreamEvent({
|
|
242034
|
+
event,
|
|
242035
|
+
threadId,
|
|
242036
|
+
browserId,
|
|
242037
|
+
environmentId,
|
|
242038
|
+
userId,
|
|
242039
|
+
agentId: null,
|
|
242040
|
+
ownerRole: "lead",
|
|
242041
|
+
display: event.type === "session.started" || event.type === "session.updated" ? state.displayForSnapshot(event.payload.snapshot) : DEFAULT_AGENT_BROWSER_DISPLAY,
|
|
242042
|
+
controlState: state.controlStateFor(event.type === "session.closed" ? event.payload.sessionId : event.sessionId)
|
|
242043
|
+
});
|
|
242044
|
+
if (mapped) require_Schema$1.runPromise(listener(mapped));
|
|
242045
|
+
if (event.type === "session.closed") state.controlStates.delete(event.payload.sessionId);
|
|
242046
|
+
});
|
|
242047
|
+
return () => {
|
|
242048
|
+
state.localSubscriptions.delete(localSubscription);
|
|
242049
|
+
unsubscribeBridge();
|
|
242050
|
+
};
|
|
242051
|
+
})
|
|
242052
|
+
};
|
|
242053
|
+
}
|
|
242054
|
+
//#endregion
|
|
242055
|
+
//#region ../../node_modules/.bun/debug@4.4.0+2b91fc17bf64bdfd/node_modules/debug/src/common.js
|
|
242056
|
+
var require_common$2 = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, module) => {
|
|
242057
|
+
/**
|
|
242058
|
+
* This is the common logic for both the Node.js and web browser
|
|
242059
|
+
* implementations of `debug()`.
|
|
242060
|
+
*/
|
|
242061
|
+
function setup(env) {
|
|
242062
|
+
createDebug.debug = createDebug;
|
|
242063
|
+
createDebug.default = createDebug;
|
|
242064
|
+
createDebug.coerce = coerce;
|
|
242065
|
+
createDebug.disable = disable;
|
|
242066
|
+
createDebug.enable = enable;
|
|
242067
|
+
createDebug.enabled = enabled;
|
|
242068
|
+
createDebug.humanize = require_ms();
|
|
242069
|
+
createDebug.destroy = destroy;
|
|
242070
|
+
Object.keys(env).forEach((key) => {
|
|
242071
|
+
createDebug[key] = env[key];
|
|
242072
|
+
});
|
|
242073
|
+
/**
|
|
242074
|
+
* The currently active debug mode names, and names to skip.
|
|
242075
|
+
*/
|
|
242076
|
+
createDebug.names = [];
|
|
242077
|
+
createDebug.skips = [];
|
|
242078
|
+
/**
|
|
242079
|
+
* Map of special "%n" handling functions, for the debug "format" argument.
|
|
242080
|
+
*
|
|
242081
|
+
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
|
|
242082
|
+
*/
|
|
242083
|
+
createDebug.formatters = {};
|
|
242084
|
+
/**
|
|
242085
|
+
* Selects a color for a debug namespace
|
|
242086
|
+
* @param {String} namespace The namespace string for the debug instance to be colored
|
|
242087
|
+
* @return {Number|String} An ANSI color code for the given namespace
|
|
242088
|
+
* @api private
|
|
242089
|
+
*/
|
|
242090
|
+
function selectColor(namespace) {
|
|
242091
|
+
let hash = 0;
|
|
242092
|
+
for (let i = 0; i < namespace.length; i++) {
|
|
242093
|
+
hash = (hash << 5) - hash + namespace.charCodeAt(i);
|
|
242094
|
+
hash |= 0;
|
|
242095
|
+
}
|
|
242096
|
+
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
|
|
242097
|
+
}
|
|
242098
|
+
createDebug.selectColor = selectColor;
|
|
242099
|
+
/**
|
|
242100
|
+
* Create a debugger with the given `namespace`.
|
|
242101
|
+
*
|
|
242102
|
+
* @param {String} namespace
|
|
242103
|
+
* @return {Function}
|
|
242104
|
+
* @api public
|
|
242105
|
+
*/
|
|
242106
|
+
function createDebug(namespace) {
|
|
242107
|
+
let prevTime;
|
|
242108
|
+
let enableOverride = null;
|
|
242109
|
+
let namespacesCache;
|
|
242110
|
+
let enabledCache;
|
|
242111
|
+
function debug(...args) {
|
|
242112
|
+
if (!debug.enabled) return;
|
|
242113
|
+
const self = debug;
|
|
242114
|
+
const curr = Number(/* @__PURE__ */ new Date());
|
|
242115
|
+
self.diff = curr - (prevTime || curr);
|
|
242116
|
+
self.prev = prevTime;
|
|
242117
|
+
self.curr = curr;
|
|
242118
|
+
prevTime = curr;
|
|
242119
|
+
args[0] = createDebug.coerce(args[0]);
|
|
242120
|
+
if (typeof args[0] !== "string") args.unshift("%O");
|
|
242121
|
+
let index = 0;
|
|
242122
|
+
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
|
|
242123
|
+
if (match === "%%") return "%";
|
|
242124
|
+
index++;
|
|
242125
|
+
const formatter = createDebug.formatters[format];
|
|
242126
|
+
if (typeof formatter === "function") {
|
|
242127
|
+
const val = args[index];
|
|
242128
|
+
match = formatter.call(self, val);
|
|
242129
|
+
args.splice(index, 1);
|
|
242130
|
+
index--;
|
|
242131
|
+
}
|
|
242132
|
+
return match;
|
|
242133
|
+
});
|
|
242134
|
+
createDebug.formatArgs.call(self, args);
|
|
242135
|
+
(self.log || createDebug.log).apply(self, args);
|
|
242136
|
+
}
|
|
242137
|
+
debug.namespace = namespace;
|
|
242138
|
+
debug.useColors = createDebug.useColors();
|
|
242139
|
+
debug.color = createDebug.selectColor(namespace);
|
|
242140
|
+
debug.extend = extend;
|
|
242141
|
+
debug.destroy = createDebug.destroy;
|
|
242142
|
+
Object.defineProperty(debug, "enabled", {
|
|
242143
|
+
enumerable: true,
|
|
242144
|
+
configurable: false,
|
|
242145
|
+
get: () => {
|
|
242146
|
+
if (enableOverride !== null) return enableOverride;
|
|
242147
|
+
if (namespacesCache !== createDebug.namespaces) {
|
|
242148
|
+
namespacesCache = createDebug.namespaces;
|
|
242149
|
+
enabledCache = createDebug.enabled(namespace);
|
|
242150
|
+
}
|
|
242151
|
+
return enabledCache;
|
|
242152
|
+
},
|
|
242153
|
+
set: (v) => {
|
|
242154
|
+
enableOverride = v;
|
|
242155
|
+
}
|
|
242156
|
+
});
|
|
242157
|
+
if (typeof createDebug.init === "function") createDebug.init(debug);
|
|
242158
|
+
return debug;
|
|
242159
|
+
}
|
|
242160
|
+
function extend(namespace, delimiter) {
|
|
242161
|
+
const newDebug = createDebug(this.namespace + (typeof delimiter === "undefined" ? ":" : delimiter) + namespace);
|
|
242162
|
+
newDebug.log = this.log;
|
|
242163
|
+
return newDebug;
|
|
242164
|
+
}
|
|
242165
|
+
/**
|
|
242166
|
+
* Enables a debug mode by namespaces. This can include modes
|
|
242167
|
+
* separated by a colon and wildcards.
|
|
242168
|
+
*
|
|
242169
|
+
* @param {String} namespaces
|
|
242170
|
+
* @api public
|
|
242171
|
+
*/
|
|
242172
|
+
function enable(namespaces) {
|
|
242173
|
+
createDebug.save(namespaces);
|
|
242174
|
+
createDebug.namespaces = namespaces;
|
|
242175
|
+
createDebug.names = [];
|
|
242176
|
+
createDebug.skips = [];
|
|
242177
|
+
const split = (typeof namespaces === "string" ? namespaces : "").trim().replace(" ", ",").split(",").filter(Boolean);
|
|
242178
|
+
for (const ns of split) if (ns[0] === "-") createDebug.skips.push(ns.slice(1));
|
|
242179
|
+
else createDebug.names.push(ns);
|
|
242180
|
+
}
|
|
242181
|
+
/**
|
|
242182
|
+
* Checks if the given string matches a namespace template, honoring
|
|
242183
|
+
* asterisks as wildcards.
|
|
242184
|
+
*
|
|
242185
|
+
* @param {String} search
|
|
242186
|
+
* @param {String} template
|
|
242187
|
+
* @return {Boolean}
|
|
242188
|
+
*/
|
|
242189
|
+
function matchesTemplate(search, template) {
|
|
242190
|
+
let searchIndex = 0;
|
|
242191
|
+
let templateIndex = 0;
|
|
242192
|
+
let starIndex = -1;
|
|
241597
242193
|
let matchIndex = 0;
|
|
241598
242194
|
while (searchIndex < search.length) if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) if (template[templateIndex] === "*") {
|
|
241599
242195
|
starIndex = templateIndex;
|
|
@@ -283921,234 +284517,317 @@ async function createBrowserWebRtcDisplayCaptureRuntime(options = {}) {
|
|
|
283921
284517
|
}
|
|
283922
284518
|
}
|
|
283923
284519
|
//#endregion
|
|
283924
|
-
//#region src/
|
|
284520
|
+
//#region src/browser/browserProfilePaths.ts
|
|
283925
284521
|
/**
|
|
283926
|
-
*
|
|
283927
|
-
*
|
|
283928
|
-
* ── why this exists ─────────────────────────────────────────────────────────
|
|
283929
|
-
* A sprite's container cgroup is frozen within ~1 second of the last inbound
|
|
283930
|
-
* request. Processes are paused mid-instruction, not killed, and resume when
|
|
283931
|
-
* traffic returns. Measured on a throwaway sprite with a 1 Hz ticker
|
|
283932
|
-
* (`pocs/sprite-lifecycle-spike/`):
|
|
283933
|
-
*
|
|
283934
|
-
* detached, nothing connected 2 ticks / 121 s = 1.7%
|
|
283935
|
-
* with a task hold 90 ticks / 90 s = 100%
|
|
283936
|
-
*
|
|
283937
|
-
* So an agent turn that nobody is watching effectively stops. fly.io names our
|
|
283938
|
-
* previous approach — relying on a running process after disconnection — as an
|
|
283939
|
-
* anti-pattern, and points at the Tasks API instead. See
|
|
283940
|
-
* https://docs.sprites.dev/keeping-sprites-running/ and
|
|
283941
|
-
* `docs/specs/2026-07-30-sprites-platform-ground-truth.md` §1.
|
|
283942
|
-
*
|
|
283943
|
-
* Note that Services (the platform supervisor) do NOT solve this: "a Sprite with
|
|
283944
|
-
* ten services defined still pauses when it goes idle." Supervision and
|
|
283945
|
-
* wakefulness are separate primitives, and this module is only the second one.
|
|
283946
|
-
*
|
|
283947
|
-
* ── why it lives in the guest ───────────────────────────────────────────────
|
|
283948
|
-
* The Tasks API is guest-only. `GET https://api.sprites.dev/v1/sprites/{n}/tasks`
|
|
283949
|
-
* answers 404; the socket at /.sprite/api.sock is the only surface. CMMD cannot
|
|
283950
|
-
* take this hold on our behalf, which is why the env vars below are exported by
|
|
283951
|
-
* CMMD's provider and consumed here.
|
|
283952
|
-
*
|
|
283953
|
-
* ── the ordering rules ──────────────────────────────────────────────────────
|
|
283954
|
-
* 1. The refresher MUST be stopped before the DELETE is issued. A refresher that
|
|
283955
|
-
* outlives the delete re-acquires the hold on its next tick; that was
|
|
283956
|
-
* previously measured leaking 37 renewals over 18 minutes, pinning a sprite
|
|
283957
|
-
* running — and billing — 33 minutes after its work had ended.
|
|
283958
|
-
* 2. Stopping the interval only cancels FUTURE ticks. A PUT already on the wire
|
|
283959
|
-
* must be JOINED before the DELETE, or it lands afterwards and recreates the
|
|
283960
|
-
* hold for another full expiry window. fly.io's reference worker cancels and
|
|
283961
|
-
* awaits its heartbeat for the same reason.
|
|
283962
|
-
* 3. `release` can arrive while `acquire` is still awaiting its own PUT —
|
|
283963
|
-
* `releaseAll` runs from runtime drain and the shutdown finalizer,
|
|
283964
|
-
* concurrently with the provider event stream. The hold is registered before
|
|
283965
|
-
* that await (so a duplicate turn.started cannot double-acquire), so release
|
|
283966
|
-
* can remove the map entry first; `acquire` must then NOT install a
|
|
283967
|
-
* refresher. One attached to an unmapped hold is unreachable by release,
|
|
283968
|
-
* releaseThread and releaseAll alike, and re-PUTs every 60 s for the life of
|
|
283969
|
-
* the process — an unbounded pin.
|
|
283970
|
-
*
|
|
283971
|
-
* All three are pinned by tests.
|
|
284522
|
+
* Canonical Agent Browser Chromium profile paths.
|
|
283972
284523
|
*
|
|
283973
|
-
*
|
|
283974
|
-
* `
|
|
283975
|
-
*
|
|
283976
|
-
|
|
283977
|
-
|
|
283978
|
-
const SPRITE_TASK_HOLD_SOCKET_ENV = "FORGE_CMMD_SPRITE_TASK_HOLD_SOCKET";
|
|
283979
|
-
const SPRITE_TASK_HOLD_REFRESH_MS = 6e4;
|
|
283980
|
-
/**
|
|
283981
|
-
* Per-request deadline for the guest-socket calls. Must stay strictly under
|
|
283982
|
-
* SPRITE_TASK_HOLD_REFRESH_MS: the manager serializes refreshes, so a PUT that
|
|
283983
|
-
* never settles turns every later tick into a no-op and the hold expires
|
|
283984
|
-
* mid-turn. Bounding the call means a hung socket settles as a rejection, the
|
|
283985
|
-
* next 60 s tick retries, and release is not wedged joining it. From the last
|
|
283986
|
-
* good refresh there are still four attempts (+60/+120/+180/+240 s) before the
|
|
283987
|
-
* 300 s expiry. 10 s matches the `-m 10` cap CMMD already puts on every curl in
|
|
283988
|
-
* its own hold preamble; the guest-side manager was the one path left unbounded.
|
|
284524
|
+
* Both the host bridge adapter (which launches Chromium with a per-session
|
|
284525
|
+
* `userDataDir`) and the orphan reaper (which reconciles live Chromium
|
|
284526
|
+
* processes against registered sessions) must agree byte-for-byte on this
|
|
284527
|
+
* mapping — the reaper matches processes to sessions by profile directory
|
|
284528
|
+
* basename.
|
|
283989
284529
|
*/
|
|
283990
|
-
const
|
|
284530
|
+
const AGENT_BROWSER_PROFILE_ROOT = "/tmp/forge-agent-browser-profiles";
|
|
284531
|
+
/** Sanitized directory basename for a session id. Lossy — never invert it. */
|
|
284532
|
+
function browserProfileSessionKey(sessionId) {
|
|
284533
|
+
return sessionId.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 160) || "default";
|
|
284534
|
+
}
|
|
284535
|
+
function browserProfileDirForSession(sessionId, root = AGENT_BROWSER_PROFILE_ROOT) {
|
|
284536
|
+
return `${root}/${browserProfileSessionKey(sessionId)}`;
|
|
284537
|
+
}
|
|
284538
|
+
//#endregion
|
|
284539
|
+
//#region src/browser/Layers/BrowserOrphanReaper.ts
|
|
283991
284540
|
/**
|
|
283992
|
-
*
|
|
284541
|
+
* E7 production orphan reaper (spec:
|
|
284542
|
+
* docs/specs/2026-06-10-windows-free-dev-and-browser-lifecycle-hardening-plan.md).
|
|
283993
284543
|
*
|
|
283994
|
-
*
|
|
283995
|
-
*
|
|
283996
|
-
*
|
|
284544
|
+
* Periodically reconciles live Chromium browser-main processes (distinct
|
|
284545
|
+
* `--user-data-dir` under the Agent Browser profile root) against the set of
|
|
284546
|
+
* sessions the host bridge knows about, kills orphans, enforces a max-session
|
|
284547
|
+
* TTL through graceful closes, and removes stale profile directories.
|
|
284548
|
+
*
|
|
284549
|
+
* Guard-rails:
|
|
284550
|
+
* - never kills a registered live session;
|
|
284551
|
+
* - the first sweep *attempt* after boot is observe-only (cold-start grace)
|
|
284552
|
+
* -- this is consumed unconditionally by that one attempt, so a transient
|
|
284553
|
+
* read failure on it can never grant a second free pass (which would
|
|
284554
|
+
* otherwise let a restart-orphaned Chromium outlive two sweep intervals
|
|
284555
|
+
* instead of one);
|
|
284556
|
+
* - unregistered Chromium younger than `minOrphanAgeMs` is spared so
|
|
284557
|
+
* in-flight opens are never raced;
|
|
284558
|
+
* - if the session registry cannot be read, the sweep kills nothing --
|
|
284559
|
+
* distinct from a registry that reads back empty (e.g. a freshly booted
|
|
284560
|
+
* server with zero sessions), which is healthy and reapable;
|
|
284561
|
+
* - `protectedSessionKeys` (the shared "default" backend profile) are never
|
|
284562
|
+
* touched.
|
|
283997
284563
|
*/
|
|
283998
|
-
|
|
283999
|
-
|
|
284000
|
-
|
|
284564
|
+
const execFileAsync$2 = (0, node_util.promisify)(node_child_process.execFile);
|
|
284565
|
+
var BrowserReaperStepError = class extends require_Schema$1.TaggedError("BrowserReaperStepError") {};
|
|
284566
|
+
const DEFAULT_REAPER_INTERVAL_MS = 6e4;
|
|
284567
|
+
const DEFAULT_MAX_SESSION_TTL_MS = 14400 * 1e3;
|
|
284568
|
+
const DEFAULT_MIN_ORPHAN_AGE_MS = 120 * 1e3;
|
|
284569
|
+
const KILL_ESCALATION_TIMEOUT_MS = 5e3;
|
|
284570
|
+
const KILL_POLL_INTERVAL_MS = 100;
|
|
284571
|
+
function positiveIntFromEnv(value) {
|
|
284572
|
+
if (!value) return;
|
|
284573
|
+
const parsed = Number.parseInt(value, 10);
|
|
284574
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
284575
|
+
}
|
|
284576
|
+
function browserOrphanReaperConfigFromEnv(env, profileRoot = "/tmp/forge-agent-browser-profiles") {
|
|
284577
|
+
const enabledRaw = env.FORGE_BROWSER_REAPER_ENABLED?.trim().toLowerCase();
|
|
284001
284578
|
return {
|
|
284002
|
-
|
|
284003
|
-
|
|
284579
|
+
profileRoot,
|
|
284580
|
+
intervalMs: positiveIntFromEnv(env.FORGE_BROWSER_REAPER_INTERVAL_MS) || DEFAULT_REAPER_INTERVAL_MS,
|
|
284581
|
+
maxSessionTtlMs: positiveIntFromEnv(env.FORGE_BROWSER_SESSION_MAX_TTL_MS) ?? DEFAULT_MAX_SESSION_TTL_MS,
|
|
284582
|
+
minOrphanAgeMs: positiveIntFromEnv(env.FORGE_BROWSER_REAPER_MIN_ORPHAN_AGE_MS) ?? DEFAULT_MIN_ORPHAN_AGE_MS,
|
|
284583
|
+
enabled: enabledRaw !== "0" && enabledRaw !== "false",
|
|
284584
|
+
protectedSessionKeys: new Set(["default"])
|
|
284004
284585
|
};
|
|
284005
284586
|
}
|
|
284587
|
+
/** [[dd-]hh:]mm:ss as printed by `ps -o etime=`. Returns null on malformed input. */
|
|
284588
|
+
function parseEtimeToMs(etime) {
|
|
284589
|
+
const match = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/.exec(etime.trim());
|
|
284590
|
+
if (!match) return null;
|
|
284591
|
+
const [, days, hours, minutes, seconds] = match;
|
|
284592
|
+
return (Number(days ?? 0) * 24 * 60 * 60 + Number(hours ?? 0) * 60 * 60 + Number(minutes) * 60 + Number(seconds)) * 1e3;
|
|
284593
|
+
}
|
|
284006
284594
|
/**
|
|
284007
|
-
*
|
|
284008
|
-
*
|
|
284595
|
+
* A Chromium browser-main owns a profile under our root and has no `--type=`
|
|
284596
|
+
* flag (renderer/gpu/utility children all carry one). Killing the main tears
|
|
284597
|
+
* down the whole Chromium process tree.
|
|
284009
284598
|
*/
|
|
284010
|
-
function
|
|
284011
|
-
return
|
|
284599
|
+
function isChromiumMainCommand(command, profileRoot) {
|
|
284600
|
+
return command.includes(`--user-data-dir=${profileRoot}/`) && !/\s--type=/.test(` ${command}`);
|
|
284012
284601
|
}
|
|
284013
|
-
|
|
284014
|
-
|
|
284015
|
-
|
|
284016
|
-
|
|
284602
|
+
function extractUserDataDir(command) {
|
|
284603
|
+
const match = /--user-data-dir=(\S+)/.exec(command);
|
|
284604
|
+
return match ? match[1] ?? null : null;
|
|
284605
|
+
}
|
|
284606
|
+
function profileKeyForDir(userDataDir, profileRoot) {
|
|
284607
|
+
const resolved = node_path.default.resolve(userDataDir);
|
|
284608
|
+
const resolvedRoot = node_path.default.resolve(profileRoot);
|
|
284609
|
+
if (resolved === resolvedRoot || !resolved.startsWith(resolvedRoot + node_path.default.sep)) return null;
|
|
284610
|
+
const relative = resolved.slice(resolvedRoot.length + 1);
|
|
284611
|
+
return relative.includes(node_path.default.sep) ? null : relative;
|
|
284612
|
+
}
|
|
284613
|
+
function makeSweepState(deps, config, observeOnly) {
|
|
284614
|
+
return {
|
|
284615
|
+
deps,
|
|
284616
|
+
config,
|
|
284617
|
+
observeOnly,
|
|
284618
|
+
errors: [],
|
|
284619
|
+
wouldReapOrphans: [],
|
|
284620
|
+
wouldCloseTtlSessions: [],
|
|
284621
|
+
wouldRemoveStaleProfiles: [],
|
|
284622
|
+
killedOrphans: [],
|
|
284623
|
+
closedTtlSessions: [],
|
|
284624
|
+
removedStaleProfiles: []
|
|
284625
|
+
};
|
|
284626
|
+
}
|
|
284627
|
+
function sweepReport(state, registryUnavailable) {
|
|
284628
|
+
return {
|
|
284629
|
+
observeOnly: state.observeOnly,
|
|
284630
|
+
registryUnavailable,
|
|
284631
|
+
wouldReapOrphans: state.wouldReapOrphans,
|
|
284632
|
+
wouldCloseTtlSessions: state.wouldCloseTtlSessions,
|
|
284633
|
+
wouldRemoveStaleProfiles: state.wouldRemoveStaleProfiles,
|
|
284634
|
+
killedOrphans: state.killedOrphans,
|
|
284635
|
+
closedTtlSessions: state.closedTtlSessions,
|
|
284636
|
+
removedStaleProfiles: state.removedStaleProfiles,
|
|
284637
|
+
errors: state.errors
|
|
284638
|
+
};
|
|
284639
|
+
}
|
|
284640
|
+
/** Run a dep call; on failure record the error into the state and yield null. */
|
|
284641
|
+
function attemptOrNull(state, label, run) {
|
|
284642
|
+
return require_Schema$1.tryPromise({
|
|
284643
|
+
try: run,
|
|
284644
|
+
catch: (error) => new BrowserReaperStepError({
|
|
284645
|
+
message: `${label}: ${error instanceof Error ? error.message : String(error)}`,
|
|
284646
|
+
cause: error
|
|
284647
|
+
})
|
|
284648
|
+
}).pipe(require_Schema$1.map((value) => value), require_Schema$1.catch_((error) => require_Schema$1.sync(() => (state.errors.push(error.message), null))));
|
|
284649
|
+
}
|
|
284650
|
+
/** Like attemptOrNull, but collapse the outcome to a success flag. */
|
|
284651
|
+
function attemptFlag(state, label, run) {
|
|
284652
|
+
return attemptOrNull(state, label, async () => {
|
|
284653
|
+
await run();
|
|
284654
|
+
return true;
|
|
284655
|
+
}).pipe(require_Schema$1.map((value) => value === true));
|
|
284656
|
+
}
|
|
284657
|
+
function mapRegisteredByKey(registered) {
|
|
284658
|
+
const registeredByKey = /* @__PURE__ */ new Map();
|
|
284659
|
+
for (const sessionId of registered) registeredByKey.set(browserProfileSessionKey(sessionId), sessionId);
|
|
284660
|
+
return registeredByKey;
|
|
284661
|
+
}
|
|
284662
|
+
const enforceSessionTtl = (state, sessionId, main) => require_Schema$1.gen(function* () {
|
|
284663
|
+
if (state.config.maxSessionTtlMs <= 0 || main.ageMs <= state.config.maxSessionTtlMs) return;
|
|
284664
|
+
if (state.observeOnly) {
|
|
284665
|
+
state.wouldCloseTtlSessions.push(sessionId);
|
|
284666
|
+
return;
|
|
284017
284667
|
}
|
|
284018
|
-
}
|
|
284019
|
-
|
|
284020
|
-
*
|
|
284021
|
-
*
|
|
284022
|
-
* Every method is failure-tolerant by design. Acquiring a hold is an
|
|
284023
|
-
* optimisation, not a precondition: if it fails the turn still runs, just at the
|
|
284024
|
-
* degraded speed it has today. Failing the turn instead would convert a billing
|
|
284025
|
-
* inefficiency into an outage.
|
|
284026
|
-
*/
|
|
284027
|
-
var SpriteTaskHoldManager = class {
|
|
284028
|
-
#config;
|
|
284029
|
-
#transport;
|
|
284030
|
-
#timer;
|
|
284031
|
-
#onError;
|
|
284032
|
-
#holds = /* @__PURE__ */ new Map();
|
|
284033
|
-
constructor(options) {
|
|
284034
|
-
this.#config = options.config;
|
|
284035
|
-
this.#transport = options.transport;
|
|
284036
|
-
this.#timer = options.timer ?? DEFAULT_TIMER;
|
|
284037
|
-
this.#onError = options.onError ?? (() => {});
|
|
284668
|
+
if (yield* attemptFlag(state, `closeSession(${sessionId})`, () => state.deps.closeSession(sessionId))) {
|
|
284669
|
+
state.closedTtlSessions.push(sessionId);
|
|
284670
|
+
yield* increment(browserSessionsReapedTotal, { reason: "ttl" });
|
|
284038
284671
|
}
|
|
284039
|
-
|
|
284040
|
-
|
|
284041
|
-
|
|
284672
|
+
});
|
|
284673
|
+
const reapOrphanMain = (state, key, main) => require_Schema$1.gen(function* () {
|
|
284674
|
+
if (main.ageMs < state.config.minOrphanAgeMs) return;
|
|
284675
|
+
if (state.observeOnly) {
|
|
284676
|
+
state.wouldReapOrphans.push(key);
|
|
284677
|
+
return;
|
|
284042
284678
|
}
|
|
284043
|
-
|
|
284044
|
-
|
|
284045
|
-
|
|
284046
|
-
|
|
284047
|
-
|
|
284048
|
-
|
|
284049
|
-
|
|
284050
|
-
|
|
284051
|
-
|
|
284052
|
-
|
|
284053
|
-
|
|
284054
|
-
|
|
284055
|
-
const
|
|
284056
|
-
|
|
284057
|
-
|
|
284058
|
-
|
|
284059
|
-
|
|
284060
|
-
|
|
284061
|
-
|
|
284062
|
-
|
|
284679
|
+
if (!(yield* attemptFlag(state, `killProcess(${main.pid})`, () => state.deps.killProcess(main.pid)))) return;
|
|
284680
|
+
state.killedOrphans.push(key);
|
|
284681
|
+
yield* increment(browserSessionsReapedTotal, { reason: "orphan" });
|
|
284682
|
+
yield* attemptFlag(state, `removeProfileDir(${main.userDataDir})`, () => state.deps.removeProfileDir(main.userDataDir));
|
|
284683
|
+
});
|
|
284684
|
+
const sweepChromiumMains = (state, registeredByKey, mains) => require_Schema$1.gen(function* () {
|
|
284685
|
+
const liveKeys = /* @__PURE__ */ new Set();
|
|
284686
|
+
for (const main of mains) {
|
|
284687
|
+
const key = profileKeyForDir(main.userDataDir, state.config.profileRoot);
|
|
284688
|
+
if (key === null) continue;
|
|
284689
|
+
liveKeys.add(key);
|
|
284690
|
+
if (state.config.protectedSessionKeys.has(key)) continue;
|
|
284691
|
+
const registeredSessionId = registeredByKey.get(key);
|
|
284692
|
+
if (registeredSessionId !== void 0) yield* enforceSessionTtl(state, registeredSessionId, main);
|
|
284693
|
+
else yield* reapOrphanMain(state, key, main);
|
|
284694
|
+
}
|
|
284695
|
+
return liveKeys;
|
|
284696
|
+
});
|
|
284697
|
+
function isRetainedProfileDir(state, registeredByKey, liveKeys, entry) {
|
|
284698
|
+
const key = profileKeyForDir(entry.path, state.config.profileRoot);
|
|
284699
|
+
return key === null || state.config.protectedSessionKeys.has(key) || registeredByKey.has(key) || liveKeys.has(key) || entry.ageMs < state.config.minOrphanAgeMs;
|
|
284700
|
+
}
|
|
284701
|
+
const sweepStaleProfileDirs = (state, registeredByKey, liveKeys) => require_Schema$1.gen(function* () {
|
|
284702
|
+
const profileDirs = yield* attemptOrNull(state, "listProfileDirs", state.deps.listProfileDirs);
|
|
284703
|
+
if (profileDirs === null) return;
|
|
284704
|
+
for (const entry of profileDirs) {
|
|
284705
|
+
if (isRetainedProfileDir(state, registeredByKey, liveKeys, entry)) continue;
|
|
284706
|
+
if (state.observeOnly) {
|
|
284707
|
+
state.wouldRemoveStaleProfiles.push(entry.path);
|
|
284708
|
+
continue;
|
|
284709
|
+
}
|
|
284710
|
+
if (yield* attemptFlag(state, `removeProfileDir(${entry.path})`, () => state.deps.removeProfileDir(entry.path))) {
|
|
284711
|
+
state.removedStaleProfiles.push(entry.path);
|
|
284712
|
+
yield* increment(browserSessionsReapedTotal, { reason: "stale_profile" });
|
|
284063
284713
|
}
|
|
284064
|
-
hold.inFlight.delete(tracked);
|
|
284065
|
-
if (hold.released || this.#holds.get(turnId) !== hold) return;
|
|
284066
|
-
hold.refresher = this.#timer.setInterval(() => {
|
|
284067
|
-
if (hold.inFlight.size > 0) return;
|
|
284068
|
-
const refresh = this.#transport.put(name, 300).catch((cause) => this.#onError(`failed to refresh sprite task hold for turn ${turnId}`, cause));
|
|
284069
|
-
hold.inFlight.add(refresh);
|
|
284070
|
-
refresh.finally(() => hold.inFlight.delete(refresh));
|
|
284071
|
-
}, SPRITE_TASK_HOLD_REFRESH_MS);
|
|
284072
284714
|
}
|
|
284073
|
-
|
|
284074
|
-
|
|
284075
|
-
|
|
284076
|
-
|
|
284077
|
-
|
|
284078
|
-
|
|
284079
|
-
|
|
284715
|
+
});
|
|
284716
|
+
function sweepHasActivity(report) {
|
|
284717
|
+
return report.killedOrphans.length > 0 || report.closedTtlSessions.length > 0 || report.removedStaleProfiles.length > 0 || report.wouldReapOrphans.length > 0 || report.wouldCloseTtlSessions.length > 0 || report.wouldRemoveStaleProfiles.length > 0 || report.errors.length > 0;
|
|
284718
|
+
}
|
|
284719
|
+
const logSweepReportIfActive = (report) => sweepHasActivity(report) ? require_Schema$1.logInfo("browser orphan reaper sweep", {
|
|
284720
|
+
observeOnly: report.observeOnly,
|
|
284721
|
+
killedOrphans: report.killedOrphans,
|
|
284722
|
+
closedTtlSessions: report.closedTtlSessions,
|
|
284723
|
+
removedStaleProfiles: report.removedStaleProfiles,
|
|
284724
|
+
wouldReapOrphans: report.wouldReapOrphans,
|
|
284725
|
+
wouldCloseTtlSessions: report.wouldCloseTtlSessions,
|
|
284726
|
+
wouldRemoveStaleProfiles: report.wouldRemoveStaleProfiles,
|
|
284727
|
+
errors: report.errors
|
|
284728
|
+
}) : require_Schema$1.void_;
|
|
284729
|
+
function createBrowserOrphanReaper(deps, config) {
|
|
284730
|
+
let coldStartGraceConsumed = false;
|
|
284731
|
+
return { sweep: require_Schema$1.gen(function* () {
|
|
284732
|
+
const observeOnly = !coldStartGraceConsumed;
|
|
284733
|
+
coldStartGraceConsumed = true;
|
|
284734
|
+
const state = makeSweepState(deps, config, observeOnly);
|
|
284735
|
+
const registered = yield* attemptOrNull(state, "listRegisteredSessionIds", deps.listRegisteredSessionIds);
|
|
284736
|
+
if (registered === null) {
|
|
284737
|
+
yield* require_Schema$1.logWarning("browser orphan reaper: session registry unavailable; skipping", { errors: state.errors });
|
|
284738
|
+
return sweepReport(state, true);
|
|
284080
284739
|
}
|
|
284081
|
-
|
|
284082
|
-
if (
|
|
284740
|
+
const mains = yield* attemptOrNull(state, "listChromiumMains", deps.listChromiumMains);
|
|
284741
|
+
if (mains === null) return sweepReport(state, false);
|
|
284742
|
+
const registeredByKey = mapRegisteredByKey(registered);
|
|
284743
|
+
yield* sweepStaleProfileDirs(state, registeredByKey, yield* sweepChromiumMains(state, registeredByKey, mains));
|
|
284744
|
+
const report = sweepReport(state, false);
|
|
284745
|
+
yield* logSweepReportIfActive(report);
|
|
284746
|
+
return report;
|
|
284747
|
+
}) };
|
|
284748
|
+
}
|
|
284749
|
+
function sleep(ms) {
|
|
284750
|
+
return new Promise((resolve) => {
|
|
284751
|
+
setTimeout(resolve, ms).unref?.();
|
|
284752
|
+
});
|
|
284753
|
+
}
|
|
284754
|
+
function isEsrch(error) {
|
|
284755
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH";
|
|
284756
|
+
}
|
|
284757
|
+
/** SIGTERM, poll for exit, escalate to SIGKILL. Chromium tears down its own children. */
|
|
284758
|
+
async function killProcessWithEscalation(pid) {
|
|
284759
|
+
try {
|
|
284760
|
+
process.kill(pid, "SIGTERM");
|
|
284761
|
+
} catch (error) {
|
|
284762
|
+
if (isEsrch(error)) return;
|
|
284763
|
+
throw error;
|
|
284764
|
+
}
|
|
284765
|
+
const deadline = Date.now() + KILL_ESCALATION_TIMEOUT_MS;
|
|
284766
|
+
while (Date.now() < deadline) {
|
|
284767
|
+
await sleep(KILL_POLL_INTERVAL_MS);
|
|
284083
284768
|
try {
|
|
284084
|
-
|
|
284085
|
-
} catch
|
|
284086
|
-
|
|
284769
|
+
process.kill(pid, 0);
|
|
284770
|
+
} catch {
|
|
284771
|
+
return;
|
|
284087
284772
|
}
|
|
284088
284773
|
}
|
|
284089
|
-
|
|
284090
|
-
|
|
284091
|
-
|
|
284092
|
-
|
|
284093
|
-
* dead session's turn will never emit `turn.completed`. Releasing by thread —
|
|
284094
|
-
* rather than releasing everything — is what keeps a crash in one worktree
|
|
284095
|
-
* from silently dropping the holds of the other turns running beside it.
|
|
284096
|
-
*/
|
|
284097
|
-
async releaseThread(threadId) {
|
|
284098
|
-
const turnIds = [...this.#holds.entries()].filter(([, hold]) => hold.threadId === threadId).map(([turnId]) => turnId);
|
|
284099
|
-
await Promise.all(turnIds.map((turnId) => this.release(turnId)));
|
|
284100
|
-
}
|
|
284101
|
-
/**
|
|
284102
|
-
* Release everything. Runtime drain and server shutdown must call this, or a
|
|
284103
|
-
* drained sprite keeps billing until each hold expires.
|
|
284104
|
-
*/
|
|
284105
|
-
async releaseAll() {
|
|
284106
|
-
await Promise.all([...this.#holds.keys()].map((turnId) => this.release(turnId)));
|
|
284774
|
+
try {
|
|
284775
|
+
process.kill(pid, "SIGKILL");
|
|
284776
|
+
} catch (error) {
|
|
284777
|
+
if (!isEsrch(error)) throw error;
|
|
284107
284778
|
}
|
|
284108
|
-
}
|
|
284109
|
-
|
|
284110
|
-
|
|
284111
|
-
|
|
284112
|
-
|
|
284113
|
-
|
|
284114
|
-
|
|
284115
|
-
|
|
284116
|
-
|
|
284117
|
-
|
|
284118
|
-
|
|
284119
|
-
|
|
284120
|
-
|
|
284121
|
-
|
|
284122
|
-
|
|
284123
|
-
|
|
284124
|
-
|
|
284779
|
+
}
|
|
284780
|
+
function createSystemBrowserOrphanReaperDeps(input) {
|
|
284781
|
+
const { profileRoot } = input;
|
|
284782
|
+
return {
|
|
284783
|
+
listChromiumMains: async () => {
|
|
284784
|
+
const { stdout } = await execFileAsync$2("ps", ["-axo", "pid=,etime=,args="], { maxBuffer: 16 * 1024 * 1024 });
|
|
284785
|
+
const mains = [];
|
|
284786
|
+
for (const line of stdout.split("\n")) {
|
|
284787
|
+
const match = /^\s*(\d+)\s+(\S+)\s+(.+)$/.exec(line);
|
|
284788
|
+
if (!match) continue;
|
|
284789
|
+
const [, pidRaw, etime, command] = match;
|
|
284790
|
+
if (!isChromiumMainCommand(command, profileRoot)) continue;
|
|
284791
|
+
const userDataDir = extractUserDataDir(command);
|
|
284792
|
+
const ageMs = parseEtimeToMs(etime);
|
|
284793
|
+
if (userDataDir === null || ageMs === null) continue;
|
|
284794
|
+
mains.push({
|
|
284795
|
+
pid: Number(pidRaw),
|
|
284796
|
+
userDataDir,
|
|
284797
|
+
ageMs
|
|
284798
|
+
});
|
|
284799
|
+
}
|
|
284800
|
+
return mains;
|
|
284801
|
+
},
|
|
284802
|
+
listRegisteredSessionIds: async () => (await input.listSessions()).map((session) => session.sessionId),
|
|
284803
|
+
closeSession: input.closeSession,
|
|
284804
|
+
killProcess: killProcessWithEscalation,
|
|
284805
|
+
listProfileDirs: async () => {
|
|
284806
|
+
const entries = await node_fs_promises.default.readdir(profileRoot, { withFileTypes: true }).catch(() => []);
|
|
284807
|
+
const now = Date.now();
|
|
284808
|
+
const dirs = [];
|
|
284809
|
+
for (const entry of entries) {
|
|
284810
|
+
if (!entry.isDirectory()) continue;
|
|
284811
|
+
const dirPath = node_path.default.join(profileRoot, entry.name);
|
|
284812
|
+
const stat = await node_fs_promises.default.stat(dirPath).catch(() => null);
|
|
284813
|
+
if (stat === null) continue;
|
|
284814
|
+
dirs.push({
|
|
284815
|
+
path: dirPath,
|
|
284816
|
+
ageMs: Math.max(0, now - stat.mtimeMs)
|
|
284817
|
+
});
|
|
284818
|
+
}
|
|
284819
|
+
return dirs;
|
|
284820
|
+
},
|
|
284821
|
+
removeProfileDir: async (dir) => {
|
|
284822
|
+
const resolved = node_path.default.resolve(dir);
|
|
284823
|
+
const resolvedRoot = node_path.default.resolve(profileRoot);
|
|
284824
|
+
if (resolved === resolvedRoot || !resolved.startsWith(resolvedRoot + node_path.default.sep)) throw new Error(`refusing to remove path outside the profile root: ${dir}`);
|
|
284825
|
+
await node_fs_promises.default.rm(resolved, {
|
|
284826
|
+
recursive: true,
|
|
284827
|
+
force: true
|
|
284125
284828
|
});
|
|
284126
|
-
if (!response.ok) throw new Error(`sprite task API responded ${response.status}`);
|
|
284127
|
-
} finally {
|
|
284128
|
-
clearTimeout(deadline);
|
|
284129
284829
|
}
|
|
284130
284830
|
};
|
|
284131
|
-
return {
|
|
284132
|
-
put: (name, expireSeconds) => call(`/${encodeURIComponent(name)}`, {
|
|
284133
|
-
method: "PUT",
|
|
284134
|
-
headers: { "content-type": "application/json" },
|
|
284135
|
-
body: JSON.stringify({ expire: `${expireSeconds}s` })
|
|
284136
|
-
}),
|
|
284137
|
-
delete: (name) => call(`/${encodeURIComponent(name)}`, { method: "DELETE" })
|
|
284138
|
-
};
|
|
284139
|
-
}
|
|
284140
|
-
/**
|
|
284141
|
-
* Build a manager for this process, or null when the runtime is not a sprite.
|
|
284142
|
-
* Callers treat null as "this feature does not apply here".
|
|
284143
|
-
*/
|
|
284144
|
-
function makeSpriteTaskHoldManager(env = process.env, onError) {
|
|
284145
|
-
const config = resolveSpriteTaskHoldConfig(env);
|
|
284146
|
-
if (config === null) return null;
|
|
284147
|
-
return new SpriteTaskHoldManager({
|
|
284148
|
-
config,
|
|
284149
|
-
transport: makeSpriteTaskHoldTransport(config),
|
|
284150
|
-
...onError ? { onError } : {}
|
|
284151
|
-
});
|
|
284152
284831
|
}
|
|
284153
284832
|
//#endregion
|
|
284154
284833
|
//#region src/browser/Layers/displaySessionTaskHold.ts
|
|
@@ -284580,25 +285259,6 @@ function createWebRtcDisplaySessionManager(input = {}) {
|
|
|
284580
285259
|
})
|
|
284581
285260
|
};
|
|
284582
285261
|
}
|
|
284583
|
-
//#endregion
|
|
284584
|
-
//#region src/browser/browserProfilePaths.ts
|
|
284585
|
-
/**
|
|
284586
|
-
* Canonical Agent Browser Chromium profile paths.
|
|
284587
|
-
*
|
|
284588
|
-
* Both the host bridge adapter (which launches Chromium with a per-session
|
|
284589
|
-
* `userDataDir`) and the orphan reaper (which reconciles live Chromium
|
|
284590
|
-
* processes against registered sessions) must agree byte-for-byte on this
|
|
284591
|
-
* mapping — the reaper matches processes to sessions by profile directory
|
|
284592
|
-
* basename.
|
|
284593
|
-
*/
|
|
284594
|
-
const AGENT_BROWSER_PROFILE_ROOT = "/tmp/forge-agent-browser-profiles";
|
|
284595
|
-
/** Sanitized directory basename for a session id. Lossy — never invert it. */
|
|
284596
|
-
function browserProfileSessionKey(sessionId) {
|
|
284597
|
-
return sessionId.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 160) || "default";
|
|
284598
|
-
}
|
|
284599
|
-
function browserProfileDirForSession(sessionId, root = AGENT_BROWSER_PROFILE_ROOT) {
|
|
284600
|
-
return `${root}/${browserProfileSessionKey(sessionId)}`;
|
|
284601
|
-
}
|
|
284602
285262
|
const HOST_BRIDGE_RECONNECT_MAX_MS = 1e3;
|
|
284603
285263
|
/**
|
|
284604
285264
|
* Exponential backoff, capped.
|
|
@@ -284678,620 +285338,9 @@ function drainHostBridgeNdjsonBuffer(buffer, callback, parseLine = (line) => JSO
|
|
|
284678
285338
|
return nextBuffer;
|
|
284679
285339
|
}
|
|
284680
285340
|
//#endregion
|
|
284681
|
-
//#region src/browser/
|
|
284682
|
-
/**
|
|
284683
|
-
* E7 production orphan reaper (spec:
|
|
284684
|
-
* docs/specs/2026-06-10-windows-free-dev-and-browser-lifecycle-hardening-plan.md).
|
|
284685
|
-
*
|
|
284686
|
-
* Periodically reconciles live Chromium browser-main processes (distinct
|
|
284687
|
-
* `--user-data-dir` under the Agent Browser profile root) against the set of
|
|
284688
|
-
* sessions the host bridge knows about, kills orphans, enforces a max-session
|
|
284689
|
-
* TTL through graceful closes, and removes stale profile directories.
|
|
284690
|
-
*
|
|
284691
|
-
* Guard-rails:
|
|
284692
|
-
* - never kills a registered live session;
|
|
284693
|
-
* - the first sweep *attempt* after boot is observe-only (cold-start grace)
|
|
284694
|
-
* -- this is consumed unconditionally by that one attempt, so a transient
|
|
284695
|
-
* read failure on it can never grant a second free pass (which would
|
|
284696
|
-
* otherwise let a restart-orphaned Chromium outlive two sweep intervals
|
|
284697
|
-
* instead of one);
|
|
284698
|
-
* - unregistered Chromium younger than `minOrphanAgeMs` is spared so
|
|
284699
|
-
* in-flight opens are never raced;
|
|
284700
|
-
* - if the session registry cannot be read, the sweep kills nothing --
|
|
284701
|
-
* distinct from a registry that reads back empty (e.g. a freshly booted
|
|
284702
|
-
* server with zero sessions), which is healthy and reapable;
|
|
284703
|
-
* - `protectedSessionKeys` (the shared "default" backend profile) are never
|
|
284704
|
-
* touched.
|
|
284705
|
-
*/
|
|
284706
|
-
const execFileAsync$2 = (0, node_util.promisify)(node_child_process.execFile);
|
|
284707
|
-
var BrowserReaperStepError = class extends require_Schema$1.TaggedError("BrowserReaperStepError") {};
|
|
284708
|
-
const DEFAULT_REAPER_INTERVAL_MS = 6e4;
|
|
284709
|
-
const DEFAULT_MAX_SESSION_TTL_MS = 14400 * 1e3;
|
|
284710
|
-
const DEFAULT_MIN_ORPHAN_AGE_MS = 120 * 1e3;
|
|
284711
|
-
const KILL_ESCALATION_TIMEOUT_MS = 5e3;
|
|
284712
|
-
const KILL_POLL_INTERVAL_MS = 100;
|
|
284713
|
-
function positiveIntFromEnv(value) {
|
|
284714
|
-
if (!value) return;
|
|
284715
|
-
const parsed = Number.parseInt(value, 10);
|
|
284716
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
284717
|
-
}
|
|
284718
|
-
function browserOrphanReaperConfigFromEnv(env, profileRoot = "/tmp/forge-agent-browser-profiles") {
|
|
284719
|
-
const enabledRaw = env.FORGE_BROWSER_REAPER_ENABLED?.trim().toLowerCase();
|
|
284720
|
-
return {
|
|
284721
|
-
profileRoot,
|
|
284722
|
-
intervalMs: positiveIntFromEnv(env.FORGE_BROWSER_REAPER_INTERVAL_MS) || DEFAULT_REAPER_INTERVAL_MS,
|
|
284723
|
-
maxSessionTtlMs: positiveIntFromEnv(env.FORGE_BROWSER_SESSION_MAX_TTL_MS) ?? DEFAULT_MAX_SESSION_TTL_MS,
|
|
284724
|
-
minOrphanAgeMs: positiveIntFromEnv(env.FORGE_BROWSER_REAPER_MIN_ORPHAN_AGE_MS) ?? DEFAULT_MIN_ORPHAN_AGE_MS,
|
|
284725
|
-
enabled: enabledRaw !== "0" && enabledRaw !== "false",
|
|
284726
|
-
protectedSessionKeys: new Set(["default"])
|
|
284727
|
-
};
|
|
284728
|
-
}
|
|
284729
|
-
/** [[dd-]hh:]mm:ss as printed by `ps -o etime=`. Returns null on malformed input. */
|
|
284730
|
-
function parseEtimeToMs(etime) {
|
|
284731
|
-
const match = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/.exec(etime.trim());
|
|
284732
|
-
if (!match) return null;
|
|
284733
|
-
const [, days, hours, minutes, seconds] = match;
|
|
284734
|
-
return (Number(days ?? 0) * 24 * 60 * 60 + Number(hours ?? 0) * 60 * 60 + Number(minutes) * 60 + Number(seconds)) * 1e3;
|
|
284735
|
-
}
|
|
284736
|
-
/**
|
|
284737
|
-
* A Chromium browser-main owns a profile under our root and has no `--type=`
|
|
284738
|
-
* flag (renderer/gpu/utility children all carry one). Killing the main tears
|
|
284739
|
-
* down the whole Chromium process tree.
|
|
284740
|
-
*/
|
|
284741
|
-
function isChromiumMainCommand(command, profileRoot) {
|
|
284742
|
-
return command.includes(`--user-data-dir=${profileRoot}/`) && !/\s--type=/.test(` ${command}`);
|
|
284743
|
-
}
|
|
284744
|
-
function extractUserDataDir(command) {
|
|
284745
|
-
const match = /--user-data-dir=(\S+)/.exec(command);
|
|
284746
|
-
return match ? match[1] ?? null : null;
|
|
284747
|
-
}
|
|
284748
|
-
function profileKeyForDir(userDataDir, profileRoot) {
|
|
284749
|
-
const resolved = node_path.default.resolve(userDataDir);
|
|
284750
|
-
const resolvedRoot = node_path.default.resolve(profileRoot);
|
|
284751
|
-
if (resolved === resolvedRoot || !resolved.startsWith(resolvedRoot + node_path.default.sep)) return null;
|
|
284752
|
-
const relative = resolved.slice(resolvedRoot.length + 1);
|
|
284753
|
-
return relative.includes(node_path.default.sep) ? null : relative;
|
|
284754
|
-
}
|
|
284755
|
-
function makeSweepState(deps, config, observeOnly) {
|
|
284756
|
-
return {
|
|
284757
|
-
deps,
|
|
284758
|
-
config,
|
|
284759
|
-
observeOnly,
|
|
284760
|
-
errors: [],
|
|
284761
|
-
wouldReapOrphans: [],
|
|
284762
|
-
wouldCloseTtlSessions: [],
|
|
284763
|
-
wouldRemoveStaleProfiles: [],
|
|
284764
|
-
killedOrphans: [],
|
|
284765
|
-
closedTtlSessions: [],
|
|
284766
|
-
removedStaleProfiles: []
|
|
284767
|
-
};
|
|
284768
|
-
}
|
|
284769
|
-
function sweepReport(state, registryUnavailable) {
|
|
284770
|
-
return {
|
|
284771
|
-
observeOnly: state.observeOnly,
|
|
284772
|
-
registryUnavailable,
|
|
284773
|
-
wouldReapOrphans: state.wouldReapOrphans,
|
|
284774
|
-
wouldCloseTtlSessions: state.wouldCloseTtlSessions,
|
|
284775
|
-
wouldRemoveStaleProfiles: state.wouldRemoveStaleProfiles,
|
|
284776
|
-
killedOrphans: state.killedOrphans,
|
|
284777
|
-
closedTtlSessions: state.closedTtlSessions,
|
|
284778
|
-
removedStaleProfiles: state.removedStaleProfiles,
|
|
284779
|
-
errors: state.errors
|
|
284780
|
-
};
|
|
284781
|
-
}
|
|
284782
|
-
/** Run a dep call; on failure record the error into the state and yield null. */
|
|
284783
|
-
function attemptOrNull(state, label, run) {
|
|
284784
|
-
return require_Schema$1.tryPromise({
|
|
284785
|
-
try: run,
|
|
284786
|
-
catch: (error) => new BrowserReaperStepError({
|
|
284787
|
-
message: `${label}: ${error instanceof Error ? error.message : String(error)}`,
|
|
284788
|
-
cause: error
|
|
284789
|
-
})
|
|
284790
|
-
}).pipe(require_Schema$1.map((value) => value), require_Schema$1.catch_((error) => require_Schema$1.sync(() => (state.errors.push(error.message), null))));
|
|
284791
|
-
}
|
|
284792
|
-
/** Like attemptOrNull, but collapse the outcome to a success flag. */
|
|
284793
|
-
function attemptFlag(state, label, run) {
|
|
284794
|
-
return attemptOrNull(state, label, async () => {
|
|
284795
|
-
await run();
|
|
284796
|
-
return true;
|
|
284797
|
-
}).pipe(require_Schema$1.map((value) => value === true));
|
|
284798
|
-
}
|
|
284799
|
-
function mapRegisteredByKey(registered) {
|
|
284800
|
-
const registeredByKey = /* @__PURE__ */ new Map();
|
|
284801
|
-
for (const sessionId of registered) registeredByKey.set(browserProfileSessionKey(sessionId), sessionId);
|
|
284802
|
-
return registeredByKey;
|
|
284803
|
-
}
|
|
284804
|
-
const enforceSessionTtl = (state, sessionId, main) => require_Schema$1.gen(function* () {
|
|
284805
|
-
if (state.config.maxSessionTtlMs <= 0 || main.ageMs <= state.config.maxSessionTtlMs) return;
|
|
284806
|
-
if (state.observeOnly) {
|
|
284807
|
-
state.wouldCloseTtlSessions.push(sessionId);
|
|
284808
|
-
return;
|
|
284809
|
-
}
|
|
284810
|
-
if (yield* attemptFlag(state, `closeSession(${sessionId})`, () => state.deps.closeSession(sessionId))) {
|
|
284811
|
-
state.closedTtlSessions.push(sessionId);
|
|
284812
|
-
yield* increment(browserSessionsReapedTotal, { reason: "ttl" });
|
|
284813
|
-
}
|
|
284814
|
-
});
|
|
284815
|
-
const reapOrphanMain = (state, key, main) => require_Schema$1.gen(function* () {
|
|
284816
|
-
if (main.ageMs < state.config.minOrphanAgeMs) return;
|
|
284817
|
-
if (state.observeOnly) {
|
|
284818
|
-
state.wouldReapOrphans.push(key);
|
|
284819
|
-
return;
|
|
284820
|
-
}
|
|
284821
|
-
if (!(yield* attemptFlag(state, `killProcess(${main.pid})`, () => state.deps.killProcess(main.pid)))) return;
|
|
284822
|
-
state.killedOrphans.push(key);
|
|
284823
|
-
yield* increment(browserSessionsReapedTotal, { reason: "orphan" });
|
|
284824
|
-
yield* attemptFlag(state, `removeProfileDir(${main.userDataDir})`, () => state.deps.removeProfileDir(main.userDataDir));
|
|
284825
|
-
});
|
|
284826
|
-
const sweepChromiumMains = (state, registeredByKey, mains) => require_Schema$1.gen(function* () {
|
|
284827
|
-
const liveKeys = /* @__PURE__ */ new Set();
|
|
284828
|
-
for (const main of mains) {
|
|
284829
|
-
const key = profileKeyForDir(main.userDataDir, state.config.profileRoot);
|
|
284830
|
-
if (key === null) continue;
|
|
284831
|
-
liveKeys.add(key);
|
|
284832
|
-
if (state.config.protectedSessionKeys.has(key)) continue;
|
|
284833
|
-
const registeredSessionId = registeredByKey.get(key);
|
|
284834
|
-
if (registeredSessionId !== void 0) yield* enforceSessionTtl(state, registeredSessionId, main);
|
|
284835
|
-
else yield* reapOrphanMain(state, key, main);
|
|
284836
|
-
}
|
|
284837
|
-
return liveKeys;
|
|
284838
|
-
});
|
|
284839
|
-
function isRetainedProfileDir(state, registeredByKey, liveKeys, entry) {
|
|
284840
|
-
const key = profileKeyForDir(entry.path, state.config.profileRoot);
|
|
284841
|
-
return key === null || state.config.protectedSessionKeys.has(key) || registeredByKey.has(key) || liveKeys.has(key) || entry.ageMs < state.config.minOrphanAgeMs;
|
|
284842
|
-
}
|
|
284843
|
-
const sweepStaleProfileDirs = (state, registeredByKey, liveKeys) => require_Schema$1.gen(function* () {
|
|
284844
|
-
const profileDirs = yield* attemptOrNull(state, "listProfileDirs", state.deps.listProfileDirs);
|
|
284845
|
-
if (profileDirs === null) return;
|
|
284846
|
-
for (const entry of profileDirs) {
|
|
284847
|
-
if (isRetainedProfileDir(state, registeredByKey, liveKeys, entry)) continue;
|
|
284848
|
-
if (state.observeOnly) {
|
|
284849
|
-
state.wouldRemoveStaleProfiles.push(entry.path);
|
|
284850
|
-
continue;
|
|
284851
|
-
}
|
|
284852
|
-
if (yield* attemptFlag(state, `removeProfileDir(${entry.path})`, () => state.deps.removeProfileDir(entry.path))) {
|
|
284853
|
-
state.removedStaleProfiles.push(entry.path);
|
|
284854
|
-
yield* increment(browserSessionsReapedTotal, { reason: "stale_profile" });
|
|
284855
|
-
}
|
|
284856
|
-
}
|
|
284857
|
-
});
|
|
284858
|
-
function sweepHasActivity(report) {
|
|
284859
|
-
return report.killedOrphans.length > 0 || report.closedTtlSessions.length > 0 || report.removedStaleProfiles.length > 0 || report.wouldReapOrphans.length > 0 || report.wouldCloseTtlSessions.length > 0 || report.wouldRemoveStaleProfiles.length > 0 || report.errors.length > 0;
|
|
284860
|
-
}
|
|
284861
|
-
const logSweepReportIfActive = (report) => sweepHasActivity(report) ? require_Schema$1.logInfo("browser orphan reaper sweep", {
|
|
284862
|
-
observeOnly: report.observeOnly,
|
|
284863
|
-
killedOrphans: report.killedOrphans,
|
|
284864
|
-
closedTtlSessions: report.closedTtlSessions,
|
|
284865
|
-
removedStaleProfiles: report.removedStaleProfiles,
|
|
284866
|
-
wouldReapOrphans: report.wouldReapOrphans,
|
|
284867
|
-
wouldCloseTtlSessions: report.wouldCloseTtlSessions,
|
|
284868
|
-
wouldRemoveStaleProfiles: report.wouldRemoveStaleProfiles,
|
|
284869
|
-
errors: report.errors
|
|
284870
|
-
}) : require_Schema$1.void_;
|
|
284871
|
-
function createBrowserOrphanReaper(deps, config) {
|
|
284872
|
-
let coldStartGraceConsumed = false;
|
|
284873
|
-
return { sweep: require_Schema$1.gen(function* () {
|
|
284874
|
-
const observeOnly = !coldStartGraceConsumed;
|
|
284875
|
-
coldStartGraceConsumed = true;
|
|
284876
|
-
const state = makeSweepState(deps, config, observeOnly);
|
|
284877
|
-
const registered = yield* attemptOrNull(state, "listRegisteredSessionIds", deps.listRegisteredSessionIds);
|
|
284878
|
-
if (registered === null) {
|
|
284879
|
-
yield* require_Schema$1.logWarning("browser orphan reaper: session registry unavailable; skipping", { errors: state.errors });
|
|
284880
|
-
return sweepReport(state, true);
|
|
284881
|
-
}
|
|
284882
|
-
const mains = yield* attemptOrNull(state, "listChromiumMains", deps.listChromiumMains);
|
|
284883
|
-
if (mains === null) return sweepReport(state, false);
|
|
284884
|
-
const registeredByKey = mapRegisteredByKey(registered);
|
|
284885
|
-
yield* sweepStaleProfileDirs(state, registeredByKey, yield* sweepChromiumMains(state, registeredByKey, mains));
|
|
284886
|
-
const report = sweepReport(state, false);
|
|
284887
|
-
yield* logSweepReportIfActive(report);
|
|
284888
|
-
return report;
|
|
284889
|
-
}) };
|
|
284890
|
-
}
|
|
284891
|
-
function sleep(ms) {
|
|
284892
|
-
return new Promise((resolve) => {
|
|
284893
|
-
setTimeout(resolve, ms).unref?.();
|
|
284894
|
-
});
|
|
284895
|
-
}
|
|
284896
|
-
function isEsrch(error) {
|
|
284897
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH";
|
|
284898
|
-
}
|
|
284899
|
-
/** SIGTERM, poll for exit, escalate to SIGKILL. Chromium tears down its own children. */
|
|
284900
|
-
async function killProcessWithEscalation(pid) {
|
|
284901
|
-
try {
|
|
284902
|
-
process.kill(pid, "SIGTERM");
|
|
284903
|
-
} catch (error) {
|
|
284904
|
-
if (isEsrch(error)) return;
|
|
284905
|
-
throw error;
|
|
284906
|
-
}
|
|
284907
|
-
const deadline = Date.now() + KILL_ESCALATION_TIMEOUT_MS;
|
|
284908
|
-
while (Date.now() < deadline) {
|
|
284909
|
-
await sleep(KILL_POLL_INTERVAL_MS);
|
|
284910
|
-
try {
|
|
284911
|
-
process.kill(pid, 0);
|
|
284912
|
-
} catch {
|
|
284913
|
-
return;
|
|
284914
|
-
}
|
|
284915
|
-
}
|
|
284916
|
-
try {
|
|
284917
|
-
process.kill(pid, "SIGKILL");
|
|
284918
|
-
} catch (error) {
|
|
284919
|
-
if (!isEsrch(error)) throw error;
|
|
284920
|
-
}
|
|
284921
|
-
}
|
|
284922
|
-
function createSystemBrowserOrphanReaperDeps(input) {
|
|
284923
|
-
const { profileRoot } = input;
|
|
284924
|
-
return {
|
|
284925
|
-
listChromiumMains: async () => {
|
|
284926
|
-
const { stdout } = await execFileAsync$2("ps", ["-axo", "pid=,etime=,args="], { maxBuffer: 16 * 1024 * 1024 });
|
|
284927
|
-
const mains = [];
|
|
284928
|
-
for (const line of stdout.split("\n")) {
|
|
284929
|
-
const match = /^\s*(\d+)\s+(\S+)\s+(.+)$/.exec(line);
|
|
284930
|
-
if (!match) continue;
|
|
284931
|
-
const [, pidRaw, etime, command] = match;
|
|
284932
|
-
if (!isChromiumMainCommand(command, profileRoot)) continue;
|
|
284933
|
-
const userDataDir = extractUserDataDir(command);
|
|
284934
|
-
const ageMs = parseEtimeToMs(etime);
|
|
284935
|
-
if (userDataDir === null || ageMs === null) continue;
|
|
284936
|
-
mains.push({
|
|
284937
|
-
pid: Number(pidRaw),
|
|
284938
|
-
userDataDir,
|
|
284939
|
-
ageMs
|
|
284940
|
-
});
|
|
284941
|
-
}
|
|
284942
|
-
return mains;
|
|
284943
|
-
},
|
|
284944
|
-
listRegisteredSessionIds: async () => (await input.listSessions()).map((session) => session.sessionId),
|
|
284945
|
-
closeSession: input.closeSession,
|
|
284946
|
-
killProcess: killProcessWithEscalation,
|
|
284947
|
-
listProfileDirs: async () => {
|
|
284948
|
-
const entries = await node_fs_promises.default.readdir(profileRoot, { withFileTypes: true }).catch(() => []);
|
|
284949
|
-
const now = Date.now();
|
|
284950
|
-
const dirs = [];
|
|
284951
|
-
for (const entry of entries) {
|
|
284952
|
-
if (!entry.isDirectory()) continue;
|
|
284953
|
-
const dirPath = node_path.default.join(profileRoot, entry.name);
|
|
284954
|
-
const stat = await node_fs_promises.default.stat(dirPath).catch(() => null);
|
|
284955
|
-
if (stat === null) continue;
|
|
284956
|
-
dirs.push({
|
|
284957
|
-
path: dirPath,
|
|
284958
|
-
ageMs: Math.max(0, now - stat.mtimeMs)
|
|
284959
|
-
});
|
|
284960
|
-
}
|
|
284961
|
-
return dirs;
|
|
284962
|
-
},
|
|
284963
|
-
removeProfileDir: async (dir) => {
|
|
284964
|
-
const resolved = node_path.default.resolve(dir);
|
|
284965
|
-
const resolvedRoot = node_path.default.resolve(profileRoot);
|
|
284966
|
-
if (resolved === resolvedRoot || !resolved.startsWith(resolvedRoot + node_path.default.sep)) throw new Error(`refusing to remove path outside the profile root: ${dir}`);
|
|
284967
|
-
await node_fs_promises.default.rm(resolved, {
|
|
284968
|
-
recursive: true,
|
|
284969
|
-
force: true
|
|
284970
|
-
});
|
|
284971
|
-
}
|
|
284972
|
-
};
|
|
284973
|
-
}
|
|
284974
|
-
//#endregion
|
|
284975
|
-
//#region src/browser/browserControlProtocol.ts
|
|
284976
|
-
function resolveBrowserControlDecision(state, metadata) {
|
|
284977
|
-
const hasSessionEpoch = metadata.sessionEpoch !== void 0;
|
|
284978
|
-
const hasControlSequence = metadata.controlSequence !== void 0;
|
|
284979
|
-
const expectedControlSequence = state.lastAppliedControlSequence + 1;
|
|
284980
|
-
if (!hasSessionEpoch && !hasControlSequence) return {
|
|
284981
|
-
type: "apply",
|
|
284982
|
-
controlSequence: expectedControlSequence
|
|
284983
|
-
};
|
|
284984
|
-
if (!hasSessionEpoch || !hasControlSequence) return {
|
|
284985
|
-
type: "reject",
|
|
284986
|
-
reason: "incomplete_metadata",
|
|
284987
|
-
expectedSessionEpoch: state.sessionEpoch,
|
|
284988
|
-
expectedControlSequence,
|
|
284989
|
-
acknowledgedControlSequence: state.lastAppliedControlSequence
|
|
284990
|
-
};
|
|
284991
|
-
if (metadata.sessionEpoch !== state.sessionEpoch) return {
|
|
284992
|
-
type: "reject",
|
|
284993
|
-
reason: "stale_epoch",
|
|
284994
|
-
expectedSessionEpoch: state.sessionEpoch,
|
|
284995
|
-
acknowledgedControlSequence: state.lastAppliedControlSequence
|
|
284996
|
-
};
|
|
284997
|
-
if (metadata.controlSequence <= state.lastAppliedControlSequence) return {
|
|
284998
|
-
type: "duplicate",
|
|
284999
|
-
acknowledgedControlSequence: state.lastAppliedControlSequence
|
|
285000
|
-
};
|
|
285001
|
-
if (metadata.controlSequence !== expectedControlSequence) return {
|
|
285002
|
-
type: "reject",
|
|
285003
|
-
reason: "sequence_gap",
|
|
285004
|
-
expectedControlSequence,
|
|
285005
|
-
acknowledgedControlSequence: state.lastAppliedControlSequence
|
|
285006
|
-
};
|
|
285007
|
-
return {
|
|
285008
|
-
type: "apply",
|
|
285009
|
-
controlSequence: metadata.controlSequence
|
|
285010
|
-
};
|
|
285011
|
-
}
|
|
285012
|
-
function browserControlRejectionDetail(decision) {
|
|
285013
|
-
switch (decision.reason) {
|
|
285014
|
-
case "stale_epoch": return `Browser session is stale. Expected epoch ${decision.expectedSessionEpoch}.`;
|
|
285015
|
-
case "sequence_gap": return `Browser control sequence is out of order. Expected ${decision.expectedControlSequence}.`;
|
|
285016
|
-
case "incomplete_metadata": return "Browser control ordering requires both sessionEpoch and controlSequence.";
|
|
285017
|
-
}
|
|
285018
|
-
}
|
|
285019
|
-
//#endregion
|
|
285020
|
-
//#region src/browser/AgentBrowserHostBridgeAdapter.ts
|
|
285021
|
-
const DEFAULT_URL = "about:blank";
|
|
285022
|
-
const DEFAULT_RENDER_MODE = "interactive";
|
|
285023
|
-
const DEFAULT_AGENT_BROWSER_ID = "embedded";
|
|
285024
|
-
const DEFAULT_AGENT_BROWSER_VIEWPORT = {
|
|
285025
|
-
width: 1280,
|
|
285026
|
-
height: 720
|
|
285027
|
-
};
|
|
285341
|
+
//#region src/browser/NativeHostBrowserBridgeClient.ts
|
|
285028
285342
|
const NATIVE_HOST_EVENT_RECONNECT_BASE_MS = 25;
|
|
285029
285343
|
const NATIVE_HOST_EVENT_RECONNECT_MAX_MS = HOST_BRIDGE_RECONNECT_MAX_MS;
|
|
285030
|
-
const ABSOLUTE_WEB_URL_PATTERN = /^[a-z][a-z\d+\-.]*:\/\//i;
|
|
285031
|
-
const SPECIAL_URL_PATTERN = /^(about|blob|chrome|data|devtools|file):/i;
|
|
285032
|
-
const LOCAL_URL_LIKE_PATTERN = /^(localhost|127\.0\.0\.1|\[::1\]|::1|[a-z0-9.-]+\.localhost)(?::\d+)?(?:[/?#].*)?$/i;
|
|
285033
|
-
const DEFAULT_AGENT_BROWSER_DISPLAY = {
|
|
285034
|
-
transport: "remote-webrtc",
|
|
285035
|
-
status: "connected",
|
|
285036
|
-
reconnect: {
|
|
285037
|
-
status: "ready",
|
|
285038
|
-
attempt: 0
|
|
285039
|
-
}
|
|
285040
|
-
};
|
|
285041
|
-
const DEFAULT_AGENT_BROWSER_CONTROL_COMMAND_TIMEOUT_MS = 1500;
|
|
285042
|
-
const DEFAULT_AGENT_BROWSER_FAST_COMMAND_TIMEOUT_MS = 1e4;
|
|
285043
|
-
const DEFAULT_AGENT_BROWSER_NAVIGATION_COMMAND_TIMEOUT_MS = 9e4;
|
|
285044
|
-
const DEFAULT_AGENT_BROWSER_CAPTURE_COMMAND_TIMEOUT_MS = 9e4;
|
|
285045
|
-
const DEFAULT_AGENT_BROWSER_BRIDGE_OPEN_TIMEOUT_MS = 9e4;
|
|
285046
|
-
const DEFAULT_AGENT_BROWSER_BRIDGE_CLOSE_TIMEOUT_MS = 3e4;
|
|
285047
|
-
const DEFAULT_AGENT_BROWSER_PLAYWRIGHT_BACKEND_CLOSE_TIMEOUT_MS = 1e4;
|
|
285048
|
-
const REMOTE_WEBRTC_UNAVAILABLE_DETAIL = "Remote WebRTC browser display is not available: the current Agent Browser runtime exposes tab control but does not provide a media transport.";
|
|
285049
|
-
const REMOTE_WEBRTC_RELAY_REQUIRED_DETAIL = "Remote WebRTC browser display requires a TURN relay in cloud mode. Configure FORGE_BROWSER_WEBRTC_ICE_SERVERS with authenticated turn: or turns: URLs.";
|
|
285050
|
-
function nowIso$2() {
|
|
285051
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
285052
|
-
}
|
|
285053
|
-
function positiveTimeoutMs(value) {
|
|
285054
|
-
if (!value) return;
|
|
285055
|
-
const parsed = Number.parseInt(value, 10);
|
|
285056
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
285057
|
-
}
|
|
285058
|
-
function agentBrowserCommandTimeoutMs(command, env = process.env) {
|
|
285059
|
-
const explicitCommandTimeout = positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_COMMAND_TIMEOUT_MS);
|
|
285060
|
-
if (explicitCommandTimeout !== void 0) return explicitCommandTimeout;
|
|
285061
|
-
if (isAgentBrowserBoundedControlCommand(command)) return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_CONTROL_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_CONTROL_COMMAND_TIMEOUT_MS;
|
|
285062
|
-
switch (command.type) {
|
|
285063
|
-
case "screenshot":
|
|
285064
|
-
case "snapshot": return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_CAPTURE_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_CAPTURE_COMMAND_TIMEOUT_MS;
|
|
285065
|
-
case "newTab":
|
|
285066
|
-
case "navigate":
|
|
285067
|
-
case "goBack":
|
|
285068
|
-
case "goForward":
|
|
285069
|
-
case "reload": return DEFAULT_AGENT_BROWSER_NAVIGATION_COMMAND_TIMEOUT_MS;
|
|
285070
|
-
default: return DEFAULT_AGENT_BROWSER_FAST_COMMAND_TIMEOUT_MS;
|
|
285071
|
-
}
|
|
285072
|
-
}
|
|
285073
|
-
function agentBrowserCloseTimeoutMs(env = process.env) {
|
|
285074
|
-
return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_CLOSE_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_BRIDGE_CLOSE_TIMEOUT_MS;
|
|
285075
|
-
}
|
|
285076
|
-
function agentBrowserOpenTimeoutMs(env = process.env) {
|
|
285077
|
-
return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_OPEN_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_BRIDGE_OPEN_TIMEOUT_MS;
|
|
285078
|
-
}
|
|
285079
|
-
function agentBrowserFastTimeoutMs(env = process.env) {
|
|
285080
|
-
return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_BRIDGE_COMMAND_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_FAST_COMMAND_TIMEOUT_MS;
|
|
285081
|
-
}
|
|
285082
|
-
function agentBrowserPlaywrightBackendCloseTimeoutMs(env = process.env) {
|
|
285083
|
-
return positiveTimeoutMs(env.FORGE_AGENT_BROWSER_PLAYWRIGHT_BACKEND_CLOSE_TIMEOUT_MS) ?? DEFAULT_AGENT_BROWSER_PLAYWRIGHT_BACKEND_CLOSE_TIMEOUT_MS;
|
|
285084
|
-
}
|
|
285085
|
-
async function withAgentBrowserBridgeTimeout(label, promise, timeoutMs) {
|
|
285086
|
-
let timeout;
|
|
285087
|
-
try {
|
|
285088
|
-
return await Promise.race([promise, new Promise((_, reject) => {
|
|
285089
|
-
timeout = setTimeout(() => {
|
|
285090
|
-
reject(/* @__PURE__ */ new Error(`${label} timed out after ${Math.ceil(timeoutMs / 1e3)}s.`));
|
|
285091
|
-
}, timeoutMs);
|
|
285092
|
-
})]);
|
|
285093
|
-
} finally {
|
|
285094
|
-
if (timeout !== void 0) clearTimeout(timeout);
|
|
285095
|
-
}
|
|
285096
|
-
}
|
|
285097
|
-
async function openAgentBrowserSessionReady(bridge, input) {
|
|
285098
|
-
let resolveStarted;
|
|
285099
|
-
const started = new Promise((resolve) => {
|
|
285100
|
-
resolveStarted = resolve;
|
|
285101
|
-
});
|
|
285102
|
-
const unsubscribe = await bridge.subscribe({
|
|
285103
|
-
sessionId: input.sessionId,
|
|
285104
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
285105
|
-
}, (event) => {
|
|
285106
|
-
if (event.type === "session.started" && event.sessionId === input.sessionId) resolveStarted(event.payload.snapshot);
|
|
285107
|
-
});
|
|
285108
|
-
try {
|
|
285109
|
-
return await Promise.race([bridge.openSession(input), started]);
|
|
285110
|
-
} finally {
|
|
285111
|
-
unsubscribe();
|
|
285112
|
-
}
|
|
285113
|
-
}
|
|
285114
|
-
function isAgentBrowserBridgeTimeoutError(error) {
|
|
285115
|
-
const message = [
|
|
285116
|
-
require_Schema$1.is(BrowserError)(error) ? error.detail : void 0,
|
|
285117
|
-
error instanceof Error ? error.message : void 0,
|
|
285118
|
-
error instanceof Error ? error.stack : void 0,
|
|
285119
|
-
typeof error === "string" ? error : void 0,
|
|
285120
|
-
(() => {
|
|
285121
|
-
try {
|
|
285122
|
-
return JSON.stringify(error);
|
|
285123
|
-
} catch {
|
|
285124
|
-
return;
|
|
285125
|
-
}
|
|
285126
|
-
})(),
|
|
285127
|
-
String(error)
|
|
285128
|
-
].filter((part) => typeof part === "string" && part.length > 0).join("\n");
|
|
285129
|
-
return /timed out after \d+s\./.test(message);
|
|
285130
|
-
}
|
|
285131
|
-
function isAgentBrowserBoundedControlCommand(command) {
|
|
285132
|
-
switch (command.type) {
|
|
285133
|
-
case "click":
|
|
285134
|
-
case "clickPoint":
|
|
285135
|
-
case "pressKey":
|
|
285136
|
-
case "resetViewport":
|
|
285137
|
-
case "scroll":
|
|
285138
|
-
case "setViewport":
|
|
285139
|
-
case "type":
|
|
285140
|
-
case "typeText":
|
|
285141
|
-
case "wheel": return true;
|
|
285142
|
-
default: return false;
|
|
285143
|
-
}
|
|
285144
|
-
}
|
|
285145
|
-
function snapshotAfterTimedOutControl(snapshot, command) {
|
|
285146
|
-
switch (command.type) {
|
|
285147
|
-
case "setViewport": return {
|
|
285148
|
-
...snapshot,
|
|
285149
|
-
viewport: command.viewport,
|
|
285150
|
-
updatedAt: nowIso$2()
|
|
285151
|
-
};
|
|
285152
|
-
case "resetViewport": return {
|
|
285153
|
-
...snapshot,
|
|
285154
|
-
viewport: DEFAULT_AGENT_BROWSER_VIEWPORT,
|
|
285155
|
-
updatedAt: nowIso$2()
|
|
285156
|
-
};
|
|
285157
|
-
default: return snapshot;
|
|
285158
|
-
}
|
|
285159
|
-
}
|
|
285160
|
-
async function closeAgentBrowserBackendWithTimeout(closeBackend, env = process.env) {
|
|
285161
|
-
try {
|
|
285162
|
-
await withAgentBrowserBridgeTimeout("Agent Browser Playwright backend close", closeBackend(), agentBrowserPlaywrightBackendCloseTimeoutMs(env));
|
|
285163
|
-
} catch (error) {
|
|
285164
|
-
if (!isAgentBrowserBridgeTimeoutError(error)) throw error;
|
|
285165
|
-
console.warn("[browser] Agent Browser Playwright backend close timed out; continuing", { timeoutMs: agentBrowserPlaywrightBackendCloseTimeoutMs(env) });
|
|
285166
|
-
}
|
|
285167
|
-
}
|
|
285168
|
-
var ForgeAgentBrowserPlaywrightBackend = class extends PlaywrightBackend {
|
|
285169
|
-
async close() {
|
|
285170
|
-
await closeAgentBrowserBackendWithTimeout(() => super.close());
|
|
285171
|
-
}
|
|
285172
|
-
};
|
|
285173
|
-
function toBrowserError(error, fallback) {
|
|
285174
|
-
console.warn("[browser] Agent Browser host bridge operation failed", {
|
|
285175
|
-
operation: fallback,
|
|
285176
|
-
error
|
|
285177
|
-
});
|
|
285178
|
-
if (require_Schema$1.is(BrowserError)(error)) return error;
|
|
285179
|
-
if (error instanceof Error && error.message.trim().length > 0) return new BrowserError({ detail: error.message.trim() });
|
|
285180
|
-
return new BrowserError({ detail: fallback });
|
|
285181
|
-
}
|
|
285182
|
-
function remoteWebRtcUnavailableError(_input) {
|
|
285183
|
-
return new BrowserError({ detail: REMOTE_WEBRTC_UNAVAILABLE_DETAIL });
|
|
285184
|
-
}
|
|
285185
|
-
function remoteWebRtcRelayRequiredError(_input) {
|
|
285186
|
-
return new BrowserError({ detail: REMOTE_WEBRTC_RELAY_REQUIRED_DETAIL });
|
|
285187
|
-
}
|
|
285188
|
-
function subscriptionMatchesEvent(subscription, event) {
|
|
285189
|
-
if (subscription.input.threadId && subscription.input.threadId !== event.threadId) return false;
|
|
285190
|
-
if (subscription.input.browserId && subscription.input.browserId !== event.browserId) return false;
|
|
285191
|
-
if (subscription.input.environmentId && subscription.input.environmentId !== event.environmentId) return false;
|
|
285192
|
-
if (subscription.input.userId && subscription.input.userId !== event.userId) return false;
|
|
285193
|
-
return true;
|
|
285194
|
-
}
|
|
285195
|
-
function sessionIdForThread(threadId) {
|
|
285196
|
-
return BrowserSessionId.make(threadId);
|
|
285197
|
-
}
|
|
285198
|
-
function browserBackendOptionsForSession(input) {
|
|
285199
|
-
return {
|
|
285200
|
-
...input.baseOptions,
|
|
285201
|
-
userDataDir: browserProfileDirForSession(input.sessionId)
|
|
285202
|
-
};
|
|
285203
|
-
}
|
|
285204
|
-
function browserIdFrom(input) {
|
|
285205
|
-
return BrowserId.make(input.browserId ?? DEFAULT_AGENT_BROWSER_ID);
|
|
285206
|
-
}
|
|
285207
|
-
function normalizeUserVisibleBrowserUrl(url) {
|
|
285208
|
-
const trimmed = url.trim();
|
|
285209
|
-
if (trimmed.length === 0) return trimmed;
|
|
285210
|
-
if (ABSOLUTE_WEB_URL_PATTERN.test(trimmed) || SPECIAL_URL_PATTERN.test(trimmed) || trimmed.startsWith("//")) return trimmed;
|
|
285211
|
-
if (LOCAL_URL_LIKE_PATTERN.test(trimmed)) return `http://${trimmed}`;
|
|
285212
|
-
return `https://${trimmed}`;
|
|
285213
|
-
}
|
|
285214
|
-
function agentBrowserDisplayForRuntimeMode(input) {
|
|
285215
|
-
if (input.mode !== "desktop") {
|
|
285216
|
-
const hasRelay = hasTurnRelay(input.iceServers ?? []);
|
|
285217
|
-
return {
|
|
285218
|
-
transport: "remote-webrtc",
|
|
285219
|
-
status: "degraded",
|
|
285220
|
-
reason: input.requiresRelay === true && !hasRelay ? "network_unreachable" : "transport_unavailable",
|
|
285221
|
-
detail: input.requiresRelay === true && !hasRelay ? REMOTE_WEBRTC_RELAY_REQUIRED_DETAIL : "Remote WebRTC browser display is not attached yet.",
|
|
285222
|
-
...input.viewport ? { viewport: input.viewport } : {},
|
|
285223
|
-
...input.iceServers && input.iceServers.length > 0 ? { iceServers: input.iceServers } : {},
|
|
285224
|
-
reconnect: {
|
|
285225
|
-
status: "ready",
|
|
285226
|
-
attempt: 0
|
|
285227
|
-
}
|
|
285228
|
-
};
|
|
285229
|
-
}
|
|
285230
|
-
return {
|
|
285231
|
-
transport: "native-embedded",
|
|
285232
|
-
status: "connected",
|
|
285233
|
-
...input.viewport ? { viewport: input.viewport } : {},
|
|
285234
|
-
reconnect: {
|
|
285235
|
-
status: "ready",
|
|
285236
|
-
attempt: 0
|
|
285237
|
-
}
|
|
285238
|
-
};
|
|
285239
|
-
}
|
|
285240
|
-
function hasTurnRelay(iceServers) {
|
|
285241
|
-
return browserWebRtcIceServersIncludeRelay(iceServers);
|
|
285242
|
-
}
|
|
285243
|
-
async function resolveDisplayIceServers(options) {
|
|
285244
|
-
return options?.resolveDisplayIceServers ? await options.resolveDisplayIceServers() : options?.displayIceServers ?? [];
|
|
285245
|
-
}
|
|
285246
|
-
function webRtcDisplayRequiresRelay(serverConfig) {
|
|
285247
|
-
return serverConfig.mode === "web" && serverConfig.devUrl === void 0;
|
|
285248
|
-
}
|
|
285249
|
-
function toForgeTab(input) {
|
|
285250
|
-
return {
|
|
285251
|
-
tabId: BrowserTabId.make(input.tab.id),
|
|
285252
|
-
restoreKey: input.tab.id,
|
|
285253
|
-
...input.tab.title ? { title: input.tab.title } : {},
|
|
285254
|
-
url: input.tab.url ?? DEFAULT_URL,
|
|
285255
|
-
isActive: input.tab.isActive,
|
|
285256
|
-
canGoBack: input.tab.canGoBack ?? false,
|
|
285257
|
-
canGoForward: input.tab.canGoForward ?? false,
|
|
285258
|
-
renderMode: DEFAULT_RENDER_MODE,
|
|
285259
|
-
status: "live",
|
|
285260
|
-
...input.tab.navigationStatus !== void 0 ? { navigationStatus: input.tab.navigationStatus } : {},
|
|
285261
|
-
...input.tab.requestedUrl !== void 0 ? { requestedUrl: input.tab.requestedUrl } : {},
|
|
285262
|
-
...input.tab.navigationError !== void 0 ? { navigationError: input.tab.navigationError } : {},
|
|
285263
|
-
updatedAt: input.updatedAt
|
|
285264
|
-
};
|
|
285265
|
-
}
|
|
285266
|
-
function toForgeSession(input) {
|
|
285267
|
-
return {
|
|
285268
|
-
browserId: BrowserId.make(input.snapshot.browserId),
|
|
285269
|
-
threadId: input.threadId,
|
|
285270
|
-
environmentId: EnvironmentId.make(input.environmentId ?? DEFAULT_BROWSER_ENVIRONMENT_ID),
|
|
285271
|
-
userId: BrowserUserId.make(input.userId ?? "local-user"),
|
|
285272
|
-
agentId: input.agentId ?? null,
|
|
285273
|
-
ownerRole: input.ownerRole ?? "lead",
|
|
285274
|
-
name: input.snapshot.name,
|
|
285275
|
-
visibility: input.snapshot.visibility,
|
|
285276
|
-
viewport: input.snapshot.viewport,
|
|
285277
|
-
display: input.display ?? DEFAULT_AGENT_BROWSER_DISPLAY,
|
|
285278
|
-
sessionId: BrowserSessionId.make(input.snapshot.sessionId),
|
|
285279
|
-
...input.controlState ? {
|
|
285280
|
-
sessionEpoch: input.controlState.sessionEpoch,
|
|
285281
|
-
lastAppliedControlSequence: input.controlState.lastAppliedControlSequence
|
|
285282
|
-
} : {},
|
|
285283
|
-
activeTabId: input.snapshot.activeTabId ? BrowserTabId.make(input.snapshot.activeTabId) : null,
|
|
285284
|
-
tabs: input.snapshot.tabs.map((tab) => toForgeTab({
|
|
285285
|
-
tab,
|
|
285286
|
-
updatedAt: input.snapshot.updatedAt
|
|
285287
|
-
})),
|
|
285288
|
-
updatedAt: input.snapshot.updatedAt
|
|
285289
|
-
};
|
|
285290
|
-
}
|
|
285291
|
-
function resolveDisplay(snapshot, options) {
|
|
285292
|
-
if (typeof options?.display === "function") return options.display(snapshot);
|
|
285293
|
-
return options?.display ?? DEFAULT_AGENT_BROWSER_DISPLAY;
|
|
285294
|
-
}
|
|
285295
285344
|
function nativeHostBridgeUrl(baseUrl) {
|
|
285296
285345
|
return hostBridgePathUrl(baseUrl, "/agent-browser/host-bridge");
|
|
285297
285346
|
}
|
|
@@ -285383,6 +285432,63 @@ function subscribeToNativeHostBridgeEventsViaNodeHttp(input) {
|
|
|
285383
285432
|
request.end();
|
|
285384
285433
|
});
|
|
285385
285434
|
}
|
|
285435
|
+
async function requestNativeHostBridge(input) {
|
|
285436
|
+
const abortController = new AbortController();
|
|
285437
|
+
const timeout = setTimeout(() => abortController.abort(), input.method === "closeSession" ? input.deps.closeSessionTimeoutMs() : 3e4);
|
|
285438
|
+
const response = await input.fetchImpl(nativeHostBridgeUrl(input.options.baseUrl), {
|
|
285439
|
+
method: "POST",
|
|
285440
|
+
headers: {
|
|
285441
|
+
authorization: `Bearer ${input.options.token}`,
|
|
285442
|
+
"content-type": "application/json"
|
|
285443
|
+
},
|
|
285444
|
+
body: JSON.stringify({
|
|
285445
|
+
method: input.method,
|
|
285446
|
+
params: input.params
|
|
285447
|
+
}),
|
|
285448
|
+
signal: abortController.signal
|
|
285449
|
+
}).finally(() => {
|
|
285450
|
+
clearTimeout(timeout);
|
|
285451
|
+
});
|
|
285452
|
+
if (!response.ok) {
|
|
285453
|
+
const message = await readNativeHostBridgeError(response);
|
|
285454
|
+
throw new Error(`Native browser host bridge request failed: ${response.status} ${message}`);
|
|
285455
|
+
}
|
|
285456
|
+
return (await response.json()).result;
|
|
285457
|
+
}
|
|
285458
|
+
async function readNativeHostBridgeEventsViaFetch(input) {
|
|
285459
|
+
const response = await input.fetchImpl(nativeHostBridgeEventsUrl(input.options.baseUrl, input.identity), {
|
|
285460
|
+
headers: { authorization: `Bearer ${input.options.token}` },
|
|
285461
|
+
signal: input.abortSignal
|
|
285462
|
+
});
|
|
285463
|
+
if (!response.ok) {
|
|
285464
|
+
const message = await readNativeHostBridgeError(response);
|
|
285465
|
+
throw new Error(`Native browser host bridge event stream failed: ${response.status} ${message}`);
|
|
285466
|
+
}
|
|
285467
|
+
const reader = response.body?.getReader();
|
|
285468
|
+
if (!reader) throw new Error("Native browser host bridge event stream is unavailable.");
|
|
285469
|
+
await readNativeHostBridgeEventReader(reader, input);
|
|
285470
|
+
}
|
|
285471
|
+
async function readNativeHostBridgeEventReader(reader, input) {
|
|
285472
|
+
const decoder = new TextDecoder();
|
|
285473
|
+
let buffer = "";
|
|
285474
|
+
while (!input.abortSignal.aborted) {
|
|
285475
|
+
const { done, value } = await reader.read();
|
|
285476
|
+
if (done) return;
|
|
285477
|
+
buffer = drainNativeHostBridgeNdjsonBuffer(buffer + decoder.decode(value, { stream: true }), input.callback);
|
|
285478
|
+
}
|
|
285479
|
+
}
|
|
285480
|
+
async function readNativeHostBridgeEvents(input) {
|
|
285481
|
+
if (!input.options.fetch) {
|
|
285482
|
+
await subscribeToNativeHostBridgeEventsViaNodeHttp({
|
|
285483
|
+
abortSignal: input.abortSignal,
|
|
285484
|
+
callback: input.callback,
|
|
285485
|
+
input: input.identity,
|
|
285486
|
+
options: input.options
|
|
285487
|
+
});
|
|
285488
|
+
return;
|
|
285489
|
+
}
|
|
285490
|
+
await readNativeHostBridgeEventsViaFetch(input);
|
|
285491
|
+
}
|
|
285386
285492
|
function abortableDelay(ms, signal) {
|
|
285387
285493
|
if (signal.aborted) return Promise.resolve();
|
|
285388
285494
|
return new Promise((resolve) => {
|
|
@@ -285393,82 +285499,51 @@ function abortableDelay(ms, signal) {
|
|
|
285393
285499
|
}, { once: true });
|
|
285394
285500
|
});
|
|
285395
285501
|
}
|
|
285396
|
-
function
|
|
285397
|
-
|
|
285398
|
-
|
|
285399
|
-
|
|
285400
|
-
|
|
285401
|
-
|
|
285402
|
-
|
|
285403
|
-
|
|
285404
|
-
|
|
285405
|
-
"content-type": "application/json"
|
|
285406
|
-
},
|
|
285407
|
-
body: JSON.stringify({
|
|
285408
|
-
method,
|
|
285409
|
-
params
|
|
285410
|
-
}),
|
|
285411
|
-
signal: abortController.signal
|
|
285412
|
-
}).finally(() => {
|
|
285413
|
-
clearTimeout(timeout);
|
|
285414
|
-
});
|
|
285415
|
-
if (!response.ok) {
|
|
285416
|
-
const message = await readNativeHostBridgeError(response);
|
|
285417
|
-
throw new Error(`Native browser host bridge request failed: ${response.status} ${message}`);
|
|
285502
|
+
async function maintainNativeHostBridgeEventSubscription(input) {
|
|
285503
|
+
let attempt = 0;
|
|
285504
|
+
while (!input.abortSignal.aborted) {
|
|
285505
|
+
try {
|
|
285506
|
+
await readNativeHostBridgeEvents(input);
|
|
285507
|
+
attempt = 0;
|
|
285508
|
+
} catch (error) {
|
|
285509
|
+
if (input.abortSignal.aborted) return;
|
|
285510
|
+
console.warn("[browser] native host bridge event stream failed", error);
|
|
285418
285511
|
}
|
|
285419
|
-
|
|
285420
|
-
|
|
285421
|
-
|
|
285422
|
-
|
|
285423
|
-
|
|
285424
|
-
|
|
285425
|
-
|
|
285426
|
-
|
|
285427
|
-
|
|
285428
|
-
|
|
285429
|
-
|
|
285430
|
-
|
|
285431
|
-
|
|
285432
|
-
|
|
285433
|
-
|
|
285434
|
-
|
|
285435
|
-
|
|
285436
|
-
|
|
285437
|
-
if (!response.ok) {
|
|
285438
|
-
const message = await readNativeHostBridgeError(response);
|
|
285439
|
-
throw new Error(`Native browser host bridge event stream failed: ${response.status} ${message}`);
|
|
285440
|
-
}
|
|
285441
|
-
const reader = response.body?.getReader();
|
|
285442
|
-
if (!reader) throw new Error("Native browser host bridge event stream is unavailable.");
|
|
285443
|
-
const decoder = new TextDecoder();
|
|
285444
|
-
let buffer = "";
|
|
285445
|
-
while (!abortController.signal.aborted) {
|
|
285446
|
-
const { done, value } = await reader.read();
|
|
285447
|
-
if (done) return;
|
|
285448
|
-
buffer = drainNativeHostBridgeNdjsonBuffer(buffer + decoder.decode(value, { stream: true }), callback);
|
|
285449
|
-
}
|
|
285450
|
-
};
|
|
285451
|
-
(async () => {
|
|
285452
|
-
let attempt = 0;
|
|
285453
|
-
while (!abortController.signal.aborted) {
|
|
285454
|
-
try {
|
|
285455
|
-
await readStream();
|
|
285456
|
-
attempt = 0;
|
|
285457
|
-
} catch (error) {
|
|
285458
|
-
if (abortController.signal.aborted) return;
|
|
285459
|
-
console.warn("[browser] native host bridge event stream failed", error);
|
|
285460
|
-
}
|
|
285461
|
-
if (!abortController.signal.aborted) {
|
|
285462
|
-
const delayMs = hostBridgeReconnectDelayMs(attempt, NATIVE_HOST_EVENT_RECONNECT_BASE_MS, NATIVE_HOST_EVENT_RECONNECT_MAX_MS);
|
|
285463
|
-
attempt += 1;
|
|
285464
|
-
await abortableDelay(delayMs, abortController.signal);
|
|
285465
|
-
}
|
|
285466
|
-
}
|
|
285467
|
-
})();
|
|
285468
|
-
return () => {
|
|
285469
|
-
abortController.abort();
|
|
285470
|
-
};
|
|
285512
|
+
if (!input.abortSignal.aborted) {
|
|
285513
|
+
const delayMs = hostBridgeReconnectDelayMs(attempt, NATIVE_HOST_EVENT_RECONNECT_BASE_MS, NATIVE_HOST_EVENT_RECONNECT_MAX_MS);
|
|
285514
|
+
attempt += 1;
|
|
285515
|
+
await abortableDelay(delayMs, input.abortSignal);
|
|
285516
|
+
}
|
|
285517
|
+
}
|
|
285518
|
+
}
|
|
285519
|
+
function subscribeToNativeHostBridgeEvents(input) {
|
|
285520
|
+
const abortController = new AbortController();
|
|
285521
|
+
maintainNativeHostBridgeEventSubscription({
|
|
285522
|
+
abortSignal: abortController.signal,
|
|
285523
|
+
callback: input.callback,
|
|
285524
|
+
fetchImpl: input.fetchImpl,
|
|
285525
|
+
identity: input.identity,
|
|
285526
|
+
options: input.options
|
|
285527
|
+
});
|
|
285528
|
+
return () => {
|
|
285529
|
+
abortController.abort();
|
|
285471
285530
|
};
|
|
285531
|
+
}
|
|
285532
|
+
function createNativeHostBrowserBridgeClientImplementation(options, deps) {
|
|
285533
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
285534
|
+
const request = (method, params) => requestNativeHostBridge({
|
|
285535
|
+
deps,
|
|
285536
|
+
fetchImpl,
|
|
285537
|
+
method,
|
|
285538
|
+
options,
|
|
285539
|
+
params
|
|
285540
|
+
});
|
|
285541
|
+
const subscribe = (identity, callback) => subscribeToNativeHostBridgeEvents({
|
|
285542
|
+
callback,
|
|
285543
|
+
fetchImpl,
|
|
285544
|
+
identity,
|
|
285545
|
+
options
|
|
285546
|
+
});
|
|
285472
285547
|
return {
|
|
285473
285548
|
closeSession: (input = {}) => request("closeSession", input),
|
|
285474
285549
|
command: (input) => request("command", input),
|
|
@@ -285479,634 +285554,31 @@ function createNativeHostBrowserBridgeClient(options) {
|
|
|
285479
285554
|
subscribe: async (input, callback) => subscribe(input, callback)
|
|
285480
285555
|
};
|
|
285481
285556
|
}
|
|
285482
|
-
|
|
285483
|
-
|
|
285484
|
-
|
|
285485
|
-
|
|
285486
|
-
tabId: command.tabId
|
|
285487
|
-
};
|
|
285488
|
-
case "click": return {
|
|
285489
|
-
type: "click",
|
|
285490
|
-
tabId: command.tabId,
|
|
285491
|
-
selector: command.selector
|
|
285492
|
-
};
|
|
285493
|
-
case "clickPoint": return {
|
|
285494
|
-
type: "clickPoint",
|
|
285495
|
-
tabId: command.tabId,
|
|
285496
|
-
x: command.x,
|
|
285497
|
-
y: command.y,
|
|
285498
|
-
...command.button !== void 0 ? { button: command.button } : {},
|
|
285499
|
-
...command.clickCount !== void 0 ? { clickCount: command.clickCount } : {}
|
|
285500
|
-
};
|
|
285501
|
-
case "closeTab": return {
|
|
285502
|
-
type: "closeTab",
|
|
285503
|
-
tabId: command.tabId
|
|
285504
|
-
};
|
|
285505
|
-
case "consoleLogs": return {
|
|
285506
|
-
type: "consoleLogs",
|
|
285507
|
-
...command.tabId !== void 0 ? { tabId: command.tabId } : {},
|
|
285508
|
-
...command.limit !== void 0 ? { limit: command.limit } : {},
|
|
285509
|
-
...command.levels !== void 0 ? { levels: command.levels } : {}
|
|
285510
|
-
};
|
|
285511
|
-
case "goBack": return {
|
|
285512
|
-
type: "goBack",
|
|
285513
|
-
tabId: command.tabId
|
|
285514
|
-
};
|
|
285515
|
-
case "goForward": return {
|
|
285516
|
-
type: "goForward",
|
|
285517
|
-
tabId: command.tabId
|
|
285518
|
-
};
|
|
285519
|
-
case "nameSession": return {
|
|
285520
|
-
type: "nameSession",
|
|
285521
|
-
name: command.name
|
|
285522
|
-
};
|
|
285523
|
-
case "navigate": return {
|
|
285524
|
-
type: "navigate",
|
|
285525
|
-
url: normalizeUserVisibleBrowserUrl(command.url),
|
|
285526
|
-
...command.tabId !== void 0 ? { tabId: command.tabId } : {}
|
|
285527
|
-
};
|
|
285528
|
-
case "newTab": return {
|
|
285529
|
-
type: "newTab",
|
|
285530
|
-
...command.url !== void 0 ? { url: normalizeUserVisibleBrowserUrl(command.url) } : {},
|
|
285531
|
-
...command.makeActive !== void 0 ? { makeActive: command.makeActive } : {}
|
|
285532
|
-
};
|
|
285533
|
-
case "pressKey": return {
|
|
285534
|
-
type: "pressKey",
|
|
285535
|
-
tabId: command.tabId,
|
|
285536
|
-
key: command.key
|
|
285537
|
-
};
|
|
285538
|
-
case "reload": return {
|
|
285539
|
-
type: "reload",
|
|
285540
|
-
tabId: command.tabId
|
|
285541
|
-
};
|
|
285542
|
-
case "resetViewport": return { type: "resetViewport" };
|
|
285543
|
-
case "screenshot": return {
|
|
285544
|
-
type: "screenshot",
|
|
285545
|
-
tabId: command.tabId,
|
|
285546
|
-
...command.fullPage !== void 0 ? { fullPage: command.fullPage } : {}
|
|
285547
|
-
};
|
|
285548
|
-
case "scroll": return {
|
|
285549
|
-
type: "scroll",
|
|
285550
|
-
tabId: command.tabId,
|
|
285551
|
-
scrollX: 0,
|
|
285552
|
-
scrollY: command.toTop === true ? -900 : 900,
|
|
285553
|
-
...command.x !== void 0 ? { x: command.x } : {},
|
|
285554
|
-
...command.y !== void 0 ? { y: command.y } : {}
|
|
285555
|
-
};
|
|
285556
|
-
case "setViewport": return {
|
|
285557
|
-
type: "setViewport",
|
|
285558
|
-
viewport: command.viewport
|
|
285559
|
-
};
|
|
285560
|
-
case "setVisibility": return {
|
|
285561
|
-
type: "setVisibility",
|
|
285562
|
-
visibility: command.visibility
|
|
285563
|
-
};
|
|
285564
|
-
case "snapshot": return {
|
|
285565
|
-
type: "snapshot",
|
|
285566
|
-
tabId: command.tabId
|
|
285567
|
-
};
|
|
285568
|
-
case "type": return {
|
|
285569
|
-
type: "type",
|
|
285570
|
-
tabId: command.tabId,
|
|
285571
|
-
selector: command.selector,
|
|
285572
|
-
text: command.text,
|
|
285573
|
-
...command.append !== void 0 ? { append: command.append } : {}
|
|
285574
|
-
};
|
|
285575
|
-
case "typeText": return {
|
|
285576
|
-
type: "typeText",
|
|
285577
|
-
tabId: command.tabId,
|
|
285578
|
-
text: command.text,
|
|
285579
|
-
...command.pressEnter !== void 0 ? { pressEnter: command.pressEnter } : {}
|
|
285580
|
-
};
|
|
285581
|
-
case "wheel": return {
|
|
285582
|
-
type: "wheel",
|
|
285583
|
-
tabId: command.tabId,
|
|
285584
|
-
deltaX: command.deltaX,
|
|
285585
|
-
deltaY: command.deltaY,
|
|
285586
|
-
...command.x !== void 0 ? { x: command.x } : {},
|
|
285587
|
-
...command.y !== void 0 ? { y: command.y } : {}
|
|
285588
|
-
};
|
|
285589
|
-
default: throw new Error(`Unsupported browser command: ${command.type}`);
|
|
285590
|
-
}
|
|
285591
|
-
}
|
|
285592
|
-
function toStreamEvent(input) {
|
|
285593
|
-
const base = {
|
|
285594
|
-
eventId: EventId.make(crypto.randomUUID()),
|
|
285595
|
-
browserId: input.browserId,
|
|
285596
|
-
threadId: input.threadId,
|
|
285597
|
-
environmentId: EnvironmentId.make(input.environmentId),
|
|
285598
|
-
userId: BrowserUserId.make(input.userId),
|
|
285599
|
-
agentId: input.agentId,
|
|
285600
|
-
...input.controlState ? { sessionEpoch: input.controlState.sessionEpoch } : {},
|
|
285601
|
-
createdAt: nowIso$2()
|
|
285602
|
-
};
|
|
285603
|
-
switch (input.event.type) {
|
|
285604
|
-
case "session.started": return {
|
|
285605
|
-
...base,
|
|
285606
|
-
type: "session.started",
|
|
285607
|
-
payload: { snapshot: toForgeSession({
|
|
285608
|
-
snapshot: input.event.payload.snapshot,
|
|
285609
|
-
threadId: input.threadId,
|
|
285610
|
-
environmentId: input.environmentId,
|
|
285611
|
-
userId: input.userId,
|
|
285612
|
-
agentId: input.agentId,
|
|
285613
|
-
ownerRole: input.ownerRole,
|
|
285614
|
-
display: input.display,
|
|
285615
|
-
controlState: input.controlState
|
|
285616
|
-
}) }
|
|
285617
|
-
};
|
|
285618
|
-
case "session.updated": return {
|
|
285619
|
-
...base,
|
|
285620
|
-
type: input.event.type,
|
|
285621
|
-
payload: { snapshot: toForgeSession({
|
|
285622
|
-
snapshot: input.event.payload.snapshot,
|
|
285623
|
-
threadId: input.threadId,
|
|
285624
|
-
environmentId: input.environmentId,
|
|
285625
|
-
userId: input.userId,
|
|
285626
|
-
agentId: input.agentId,
|
|
285627
|
-
ownerRole: input.ownerRole,
|
|
285628
|
-
display: input.display,
|
|
285629
|
-
controlState: input.controlState
|
|
285630
|
-
}) }
|
|
285631
|
-
};
|
|
285632
|
-
case "session.closed": return {
|
|
285633
|
-
...base,
|
|
285634
|
-
type: "session.closed",
|
|
285635
|
-
payload: { sessionId: BrowserSessionId.make(input.event.payload.sessionId) }
|
|
285636
|
-
};
|
|
285637
|
-
case "tab.closed": return {
|
|
285638
|
-
...base,
|
|
285639
|
-
type: "tab.closed",
|
|
285640
|
-
payload: {
|
|
285641
|
-
sessionId: BrowserSessionId.make(input.event.sessionId),
|
|
285642
|
-
tabId: BrowserTabId.make(input.event.payload.tabId)
|
|
285643
|
-
}
|
|
285644
|
-
};
|
|
285645
|
-
case "snapshot.ready": return {
|
|
285646
|
-
...base,
|
|
285647
|
-
type: "snapshot.ready",
|
|
285648
|
-
payload: {
|
|
285649
|
-
sessionId: BrowserSessionId.make(input.event.sessionId),
|
|
285650
|
-
tabId: BrowserTabId.make(input.event.payload.tabId),
|
|
285651
|
-
captureId: BrowserCaptureId.make(input.event.payload.captureId ?? crypto.randomUUID()),
|
|
285652
|
-
capturedAt: input.event.payload.capturedAt,
|
|
285653
|
-
url: input.event.payload.url ?? DEFAULT_URL,
|
|
285654
|
-
viewport: input.event.payload.viewport,
|
|
285655
|
-
content: input.event.payload.content
|
|
285656
|
-
}
|
|
285657
|
-
};
|
|
285658
|
-
case "screenshot.ready": return {
|
|
285659
|
-
...base,
|
|
285660
|
-
type: "screenshot.ready",
|
|
285661
|
-
payload: {
|
|
285662
|
-
sessionId: BrowserSessionId.make(input.event.sessionId),
|
|
285663
|
-
tabId: BrowserTabId.make(input.event.payload.tabId),
|
|
285664
|
-
captureId: BrowserCaptureId.make(input.event.payload.captureId ?? crypto.randomUUID()),
|
|
285665
|
-
capturedAt: input.event.payload.capturedAt,
|
|
285666
|
-
url: input.event.payload.url ?? DEFAULT_URL,
|
|
285667
|
-
viewport: input.event.payload.viewport,
|
|
285668
|
-
imageDataUrl: input.event.payload.imageDataUrl
|
|
285669
|
-
}
|
|
285670
|
-
};
|
|
285671
|
-
case "console.logs.ready": return {
|
|
285672
|
-
...base,
|
|
285673
|
-
type: "console.logs.ready",
|
|
285674
|
-
payload: {
|
|
285675
|
-
sessionId: BrowserSessionId.make(input.event.sessionId),
|
|
285676
|
-
tabId: BrowserTabId.make(input.event.payload.tabId),
|
|
285677
|
-
logs: input.event.payload.logs.map((log) => ({
|
|
285678
|
-
level: log.level === "warn" ? "warning" : log.level === "log" ? "info" : log.level,
|
|
285679
|
-
message: log.message,
|
|
285680
|
-
createdAt: log.timestamp
|
|
285681
|
-
}))
|
|
285682
|
-
}
|
|
285683
|
-
};
|
|
285684
|
-
case "action.failed": return {
|
|
285685
|
-
...base,
|
|
285686
|
-
type: "action.failed",
|
|
285687
|
-
payload: {
|
|
285688
|
-
commandType: input.event.payload.commandType,
|
|
285689
|
-
message: input.event.payload.message,
|
|
285690
|
-
...input.event.payload.tabId ? { tabId: BrowserTabId.make(input.event.payload.tabId) } : {}
|
|
285691
|
-
}
|
|
285692
|
-
};
|
|
285693
|
-
case "dom.visible.ready":
|
|
285694
|
-
case "element.info.ready":
|
|
285695
|
-
case "element.screenshot.ready":
|
|
285696
|
-
case "clipboard.text.ready": return null;
|
|
285697
|
-
}
|
|
285557
|
+
//#endregion
|
|
285558
|
+
//#region src/browser/AgentBrowserHostBridgeAdapter.ts
|
|
285559
|
+
function createNativeHostBrowserBridgeClient(options) {
|
|
285560
|
+
return createNativeHostBrowserBridgeClientImplementation(options, { closeSessionTimeoutMs: agentBrowserCloseTimeoutMs });
|
|
285698
285561
|
}
|
|
285699
|
-
function
|
|
285562
|
+
function browserBackendOptionsForSession(input) {
|
|
285700
285563
|
return {
|
|
285701
|
-
|
|
285702
|
-
|
|
285703
|
-
snapshot: input.result.snapshot,
|
|
285704
|
-
threadId: input.command.threadId,
|
|
285705
|
-
environmentId: input.command.environmentId,
|
|
285706
|
-
userId: input.command.userId,
|
|
285707
|
-
display: input.display,
|
|
285708
|
-
controlState: input.controlState
|
|
285709
|
-
}),
|
|
285710
|
-
sessionEpoch: input.controlState.sessionEpoch,
|
|
285711
|
-
acknowledgedControlSequence: input.acknowledgedControlSequence,
|
|
285712
|
-
...input.result.detail ? { detail: input.result.detail } : {},
|
|
285713
|
-
...input.latestCapture ? { latestCapture: input.latestCapture } : {}
|
|
285564
|
+
...input.baseOptions,
|
|
285565
|
+
userDataDir: browserProfileDirForSession(input.sessionId)
|
|
285714
285566
|
};
|
|
285715
285567
|
}
|
|
285716
|
-
|
|
285717
|
-
|
|
285718
|
-
|
|
285719
|
-
|
|
285720
|
-
|
|
285721
|
-
|
|
285722
|
-
|
|
285723
|
-
function
|
|
285724
|
-
|
|
285725
|
-
|
|
285726
|
-
|
|
285727
|
-
event: input.event,
|
|
285728
|
-
threadId: input.command.threadId,
|
|
285729
|
-
browserId: input.browserId,
|
|
285730
|
-
environmentId: input.command.environmentId ?? DEFAULT_BROWSER_ENVIRONMENT_ID,
|
|
285731
|
-
userId: input.command.userId ?? "local-user",
|
|
285732
|
-
agentId: null,
|
|
285733
|
-
ownerRole: "lead",
|
|
285734
|
-
display: input.display,
|
|
285735
|
-
controlState: input.controlState
|
|
285736
|
-
});
|
|
285737
|
-
return mapped?.type === "snapshot.ready" || mapped?.type === "screenshot.ready" ? mapped : void 0;
|
|
285738
|
-
}
|
|
285739
|
-
function createAgentBrowserHostBridgeManager(bridge, options) {
|
|
285740
|
-
const localSubscriptions = /* @__PURE__ */ new Set();
|
|
285741
|
-
const connectedDisplays = /* @__PURE__ */ new Map();
|
|
285742
|
-
const controlStates = /* @__PURE__ */ new Map();
|
|
285743
|
-
const commandQueues = /* @__PURE__ */ new Map();
|
|
285744
|
-
const controlStateFor = (sessionId) => {
|
|
285745
|
-
const existing = controlStates.get(sessionId);
|
|
285746
|
-
if (existing) return existing;
|
|
285747
|
-
const created = {
|
|
285748
|
-
sessionEpoch: crypto.randomUUID(),
|
|
285749
|
-
lastAppliedControlSequence: 0
|
|
285750
|
-
};
|
|
285751
|
-
controlStates.set(sessionId, created);
|
|
285752
|
-
return created;
|
|
285753
|
-
};
|
|
285754
|
-
const runSerializedCommand = async (sessionId, task) => {
|
|
285755
|
-
const previous = commandQueues.get(sessionId) ?? Promise.resolve();
|
|
285756
|
-
let releaseCurrent;
|
|
285757
|
-
const current = new Promise((resolve) => {
|
|
285758
|
-
releaseCurrent = resolve;
|
|
285759
|
-
});
|
|
285760
|
-
const queued = previous.catch(() => void 0).then(() => current);
|
|
285761
|
-
commandQueues.set(sessionId, queued);
|
|
285762
|
-
await previous.catch(() => void 0);
|
|
285763
|
-
try {
|
|
285764
|
-
return await task();
|
|
285765
|
-
} finally {
|
|
285766
|
-
releaseCurrent();
|
|
285767
|
-
if (commandQueues.get(sessionId) === queued) commandQueues.delete(sessionId);
|
|
285768
|
-
}
|
|
285769
|
-
};
|
|
285770
|
-
const clearConnectedDisplay = (input) => {
|
|
285771
|
-
const active = connectedDisplays.get(input.sessionId);
|
|
285772
|
-
if (!active) return;
|
|
285773
|
-
if (input.tabId === void 0 || active.tabId === input.tabId) connectedDisplays.delete(input.sessionId);
|
|
285774
|
-
};
|
|
285775
|
-
const displayForSnapshot = (snapshot) => {
|
|
285776
|
-
const fallback = resolveDisplay(snapshot, options);
|
|
285777
|
-
const active = connectedDisplays.get(snapshot.sessionId);
|
|
285778
|
-
if (!active) return fallback;
|
|
285779
|
-
if (!snapshot.tabs.some((tab) => tab.id === active.tabId)) {
|
|
285780
|
-
connectedDisplays.delete(snapshot.sessionId);
|
|
285781
|
-
return fallback;
|
|
285782
|
-
}
|
|
285783
|
-
if (snapshot.activeTabId !== active.tabId) return fallback;
|
|
285784
|
-
const { reason: _reason, detail: _detail, ...fallbackWithoutDegradedReason } = fallback;
|
|
285785
|
-
return {
|
|
285786
|
-
...fallbackWithoutDegradedReason,
|
|
285787
|
-
...active.display,
|
|
285788
|
-
reconnect: active.display.reconnect ?? fallback.reconnect
|
|
285789
|
-
};
|
|
285790
|
-
};
|
|
285791
|
-
const duplicateControlResult = async (input, hostSessionId, controlState, acknowledgedControlSequence) => {
|
|
285792
|
-
const snapshot = await withAgentBrowserBridgeTimeout("Agent Browser duplicate command session read", bridge.getSession({
|
|
285793
|
-
sessionId: hostSessionId,
|
|
285794
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
285795
|
-
}), agentBrowserFastTimeoutMs());
|
|
285796
|
-
if (!snapshot) throw new BrowserError({ detail: "Browser session not found." });
|
|
285797
|
-
return {
|
|
285798
|
-
ok: true,
|
|
285799
|
-
snapshot: toForgeSession({
|
|
285800
|
-
snapshot,
|
|
285801
|
-
threadId: input.threadId,
|
|
285802
|
-
environmentId: input.environmentId,
|
|
285803
|
-
userId: input.userId,
|
|
285804
|
-
display: displayForSnapshot(snapshot),
|
|
285805
|
-
controlState
|
|
285806
|
-
}),
|
|
285807
|
-
sessionEpoch: controlState.sessionEpoch,
|
|
285808
|
-
acknowledgedControlSequence,
|
|
285809
|
-
detail: "Browser control was already applied"
|
|
285810
|
-
};
|
|
285811
|
-
};
|
|
285812
|
-
const dispatchHostCommand = async (input, hostIdentity, preCommandSnapshot) => {
|
|
285813
|
-
try {
|
|
285814
|
-
return await withAgentBrowserBridgeTimeout(`Agent Browser command ${input.command.type}`, bridge.command({
|
|
285815
|
-
...hostIdentity,
|
|
285816
|
-
command: toHostCommand(input.command)
|
|
285817
|
-
}), agentBrowserCommandTimeoutMs(input.command));
|
|
285818
|
-
} catch (error) {
|
|
285819
|
-
if (!(isAgentBrowserBridgeTimeoutError(error) && isAgentBrowserBoundedControlCommand(input.command) && preCommandSnapshot !== null)) throw error;
|
|
285820
|
-
console.warn("[browser] Agent Browser control command timed out after dispatch", {
|
|
285821
|
-
commandType: input.command.type,
|
|
285822
|
-
sessionId: hostIdentity.sessionId,
|
|
285823
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
285824
|
-
});
|
|
285825
|
-
return {
|
|
285826
|
-
ok: true,
|
|
285827
|
-
snapshot: snapshotAfterTimedOutControl(preCommandSnapshot, input.command),
|
|
285828
|
-
detail: "Agent Browser control command dispatch timed out before post-action observation completed."
|
|
285829
|
-
};
|
|
285830
|
-
}
|
|
285831
|
-
};
|
|
285832
|
-
const executeHostCommand = async (input, hostSessionId, controlState, controlSequence) => {
|
|
285833
|
-
const captureTabId = commandCaptureTabId(input.command);
|
|
285834
|
-
const hostIdentity = {
|
|
285835
|
-
sessionId: hostSessionId,
|
|
285836
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
285837
|
-
};
|
|
285838
|
-
const preCommandSnapshot = isAgentBrowserBoundedControlCommand(input.command) ? await withAgentBrowserBridgeTimeout(`Agent Browser command ${input.command.type} preflight session read`, bridge.getSession(hostIdentity), agentBrowserCommandTimeoutMs(input.command)).catch(() => null) : null;
|
|
285839
|
-
let latestCaptureEvent;
|
|
285840
|
-
const unsubscribeCapture = captureTabId ? await bridge.subscribe(hostIdentity, (event) => {
|
|
285841
|
-
if ((event.type === "snapshot.ready" || event.type === "screenshot.ready") && event.sessionId === hostSessionId && event.payload.tabId === captureTabId) latestCaptureEvent = event;
|
|
285842
|
-
}) : void 0;
|
|
285843
|
-
let result;
|
|
285844
|
-
try {
|
|
285845
|
-
result = await dispatchHostCommand(input, hostIdentity, preCommandSnapshot);
|
|
285846
|
-
} finally {
|
|
285847
|
-
unsubscribeCapture?.();
|
|
285848
|
-
}
|
|
285849
|
-
if (input.command.type === "closeTab") {
|
|
285850
|
-
await require_Schema$1.runPromise(options?.displayPeers?.closeForSession({
|
|
285851
|
-
sessionId: hostSessionId,
|
|
285852
|
-
tabId: input.command.tabId
|
|
285853
|
-
}) ?? require_Schema$1.void_);
|
|
285854
|
-
clearConnectedDisplay({
|
|
285855
|
-
sessionId: hostSessionId,
|
|
285856
|
-
tabId: input.command.tabId
|
|
285857
|
-
});
|
|
285858
|
-
}
|
|
285859
|
-
const display = displayForSnapshot(result.snapshot);
|
|
285860
|
-
controlState.lastAppliedControlSequence = controlSequence;
|
|
285861
|
-
return toCommandResult({
|
|
285862
|
-
result,
|
|
285863
|
-
command: input,
|
|
285864
|
-
display,
|
|
285865
|
-
controlState,
|
|
285866
|
-
acknowledgedControlSequence: controlSequence,
|
|
285867
|
-
latestCapture: latestCaptureEvent ? toCommandLatestCapture({
|
|
285868
|
-
event: latestCaptureEvent,
|
|
285869
|
-
command: input,
|
|
285870
|
-
browserId: BrowserId.make(result.snapshot.browserId),
|
|
285871
|
-
display,
|
|
285872
|
-
controlState
|
|
285873
|
-
}) : void 0
|
|
285874
|
-
});
|
|
285875
|
-
};
|
|
285876
|
-
const executeOrderedHostCommand = async (input) => {
|
|
285877
|
-
const hostSessionId = sessionIdForThread(input.threadId);
|
|
285878
|
-
return runSerializedCommand(hostSessionId, async () => {
|
|
285879
|
-
const controlState = controlStateFor(hostSessionId);
|
|
285880
|
-
const controlDecision = resolveBrowserControlDecision(controlState, input);
|
|
285881
|
-
if (controlDecision.type === "reject") throw new BrowserError({ detail: browserControlRejectionDetail(controlDecision) });
|
|
285882
|
-
if (controlDecision.type === "duplicate") return duplicateControlResult(input, hostSessionId, controlState, controlDecision.acknowledgedControlSequence);
|
|
285883
|
-
return executeHostCommand(input, hostSessionId, controlState, controlDecision.controlSequence);
|
|
285884
|
-
});
|
|
285885
|
-
};
|
|
285886
|
-
const publishLocalEvent = (event) => {
|
|
285887
|
-
for (const subscription of localSubscriptions) {
|
|
285888
|
-
if (!subscriptionMatchesEvent(subscription, event)) continue;
|
|
285889
|
-
require_Schema$1.runPromise(subscription.listener(event)).catch((error) => {
|
|
285890
|
-
console.warn("[browser] Failed to publish local browser event", { error });
|
|
285891
|
-
});
|
|
285568
|
+
/**
|
|
285569
|
+
* Builds the Playwright-backed backend from the lazily loaded module. The
|
|
285570
|
+
* `playwright` package and everything that statically imports it must stay out
|
|
285571
|
+
* of the standalone bundle's eager import graph. Deferring the import to
|
|
285572
|
+
* backend construction keeps boot playwright-free and lets a later provision
|
|
285573
|
+
* satisfy first use.
|
|
285574
|
+
*/
|
|
285575
|
+
function createForgeAgentBrowserPlaywrightBackend(PlaywrightBackend, options) {
|
|
285576
|
+
class ForgeAgentBrowserPlaywrightBackend extends PlaywrightBackend {
|
|
285577
|
+
async close() {
|
|
285578
|
+
await closeAgentBrowserBackendWithTimeout(() => super.close());
|
|
285892
285579
|
}
|
|
285893
|
-
}
|
|
285894
|
-
|
|
285895
|
-
publishLocalEvent({
|
|
285896
|
-
eventId: EventId.make(crypto.randomUUID()),
|
|
285897
|
-
browserId: event.input.browserId ?? browserIdFrom(event.input),
|
|
285898
|
-
threadId: event.input.threadId,
|
|
285899
|
-
environmentId: event.input.environmentId ?? DEFAULT_BROWSER_ENVIRONMENT_ID,
|
|
285900
|
-
userId: event.input.userId ?? "local-user",
|
|
285901
|
-
agentId: null,
|
|
285902
|
-
createdAt: nowIso$2(),
|
|
285903
|
-
type: "display.iceCandidate",
|
|
285904
|
-
payload: {
|
|
285905
|
-
sessionId: event.input.sessionId,
|
|
285906
|
-
tabId: event.input.tabId,
|
|
285907
|
-
clientId: event.input.clientId,
|
|
285908
|
-
candidate: event.candidate
|
|
285909
|
-
}
|
|
285910
|
-
});
|
|
285911
|
-
};
|
|
285912
|
-
return {
|
|
285913
|
-
openSession: (input) => require_Schema$1.tryPromise({
|
|
285914
|
-
try: async () => {
|
|
285915
|
-
const hostSessionId = sessionIdForThread(input.threadId);
|
|
285916
|
-
const initialUrl = input.initialUrl ? normalizeUserVisibleBrowserUrl(input.initialUrl) : void 0;
|
|
285917
|
-
options?.prepareRuntimeSession?.({
|
|
285918
|
-
sessionId: hostSessionId,
|
|
285919
|
-
initialUrl: initialUrl ?? DEFAULT_URL
|
|
285920
|
-
});
|
|
285921
|
-
let snapshot;
|
|
285922
|
-
try {
|
|
285923
|
-
snapshot = await withAgentBrowserBridgeTimeout("Agent Browser openSession", openAgentBrowserSessionReady(bridge, {
|
|
285924
|
-
sessionId: hostSessionId,
|
|
285925
|
-
...input.browserId ? { browserId: input.browserId } : {},
|
|
285926
|
-
...initialUrl ? { initialUrl } : {},
|
|
285927
|
-
...input.name ? { name: input.name } : {},
|
|
285928
|
-
...input.visibility ? { visibility: input.visibility } : {},
|
|
285929
|
-
...input.viewport ? { viewport: input.viewport } : {}
|
|
285930
|
-
}), agentBrowserOpenTimeoutMs());
|
|
285931
|
-
} catch (error) {
|
|
285932
|
-
if (!isAgentBrowserBridgeTimeoutError(error)) {
|
|
285933
|
-
options?.forgetRuntimeSession?.({ sessionId: hostSessionId });
|
|
285934
|
-
throw error;
|
|
285935
|
-
}
|
|
285936
|
-
console.warn("[browser] Agent Browser openSession timed out; quarantining session", {
|
|
285937
|
-
sessionId: hostSessionId,
|
|
285938
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
285939
|
-
});
|
|
285940
|
-
await withAgentBrowserBridgeTimeout("Agent Browser openSession quarantine close", bridge.closeSession({
|
|
285941
|
-
sessionId: hostSessionId,
|
|
285942
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
285943
|
-
}), agentBrowserCloseTimeoutMs()).catch(() => void 0);
|
|
285944
|
-
clearConnectedDisplay({ sessionId: hostSessionId });
|
|
285945
|
-
controlStates.delete(hostSessionId);
|
|
285946
|
-
options?.forgetRuntimeSession?.({ sessionId: hostSessionId });
|
|
285947
|
-
throw new Error(`${error instanceof Error ? error.message : String(error)} The pending browser session was quarantined and closed.`, { cause: error });
|
|
285948
|
-
}
|
|
285949
|
-
return toForgeSession({
|
|
285950
|
-
snapshot,
|
|
285951
|
-
threadId: input.threadId,
|
|
285952
|
-
environmentId: input.environmentId,
|
|
285953
|
-
userId: input.userId,
|
|
285954
|
-
agentId: input.agentId,
|
|
285955
|
-
ownerRole: input.ownerRole,
|
|
285956
|
-
display: displayForSnapshot(snapshot),
|
|
285957
|
-
controlState: controlStateFor(snapshot.sessionId)
|
|
285958
|
-
});
|
|
285959
|
-
},
|
|
285960
|
-
catch: (error) => toBrowserError(error, "Failed to open Agent Browser session.")
|
|
285961
|
-
}),
|
|
285962
|
-
closeSession: (input) => require_Schema$1.tryPromise({
|
|
285963
|
-
try: async () => {
|
|
285964
|
-
await require_Schema$1.runPromise(options?.displayPeers?.closeForSession({ sessionId: sessionIdForThread(input.threadId) }) ?? require_Schema$1.void_);
|
|
285965
|
-
const hostSessionId = sessionIdForThread(input.threadId);
|
|
285966
|
-
const hostIdentity = {
|
|
285967
|
-
sessionId: hostSessionId,
|
|
285968
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
285969
|
-
};
|
|
285970
|
-
try {
|
|
285971
|
-
await withAgentBrowserBridgeTimeout("Agent Browser closeSession", bridge.closeSession(hostIdentity), agentBrowserCloseTimeoutMs());
|
|
285972
|
-
} catch (error) {
|
|
285973
|
-
if (!isAgentBrowserBridgeTimeoutError(error)) throw error;
|
|
285974
|
-
if (await withAgentBrowserBridgeTimeout("Agent Browser closeSession verification", bridge.getSession(hostIdentity), 2e3).catch(() => void 0) === null) console.warn("[browser] Agent Browser closeSession timed out after session removal", {
|
|
285975
|
-
sessionId: hostSessionId,
|
|
285976
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
285977
|
-
});
|
|
285978
|
-
else console.warn("[browser] Agent Browser closeSession timed out; forgetting session", {
|
|
285979
|
-
sessionId: hostSessionId,
|
|
285980
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
285981
|
-
});
|
|
285982
|
-
clearConnectedDisplay({ sessionId: hostSessionId });
|
|
285983
|
-
controlStates.delete(hostSessionId);
|
|
285984
|
-
options?.forgetRuntimeSession?.({ sessionId: hostSessionId });
|
|
285985
|
-
return;
|
|
285986
|
-
}
|
|
285987
|
-
clearConnectedDisplay({ sessionId: hostSessionId });
|
|
285988
|
-
controlStates.delete(hostSessionId);
|
|
285989
|
-
options?.forgetRuntimeSession?.({ sessionId: hostSessionId });
|
|
285990
|
-
},
|
|
285991
|
-
catch: (error) => toBrowserError(error, "Failed to close Agent Browser session.")
|
|
285992
|
-
}),
|
|
285993
|
-
clearBrowsingData: () => require_Schema$1.succeed({
|
|
285994
|
-
closedSessionCount: 0,
|
|
285995
|
-
deletedSessionCount: 0,
|
|
285996
|
-
deletedCaptureCount: 0
|
|
285997
|
-
}),
|
|
285998
|
-
getSession: (input) => require_Schema$1.tryPromise({
|
|
285999
|
-
try: async () => {
|
|
286000
|
-
const snapshot = await withAgentBrowserBridgeTimeout("Agent Browser getSession", bridge.getSession({
|
|
286001
|
-
sessionId: sessionIdForThread(input.threadId),
|
|
286002
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
286003
|
-
}), agentBrowserFastTimeoutMs());
|
|
286004
|
-
return snapshot ? toForgeSession({
|
|
286005
|
-
snapshot,
|
|
286006
|
-
threadId: input.threadId,
|
|
286007
|
-
environmentId: input.environmentId,
|
|
286008
|
-
userId: input.userId,
|
|
286009
|
-
display: displayForSnapshot(snapshot),
|
|
286010
|
-
controlState: controlStateFor(snapshot.sessionId)
|
|
286011
|
-
}) : null;
|
|
286012
|
-
},
|
|
286013
|
-
catch: (error) => toBrowserError(error, "Failed to read Agent Browser session.")
|
|
286014
|
-
}),
|
|
286015
|
-
listSessions: (input) => require_Schema$1.tryPromise({
|
|
286016
|
-
try: async () => {
|
|
286017
|
-
return (await withAgentBrowserBridgeTimeout("Agent Browser listSessions", bridge.listSessions(input.browserId ? { browserId: input.browserId } : void 0), agentBrowserFastTimeoutMs())).map((snapshot) => toForgeSession({
|
|
286018
|
-
snapshot,
|
|
286019
|
-
threadId: input.threadId ?? ThreadId.make(snapshot.sessionId),
|
|
286020
|
-
environmentId: input.environmentId,
|
|
286021
|
-
userId: input.userId,
|
|
286022
|
-
display: displayForSnapshot(snapshot),
|
|
286023
|
-
controlState: controlStateFor(snapshot.sessionId)
|
|
286024
|
-
}));
|
|
286025
|
-
},
|
|
286026
|
-
catch: (error) => toBrowserError(error, "Failed to list Agent Browser sessions.")
|
|
286027
|
-
}),
|
|
286028
|
-
command: (input) => require_Schema$1.tryPromise({
|
|
286029
|
-
try: () => executeOrderedHostCommand(input),
|
|
286030
|
-
catch: (error) => toBrowserError(error, "Failed to run Agent Browser command.")
|
|
286031
|
-
}),
|
|
286032
|
-
attachDisplay: (input) => {
|
|
286033
|
-
const displayPeers = options?.displayPeers;
|
|
286034
|
-
if (!displayPeers) return require_Schema$1.fail(remoteWebRtcUnavailableError(input));
|
|
286035
|
-
return require_Schema$1.tryPromise({
|
|
286036
|
-
try: async () => {
|
|
286037
|
-
const iceServers = await resolveDisplayIceServers(options);
|
|
286038
|
-
if (options?.requiresWebRtcRelay === true && !hasTurnRelay(iceServers)) throw remoteWebRtcRelayRequiredError(input);
|
|
286039
|
-
const result = await require_Schema$1.runPromise(displayPeers.attach(input, {
|
|
286040
|
-
display: {
|
|
286041
|
-
transport: "remote-webrtc",
|
|
286042
|
-
status: "connecting",
|
|
286043
|
-
...input.viewport ? { viewport: input.viewport } : {},
|
|
286044
|
-
devicePixelRatio: 1,
|
|
286045
|
-
...iceServers.length > 0 ? { iceServers } : {},
|
|
286046
|
-
reconnect: {
|
|
286047
|
-
status: "ready",
|
|
286048
|
-
attempt: 0
|
|
286049
|
-
}
|
|
286050
|
-
},
|
|
286051
|
-
onIceCandidate: publishWebRtcIceCandidate
|
|
286052
|
-
}));
|
|
286053
|
-
connectedDisplays.set(input.sessionId, {
|
|
286054
|
-
tabId: input.tabId,
|
|
286055
|
-
display: result.display
|
|
286056
|
-
});
|
|
286057
|
-
return result;
|
|
286058
|
-
},
|
|
286059
|
-
catch: (error) => {
|
|
286060
|
-
if (require_Schema$1.is(BrowserError)(error)) return error;
|
|
286061
|
-
return toBrowserError(error, "Failed to attach WebRTC browser display.");
|
|
286062
|
-
}
|
|
286063
|
-
});
|
|
286064
|
-
},
|
|
286065
|
-
addDisplayIceCandidate: (input) => {
|
|
286066
|
-
const displayPeers = options?.displayPeers;
|
|
286067
|
-
if (!displayPeers) return require_Schema$1.fail(remoteWebRtcUnavailableError(input));
|
|
286068
|
-
return displayPeers.addIceCandidate(input);
|
|
286069
|
-
},
|
|
286070
|
-
subscribe: (input, listener) => require_Schema$1.promise(async () => {
|
|
286071
|
-
const localSubscription = {
|
|
286072
|
-
input,
|
|
286073
|
-
listener
|
|
286074
|
-
};
|
|
286075
|
-
localSubscriptions.add(localSubscription);
|
|
286076
|
-
const threadId = input.threadId ?? ThreadId.make("unknown-thread");
|
|
286077
|
-
const browserId = browserIdFrom(input);
|
|
286078
|
-
const environmentId = input.environmentId ?? DEFAULT_BROWSER_ENVIRONMENT_ID;
|
|
286079
|
-
const userId = input.userId ?? "local-user";
|
|
286080
|
-
const subscribeInput = {
|
|
286081
|
-
...input.threadId ? { sessionId: sessionIdForThread(input.threadId) } : {},
|
|
286082
|
-
...input.browserId ? { browserId: input.browserId } : {}
|
|
286083
|
-
};
|
|
286084
|
-
const unsubscribeBridge = await bridge.subscribe(subscribeInput, (event) => {
|
|
286085
|
-
if (event.type === "session.closed") clearConnectedDisplay({ sessionId: event.payload.sessionId });
|
|
286086
|
-
if (event.type === "tab.closed") clearConnectedDisplay({
|
|
286087
|
-
sessionId: event.sessionId,
|
|
286088
|
-
tabId: event.payload.tabId
|
|
286089
|
-
});
|
|
286090
|
-
const mapped = toStreamEvent({
|
|
286091
|
-
event,
|
|
286092
|
-
threadId,
|
|
286093
|
-
browserId,
|
|
286094
|
-
environmentId,
|
|
286095
|
-
userId,
|
|
286096
|
-
agentId: null,
|
|
286097
|
-
ownerRole: "lead",
|
|
286098
|
-
display: event.type === "session.started" || event.type === "session.updated" ? displayForSnapshot(event.payload.snapshot) : DEFAULT_AGENT_BROWSER_DISPLAY,
|
|
286099
|
-
controlState: controlStateFor(event.type === "session.closed" ? event.payload.sessionId : event.sessionId)
|
|
286100
|
-
});
|
|
286101
|
-
if (mapped) require_Schema$1.runPromise(listener(mapped));
|
|
286102
|
-
if (event.type === "session.closed") controlStates.delete(event.payload.sessionId);
|
|
286103
|
-
});
|
|
286104
|
-
return () => {
|
|
286105
|
-
localSubscriptions.delete(localSubscription);
|
|
286106
|
-
unsubscribeBridge();
|
|
286107
|
-
};
|
|
286108
|
-
})
|
|
286109
|
-
};
|
|
285580
|
+
}
|
|
285581
|
+
return new ForgeAgentBrowserPlaywrightBackend(options);
|
|
286110
285582
|
}
|
|
286111
285583
|
function shouldUseNativeHostBridge(serverConfig) {
|
|
286112
285584
|
return serverConfig.mode === "desktop" && typeof serverConfig.nativeBrowserHostUrl === "string" && serverConfig.nativeBrowserHostUrl.trim().length > 0 && typeof serverConfig.nativeBrowserHostToken === "string" && serverConfig.nativeBrowserHostToken.trim().length > 0;
|
|
@@ -286131,8 +285603,10 @@ const AgentBrowserManagerLive = require_Schema$1.effect(BrowserManager, require_
|
|
|
286131
285603
|
const displayCaptureRuntime = yield* require_Schema$1.promise(() => createBrowserWebRtcDisplayCaptureRuntime({ chromiumExecutablePath: process.env.FORGE_PLAYWRIGHT_EXECUTABLE_PATH }));
|
|
286132
285604
|
yield* require_Schema$1.addFinalizer(() => require_Schema$1.promise(() => displayCaptureRuntime.close()).pipe(require_Schema$1.orDie));
|
|
286133
285605
|
if (!displayCaptureRuntime.available) yield* (!requiresWebRtcRelay && displayCaptureRuntime.unavailableDetail?.includes("root-owned /tmp/.X11-unix") === true ? require_Schema$1.logInfo : require_Schema$1.logWarning)("browser WebRTC display capture runtime unavailable", { detail: displayCaptureRuntime.unavailableDetail });
|
|
285606
|
+
const layerContext = yield* require_Schema$1.context();
|
|
285607
|
+
const runLayerFork = require_Schema$1.runForkWith(layerContext);
|
|
286134
285608
|
const displayTaskHoldManager = makeSpriteTaskHoldManager(process.env, (message, cause) => {
|
|
286135
|
-
|
|
285609
|
+
runLayerFork(require_Schema$1.logWarning(message, { cause: cause instanceof Error ? cause.message : cause }));
|
|
286136
285610
|
});
|
|
286137
285611
|
const displayPeers = createWebRtcDisplaySessionManager({
|
|
286138
285612
|
mediaSourceFactory: displayCaptureRuntime.mediaSourceFactory,
|
|
@@ -286144,71 +285618,98 @@ const AgentBrowserManagerLive = require_Schema$1.effect(BrowserManager, require_
|
|
|
286144
285618
|
yield* require_Schema$1.addFinalizer(() => require_Schema$1.promise(() => displayTaskHoldManager?.releaseAll() ?? Promise.resolve()));
|
|
286145
285619
|
const baseBackendOptions = displayCaptureRuntime.backendOptions;
|
|
286146
285620
|
const sessionInitialUrls = /* @__PURE__ */ new Map();
|
|
286147
|
-
|
|
286148
|
-
|
|
286149
|
-
|
|
286150
|
-
|
|
286151
|
-
|
|
286152
|
-
|
|
286153
|
-
|
|
286154
|
-
|
|
286155
|
-
|
|
286156
|
-
|
|
286157
|
-
|
|
286158
|
-
|
|
286159
|
-
|
|
285621
|
+
let runtimeBuild = null;
|
|
285622
|
+
const lazyDisposers = [];
|
|
285623
|
+
const buildPlaywrightRuntime = async () => {
|
|
285624
|
+
const { PlaywrightBackend } = await Promise.resolve().then(() => require("./playwright-backend-BONxTCSA.cjs"));
|
|
285625
|
+
const runtime = createAgentBrowserRuntime({
|
|
285626
|
+
backends: { [DEFAULT_AGENT_BROWSER_ID]: createForgeAgentBrowserPlaywrightBackend(PlaywrightBackend, {
|
|
285627
|
+
...browserBackendOptionsForSession({
|
|
285628
|
+
baseOptions: baseBackendOptions,
|
|
285629
|
+
sessionId: "default"
|
|
285630
|
+
}),
|
|
285631
|
+
name: "Forge Agent Browser"
|
|
285632
|
+
}) },
|
|
285633
|
+
backendFactory: (input) => createForgeAgentBrowserPlaywrightBackend(PlaywrightBackend, {
|
|
285634
|
+
...browserBackendOptionsForSession({
|
|
285635
|
+
baseOptions: baseBackendOptions,
|
|
285636
|
+
initialUrl: sessionInitialUrls.get(input.sessionId),
|
|
285637
|
+
sessionId: input.sessionId
|
|
285638
|
+
}),
|
|
285639
|
+
name: `Forge Agent Browser ${input.sessionId}`
|
|
285640
|
+
})
|
|
285641
|
+
});
|
|
285642
|
+
lazyDisposers.push(() => runtime.close());
|
|
285643
|
+
const hostBridge = createHostBrowserBridge({
|
|
285644
|
+
browserId: DEFAULT_AGENT_BROWSER_ID,
|
|
285645
|
+
closeTabsOnSessionClose: false,
|
|
285646
|
+
defaultViewport: DEFAULT_AGENT_BROWSER_VIEWPORT,
|
|
285647
|
+
runtime,
|
|
285648
|
+
tabLifecycle: {
|
|
285649
|
+
activateNextTabOnClose: true,
|
|
285650
|
+
createFallbackTabOnLastClose: true,
|
|
285651
|
+
fallbackUrl: DEFAULT_URL
|
|
285652
|
+
}
|
|
285653
|
+
});
|
|
285654
|
+
const reaperConfig = browserOrphanReaperConfigFromEnv(process.env, AGENT_BROWSER_PROFILE_ROOT);
|
|
285655
|
+
if (reaperConfig.enabled && process.platform !== "win32") {
|
|
285656
|
+
const reaper = createBrowserOrphanReaper(createSystemBrowserOrphanReaperDeps({
|
|
285657
|
+
profileRoot: AGENT_BROWSER_PROFILE_ROOT,
|
|
285658
|
+
listSessions: () => hostBridge.listSessions(),
|
|
285659
|
+
closeSession: async (sessionId) => {
|
|
285660
|
+
await hostBridge.closeSession({ sessionId: BrowserSessionId.make(sessionId) });
|
|
285661
|
+
}
|
|
285662
|
+
}), reaperConfig);
|
|
285663
|
+
const reaperInterval = setInterval(() => {
|
|
285664
|
+
runLayerFork(reaper.sweep);
|
|
285665
|
+
}, reaperConfig.intervalMs);
|
|
285666
|
+
reaperInterval.unref?.();
|
|
285667
|
+
lazyDisposers.push(() => clearInterval(reaperInterval));
|
|
285668
|
+
runLayerFork(reaper.sweep);
|
|
285669
|
+
}
|
|
285670
|
+
return createAgentBrowserHostBridgeManager(hostBridge, {
|
|
285671
|
+
display: (snapshot) => agentBrowserDisplayForRuntimeMode({
|
|
285672
|
+
mode: serverConfig.mode,
|
|
285673
|
+
iceServers: serverConfig.browserWebRtcIceServerResolver.getCachedIceServers().length > 0 ? serverConfig.browserWebRtcIceServerResolver.getCachedIceServers() : serverConfig.browserWebRtcIceServers,
|
|
285674
|
+
requiresRelay: requiresWebRtcRelay,
|
|
285675
|
+
viewport: snapshot.viewport
|
|
286160
285676
|
}),
|
|
286161
|
-
|
|
286162
|
-
|
|
286163
|
-
|
|
286164
|
-
|
|
286165
|
-
|
|
286166
|
-
|
|
286167
|
-
|
|
286168
|
-
|
|
286169
|
-
|
|
286170
|
-
|
|
286171
|
-
|
|
286172
|
-
|
|
286173
|
-
|
|
286174
|
-
|
|
286175
|
-
});
|
|
286176
|
-
const
|
|
286177
|
-
|
|
286178
|
-
|
|
286179
|
-
|
|
286180
|
-
listSessions: () => hostBridge.listSessions(),
|
|
286181
|
-
closeSession: async (sessionId) => {
|
|
286182
|
-
await hostBridge.closeSession({ sessionId: BrowserSessionId.make(sessionId) });
|
|
286183
|
-
}
|
|
286184
|
-
}), reaperConfig);
|
|
286185
|
-
const reaperContext = yield* require_Schema$1.context();
|
|
286186
|
-
const runReaperFork = require_Schema$1.runForkWith(reaperContext);
|
|
286187
|
-
const reaperInterval = setInterval(() => {
|
|
286188
|
-
runReaperFork(reaper.sweep);
|
|
286189
|
-
}, reaperConfig.intervalMs);
|
|
286190
|
-
reaperInterval.unref?.();
|
|
286191
|
-
yield* require_Schema$1.addFinalizer(() => require_Schema$1.sync(() => clearInterval(reaperInterval)));
|
|
286192
|
-
runReaperFork(reaper.sweep);
|
|
286193
|
-
}
|
|
286194
|
-
return createAgentBrowserHostBridgeManager(hostBridge, {
|
|
286195
|
-
display: (snapshot) => agentBrowserDisplayForRuntimeMode({
|
|
286196
|
-
mode: serverConfig.mode,
|
|
286197
|
-
iceServers: serverConfig.browserWebRtcIceServerResolver.getCachedIceServers().length > 0 ? serverConfig.browserWebRtcIceServerResolver.getCachedIceServers() : serverConfig.browserWebRtcIceServers,
|
|
286198
|
-
requiresRelay: requiresWebRtcRelay,
|
|
286199
|
-
viewport: snapshot.viewport
|
|
286200
|
-
}),
|
|
286201
|
-
displayIceServers: serverConfig.browserWebRtcIceServers,
|
|
286202
|
-
resolveDisplayIceServers: serverConfig.browserWebRtcIceServerResolver.resolveIceServers,
|
|
286203
|
-
displayPeers,
|
|
286204
|
-
prepareRuntimeSession: (input) => {
|
|
286205
|
-
sessionInitialUrls.set(input.sessionId, input.initialUrl);
|
|
286206
|
-
},
|
|
286207
|
-
forgetRuntimeSession: (input) => {
|
|
286208
|
-
sessionInitialUrls.delete(input.sessionId);
|
|
285677
|
+
displayIceServers: serverConfig.browserWebRtcIceServers,
|
|
285678
|
+
resolveDisplayIceServers: serverConfig.browserWebRtcIceServerResolver.resolveIceServers,
|
|
285679
|
+
displayPeers,
|
|
285680
|
+
prepareRuntimeSession: (input) => {
|
|
285681
|
+
sessionInitialUrls.set(input.sessionId, input.initialUrl);
|
|
285682
|
+
},
|
|
285683
|
+
forgetRuntimeSession: (input) => {
|
|
285684
|
+
sessionInitialUrls.delete(input.sessionId);
|
|
285685
|
+
},
|
|
285686
|
+
requiresWebRtcRelay
|
|
285687
|
+
});
|
|
285688
|
+
};
|
|
285689
|
+
yield* require_Schema$1.addFinalizer(() => require_Schema$1.promise(async () => {
|
|
285690
|
+
for (const dispose of lazyDisposers.toReversed()) await dispose();
|
|
285691
|
+
}).pipe(require_Schema$1.orDie));
|
|
285692
|
+
const ensurePlaywrightRuntime = () => require_Schema$1.tryPromise({
|
|
285693
|
+
try: () => {
|
|
285694
|
+
runtimeBuild ??= buildPlaywrightRuntime();
|
|
285695
|
+
return runtimeBuild;
|
|
286209
285696
|
},
|
|
286210
|
-
|
|
285697
|
+
catch: (cause) => {
|
|
285698
|
+
runtimeBuild = null;
|
|
285699
|
+
return toBrowserError(cause, "Browser backend could not be loaded. Browser packages load on first browser use and must be provisioned on this runtime.");
|
|
285700
|
+
}
|
|
286211
285701
|
});
|
|
285702
|
+
return {
|
|
285703
|
+
openSession: (input) => require_Schema$1.flatMap(ensurePlaywrightRuntime(), (m) => m.openSession(input)),
|
|
285704
|
+
closeSession: (input) => require_Schema$1.flatMap(ensurePlaywrightRuntime(), (m) => m.closeSession(input)),
|
|
285705
|
+
clearBrowsingData: (input) => require_Schema$1.flatMap(ensurePlaywrightRuntime(), (m) => m.clearBrowsingData(input)),
|
|
285706
|
+
getSession: (input) => require_Schema$1.flatMap(ensurePlaywrightRuntime(), (m) => m.getSession(input)),
|
|
285707
|
+
listSessions: (input) => require_Schema$1.flatMap(ensurePlaywrightRuntime(), (m) => m.listSessions(input)),
|
|
285708
|
+
command: (input) => require_Schema$1.flatMap(ensurePlaywrightRuntime(), (m) => m.command(input)),
|
|
285709
|
+
attachDisplay: (input) => require_Schema$1.flatMap(ensurePlaywrightRuntime(), (m) => m.attachDisplay(input)),
|
|
285710
|
+
addDisplayIceCandidate: (input) => require_Schema$1.flatMap(ensurePlaywrightRuntime(), (m) => m.addDisplayIceCandidate(input)),
|
|
285711
|
+
subscribe: (input, listener) => require_Schema$1.flatMap(require_Schema$1.orDie(ensurePlaywrightRuntime()), (m) => m.subscribe(input, listener))
|
|
285712
|
+
};
|
|
286212
285713
|
}));
|
|
286213
285714
|
//#endregion
|
|
286214
285715
|
//#region src/devTunnel/http.ts
|
|
@@ -298247,7 +297748,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
298247
297748
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
298248
297749
|
*/
|
|
298249
297750
|
function readBakedBuildCommit() {
|
|
298250
|
-
return "
|
|
297751
|
+
return "22e4740c3b851434e13fd47fc22833c48d658450";
|
|
298251
297752
|
}
|
|
298252
297753
|
async function resolveServerBuildCommit(input) {
|
|
298253
297754
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -321872,13 +321373,13 @@ const gitUrlCloneRouteLayer = add$1("POST", "/api/cloud/git/clone-url", require_
|
|
|
321872
321373
|
//#endregion
|
|
321873
321374
|
//#region src/server.ts
|
|
321874
321375
|
const PtyAdapterLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
321875
|
-
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunPTY-
|
|
321876
|
-
else return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodePTY-
|
|
321376
|
+
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunPTY-BiTlUj63.cjs")))).layer;
|
|
321377
|
+
else return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodePTY-BJ7874MO.cjs")))).layer;
|
|
321877
321378
|
}));
|
|
321878
321379
|
const HttpServerLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
321879
321380
|
const config = yield* ServerConfig;
|
|
321880
|
-
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunHttpServer-
|
|
321881
|
-
const [NodeHttpServer, NodeHttp] = yield* require_Schema$1.all([require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeHttpServer-
|
|
321381
|
+
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunHttpServer-CXndtc1j.cjs")))).layer(bunHttpServerOptions(config));
|
|
321382
|
+
const [NodeHttpServer, NodeHttp] = yield* require_Schema$1.all([require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeHttpServer-B4dCKqYY.cjs"))), require_Schema$1.promise(() => import("node:http"))]);
|
|
321882
321383
|
return NodeHttpServer.layer(NodeHttp.createServer, {
|
|
321883
321384
|
host: config.host,
|
|
321884
321385
|
port: config.port
|
|
@@ -321886,7 +321387,7 @@ const HttpServerLive = require_Schema$1.unwrap(require_Schema$1.gen(function* ()
|
|
|
321886
321387
|
}));
|
|
321887
321388
|
const PlatformServicesLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
321888
321389
|
if (typeof Bun !== "undefined") {
|
|
321889
|
-
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunServices-
|
|
321390
|
+
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunServices-DsWG7VM1.cjs")));
|
|
321890
321391
|
return layer;
|
|
321891
321392
|
} else {
|
|
321892
321393
|
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeServices-BvdIc9Vh.cjs")));
|
|
@@ -322889,7 +322390,7 @@ const connectLoginCommand = make$34("login", {
|
|
|
322889
322390
|
})).json();
|
|
322890
322391
|
},
|
|
322891
322392
|
openBrowser: async (url) => {
|
|
322892
|
-
const open = (await Promise.resolve().then(() => require("./open-
|
|
322393
|
+
const open = (await Promise.resolve().then(() => require("./open-CghsF25U.cjs"))).default;
|
|
322893
322394
|
await open(url);
|
|
322894
322395
|
return true;
|
|
322895
322396
|
},
|