@velanir/openclaw-browserbase 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -8
- package/dist/index.js +70 -163
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,14 +27,19 @@ browser tool ──cdpUrl http://127.0.0.1:<port>/?token=…──> broker (this
|
|
|
27
27
|
- Every session is created with the coworker's persistent context
|
|
28
28
|
(`persist: true`), the configured timeout/viewport/captcha/recording policy,
|
|
29
29
|
and `userMetadata` tagging for observability and reaping.
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
- Sessions are created with **`keepAlive: true`**, so on downstream
|
|
31
|
+
disconnect the broker **lingers**: it closes the CDP connection but keeps
|
|
32
|
+
the Browserbase session alive (default 60s) and **re-dials a fresh CDP
|
|
33
|
+
connection** to it on the next browser action — no new mint, no cooldown,
|
|
34
|
+
and tabs/login state intact. OpenClaw's browser tool opens short probe
|
|
35
|
+
connections and reconnects between actions; without linger every probe
|
|
36
|
+
minted (and released) a billable session and the post-release cooldown
|
|
37
|
+
starved the real connection. Because each client gets a brand-new CDP
|
|
38
|
+
connection (fresh id space), a late reply to a previous client's command
|
|
39
|
+
can never reach the next one. Explicit release, idle disconnect, linger
|
|
40
|
+
expiry, and gateway shutdown all release the keepAlive session
|
|
41
|
+
deterministically (keepAlive sessions do not self-terminate, so the reaper
|
|
42
|
+
also backstops crash leftovers).
|
|
38
43
|
- One session per coworker context at a time (Browserbase forbids concurrent
|
|
39
44
|
sessions on one context), with a short cooldown after release.
|
|
40
45
|
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,13 @@ function buildSessionPayload(config, mint) {
|
|
|
25
25
|
projectId: config.projectId,
|
|
26
26
|
region: config.region,
|
|
27
27
|
timeout: Math.round(config.defaults.timeoutSeconds),
|
|
28
|
-
keepAlive
|
|
28
|
+
// keepAlive lets the session survive a CDP disconnect so the broker can
|
|
29
|
+
// close the old connection and re-dial a FRESH one for the next client
|
|
30
|
+
// (linger) — no mint, and a fresh CDP id space so stale replies are
|
|
31
|
+
// impossible by construction. The broker is responsible for explicit
|
|
32
|
+
// release (linger expiry, idle, shutdown, reaper), since keepAlive
|
|
33
|
+
// sessions do not self-terminate on disconnect.
|
|
34
|
+
keepAlive: true,
|
|
29
35
|
// Omit `proxies` entirely when unset so Browserbase applies plain direct
|
|
30
36
|
// egress rather than an empty routing array.
|
|
31
37
|
...config.proxies.length > 0 ? { proxies: config.proxies } : {},
|
|
@@ -121,18 +127,8 @@ var BrowserbaseClient = class {
|
|
|
121
127
|
|
|
122
128
|
// src/cdp-pipe.ts
|
|
123
129
|
var INJECTED_ID_BASE = 19e8;
|
|
124
|
-
var INJECTED_ID_PIPE_STRIDE = 1e6;
|
|
125
|
-
var INJECTED_ID_PIPE_SLOTS = 200;
|
|
126
130
|
var INJECTED_RESPONSE_MAX_BYTES = 4096;
|
|
127
131
|
var DEFAULT_INJECTION_TIMEOUT_MS = 2e3;
|
|
128
|
-
var STALE_REPLY_TTL_MS = 15e3;
|
|
129
|
-
var MAX_TRACKED_CLIENT_IDS = 256;
|
|
130
|
-
var FRAME_HEAD_ID_RE = /"id"\s*:\s*(\d+)/;
|
|
131
|
-
function frameHeadId(data) {
|
|
132
|
-
const head = Buffer.isBuffer(data) ? data.subarray(0, 64).toString("utf8") : Array.isArray(data) ? Buffer.concat(data).subarray(0, 64).toString("utf8") : Buffer.from(data).subarray(0, 64).toString("utf8");
|
|
133
|
-
const match = FRAME_HEAD_ID_RE.exec(head);
|
|
134
|
-
return match ? Number(match[1]) : null;
|
|
135
|
-
}
|
|
136
132
|
function rawDataByteLength(data) {
|
|
137
133
|
if (Buffer.isBuffer(data)) {
|
|
138
134
|
return data.byteLength;
|
|
@@ -151,90 +147,38 @@ function rawDataToString(data) {
|
|
|
151
147
|
}
|
|
152
148
|
return Buffer.from(data).toString("utf8");
|
|
153
149
|
}
|
|
154
|
-
var CdpPipe = class
|
|
150
|
+
var CdpPipe = class {
|
|
155
151
|
constructor(downstream, upstream, opts) {
|
|
156
152
|
this.downstream = downstream;
|
|
157
153
|
this.upstream = upstream;
|
|
158
154
|
this.opts = opts;
|
|
159
|
-
const ttl = Date.now() + STALE_REPLY_TTL_MS;
|
|
160
|
-
for (const id of opts.suppressStaleIds ?? []) {
|
|
161
|
-
this.suppressStale.set(id, ttl);
|
|
162
|
-
}
|
|
163
155
|
downstream.on("message", (data, isBinary) => {
|
|
164
156
|
this.lastActivityAt = Date.now();
|
|
165
|
-
if (!isBinary && this.clientPendingIds.size < MAX_TRACKED_CLIENT_IDS) {
|
|
166
|
-
const id = frameHeadId(data);
|
|
167
|
-
if (id !== null && id < INJECTED_ID_BASE) {
|
|
168
|
-
this.clientPendingIds.add(id);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
157
|
if (upstream.readyState === upstream.OPEN) {
|
|
172
158
|
upstream.send(data, { binary: isBinary });
|
|
173
159
|
}
|
|
174
160
|
});
|
|
175
|
-
upstream.on("message",
|
|
161
|
+
upstream.on("message", (data, isBinary) => {
|
|
162
|
+
this.lastActivityAt = Date.now();
|
|
163
|
+
if (!isBinary && this.maybeResolveInjected(data)) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (downstream.readyState === downstream.OPEN) {
|
|
167
|
+
downstream.send(data, { binary: isBinary });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
176
170
|
downstream.on("close", () => this.close("downstream-closed"));
|
|
177
171
|
downstream.on("error", () => this.close("downstream-error"));
|
|
178
|
-
upstream.on("close", this.
|
|
179
|
-
upstream.on("error", this.
|
|
172
|
+
upstream.on("close", () => this.close("upstream-closed"));
|
|
173
|
+
upstream.on("error", () => this.close("upstream-error"));
|
|
180
174
|
}
|
|
181
|
-
|
|
182
|
-
nextInjectedId = INJECTED_ID_BASE + _CdpPipe.pipeSeq++ % INJECTED_ID_PIPE_SLOTS * INJECTED_ID_PIPE_STRIDE;
|
|
175
|
+
nextInjectedId = INJECTED_ID_BASE;
|
|
183
176
|
pending = /* @__PURE__ */ new Map();
|
|
184
177
|
closeReason = null;
|
|
185
|
-
upstreamClaimed = false;
|
|
186
|
-
/** Downstream command ids sent upstream with no reply seen yet. */
|
|
187
|
-
clientPendingIds = /* @__PURE__ */ new Set();
|
|
188
|
-
/** Stale ids from the previous client on this upstream → suppression deadline. */
|
|
189
|
-
suppressStale = /* @__PURE__ */ new Map();
|
|
190
178
|
lastActivityAt = Date.now();
|
|
191
|
-
// Named so takeUpstream() can detach exactly the pipe's listeners from a
|
|
192
|
-
// claimed (lingering) upstream without disturbing the hold's own listeners.
|
|
193
|
-
onUpstreamMessage = (data, isBinary) => {
|
|
194
|
-
this.lastActivityAt = Date.now();
|
|
195
|
-
if (!isBinary) {
|
|
196
|
-
const id = frameHeadId(data);
|
|
197
|
-
if (id !== null) {
|
|
198
|
-
this.clientPendingIds.delete(id);
|
|
199
|
-
const deadline = this.suppressStale.get(id);
|
|
200
|
-
if (deadline !== void 0) {
|
|
201
|
-
this.suppressStale.delete(id);
|
|
202
|
-
if (Date.now() <= deadline) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (this.maybeResolveInjected(data)) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (this.downstream.readyState === this.downstream.OPEN) {
|
|
212
|
-
this.downstream.send(data, { binary: isBinary });
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
onUpstreamClose = () => this.close("upstream-closed");
|
|
216
|
-
onUpstreamError = () => this.close("upstream-error");
|
|
217
179
|
get closed() {
|
|
218
180
|
return this.closeReason !== null;
|
|
219
181
|
}
|
|
220
|
-
/**
|
|
221
|
-
* Claim the upstream socket out of the pipe while it is still OPEN —
|
|
222
|
-
* callable from inside the onClose handler so the broker can keep the
|
|
223
|
-
* Browserbase session alive (linger) instead of releasing on downstream
|
|
224
|
-
* disconnect. Returns null when the upstream is already gone or claimed.
|
|
225
|
-
* staleClientIds are the old client's still-unanswered command ids; the
|
|
226
|
-
* next pipe on this upstream must suppress their late replies.
|
|
227
|
-
*/
|
|
228
|
-
takeUpstream() {
|
|
229
|
-
if (this.upstreamClaimed || this.upstream.readyState !== this.upstream.OPEN) {
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
this.upstreamClaimed = true;
|
|
233
|
-
this.upstream.off("message", this.onUpstreamMessage);
|
|
234
|
-
this.upstream.off("close", this.onUpstreamClose);
|
|
235
|
-
this.upstream.off("error", this.onUpstreamError);
|
|
236
|
-
return { socket: this.upstream, staleClientIds: [...this.clientPendingIds] };
|
|
237
|
-
}
|
|
238
182
|
/** Send a broker-owned CDP command on the piped connection and await its reply. */
|
|
239
183
|
inject(method, params) {
|
|
240
184
|
if (this.closeReason !== null || this.upstream.readyState !== this.upstream.OPEN) {
|
|
@@ -290,9 +234,7 @@ var CdpPipe = class _CdpPipe {
|
|
|
290
234
|
entry.reject(new Error(`CDP pipe closed: ${reason}`));
|
|
291
235
|
}
|
|
292
236
|
this.pending.clear();
|
|
293
|
-
this.
|
|
294
|
-
const sockets = this.upstreamClaimed ? [this.downstream] : [this.downstream, this.upstream];
|
|
295
|
-
for (const socket of sockets) {
|
|
237
|
+
for (const socket of [this.downstream, this.upstream]) {
|
|
296
238
|
if (socket.readyState === socket.OPEN || socket.readyState === socket.CLOSING) {
|
|
297
239
|
try {
|
|
298
240
|
socket.close(1e3);
|
|
@@ -303,6 +245,7 @@ var CdpPipe = class _CdpPipe {
|
|
|
303
245
|
socket.terminate();
|
|
304
246
|
}
|
|
305
247
|
}
|
|
248
|
+
this.opts.onClose(reason);
|
|
306
249
|
}
|
|
307
250
|
};
|
|
308
251
|
|
|
@@ -587,6 +530,9 @@ var BrowserbaseBroker = class {
|
|
|
587
530
|
idleSweepIntervalMs;
|
|
588
531
|
server = null;
|
|
589
532
|
active = null;
|
|
533
|
+
// A live keepAlive session held resumable (by connectUrl) for a window —
|
|
534
|
+
// re-dialed on the next connect (linger) or retry (adoption), released on
|
|
535
|
+
// expiry.
|
|
590
536
|
adoptable = null;
|
|
591
537
|
cooldownUntil = 0;
|
|
592
538
|
timers = [];
|
|
@@ -646,8 +592,7 @@ var BrowserbaseBroker = class {
|
|
|
646
592
|
clearTimeout(this.adoptable.timer);
|
|
647
593
|
const held = this.adoptable;
|
|
648
594
|
this.adoptable = null;
|
|
649
|
-
held.
|
|
650
|
-
await this.releaseQuietly(held.session.sessionId, "shutdown-unattached");
|
|
595
|
+
await this.releaseQuietly(held.session.sessionId, "shutdown-resumable");
|
|
651
596
|
}
|
|
652
597
|
const active = this.active;
|
|
653
598
|
if (active) {
|
|
@@ -799,9 +744,9 @@ var BrowserbaseBroker = class {
|
|
|
799
744
|
socket.destroy();
|
|
800
745
|
return;
|
|
801
746
|
}
|
|
802
|
-
let
|
|
747
|
+
let minted;
|
|
803
748
|
try {
|
|
804
|
-
|
|
749
|
+
minted = await this.acquireSession(leaseId);
|
|
805
750
|
} catch (err) {
|
|
806
751
|
const message = err instanceof Error ? err.message : String(err);
|
|
807
752
|
this.logger.warn?.(`session acquire failed: ${message}`);
|
|
@@ -813,45 +758,41 @@ var BrowserbaseBroker = class {
|
|
|
813
758
|
}
|
|
814
759
|
return;
|
|
815
760
|
}
|
|
816
|
-
const minted = held.session;
|
|
817
761
|
if (liveness.gone) {
|
|
818
762
|
liveness.detach();
|
|
819
763
|
socket.destroy();
|
|
820
|
-
this.holdForAdoption(minted
|
|
764
|
+
this.holdForAdoption(minted);
|
|
821
765
|
return;
|
|
822
766
|
}
|
|
823
|
-
let upstream
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
} else {
|
|
837
|
-
destroyWithHttpError(socket, 502, "Bad Gateway", `Browserbase connect failed: ${message}`);
|
|
838
|
-
}
|
|
839
|
-
return;
|
|
767
|
+
let upstream;
|
|
768
|
+
try {
|
|
769
|
+
upstream = await this.dialUpstream(minted.connectUrl);
|
|
770
|
+
} catch (err) {
|
|
771
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
772
|
+
this.logger.warn?.(`upstream dial failed for session ${minted.sessionId}: ${message}`);
|
|
773
|
+
await this.releaseQuietly(minted.sessionId, "upstream-dial-failed");
|
|
774
|
+
this.cooldownUntil = Date.now() + this.config.context.cooldownSeconds * 1e3;
|
|
775
|
+
liveness.detach();
|
|
776
|
+
if (liveness.gone) {
|
|
777
|
+
socket.destroy();
|
|
778
|
+
} else {
|
|
779
|
+
destroyWithHttpError(socket, 502, "Bad Gateway", `Browserbase connect failed: ${message}`);
|
|
840
780
|
}
|
|
781
|
+
return;
|
|
841
782
|
}
|
|
842
783
|
if (liveness.gone) {
|
|
843
784
|
liveness.detach();
|
|
844
785
|
socket.destroy();
|
|
845
|
-
|
|
786
|
+
upstream.terminate();
|
|
787
|
+
this.holdForAdoption(minted);
|
|
846
788
|
return;
|
|
847
789
|
}
|
|
848
790
|
liveness.detach();
|
|
849
|
-
const staleClientIds = held.staleClientIds ?? [];
|
|
850
791
|
this.wss.handleUpgrade(req, socket, head, (downstream) => {
|
|
851
|
-
this.attachPipe(downstream, upstream, minted
|
|
792
|
+
this.attachPipe(downstream, upstream, minted);
|
|
852
793
|
});
|
|
853
794
|
}
|
|
854
|
-
attachPipe(downstream, upstream, minted
|
|
795
|
+
attachPipe(downstream, upstream, minted) {
|
|
855
796
|
if (upstream.readyState !== upstream.OPEN || downstream.readyState !== downstream.OPEN) {
|
|
856
797
|
upstream.terminate();
|
|
857
798
|
if (downstream.readyState === downstream.OPEN) {
|
|
@@ -864,8 +805,7 @@ var BrowserbaseBroker = class {
|
|
|
864
805
|
return;
|
|
865
806
|
}
|
|
866
807
|
const pipe = new CdpPipe(downstream, upstream, {
|
|
867
|
-
onClose: (reason) => this.onPipeClosed(minted.sessionId, reason)
|
|
868
|
-
...staleClientIds.length ? { suppressStaleIds: staleClientIds } : {}
|
|
808
|
+
onClose: (reason) => this.onPipeClosed(minted.sessionId, reason)
|
|
869
809
|
});
|
|
870
810
|
this.active = {
|
|
871
811
|
sessionId: minted.sessionId,
|
|
@@ -888,21 +828,19 @@ var BrowserbaseBroker = class {
|
|
|
888
828
|
this.active = null;
|
|
889
829
|
const downstreamGone = reason === "downstream-closed" || reason === "downstream-error";
|
|
890
830
|
if (downstreamGone && !this.stopping && this.config.lingerSeconds > 0) {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
831
|
+
this.holdForAdoption(
|
|
832
|
+
{
|
|
833
|
+
sessionId: active.sessionId,
|
|
834
|
+
connectUrl: active.connectUrl,
|
|
835
|
+
contextId: active.contextId,
|
|
836
|
+
leaseId: active.leaseId,
|
|
837
|
+
...active.expiresAt ? { expiresAt: active.expiresAt } : {}
|
|
838
|
+
},
|
|
839
|
+
{ windowMs: this.config.lingerSeconds * 1e3, reason: "linger" }
|
|
840
|
+
);
|
|
841
|
+
this.logger.info?.(`session ${sessionId} disconnected (${reason}); lingering for re-dial (keepAlive)`);
|
|
842
|
+
this.onActiveReleased?.();
|
|
843
|
+
return;
|
|
906
844
|
}
|
|
907
845
|
this.cooldownUntil = Date.now() + this.config.context.cooldownSeconds * 1e3;
|
|
908
846
|
this.logger.info?.(`session ${sessionId} disconnected (${reason}); releasing`);
|
|
@@ -915,7 +853,7 @@ var BrowserbaseBroker = class {
|
|
|
915
853
|
const adopted = this.takeAdoptable();
|
|
916
854
|
if (adopted) {
|
|
917
855
|
this.logger.info?.(
|
|
918
|
-
`
|
|
856
|
+
`resuming held session ${adopted.sessionId} (minted for lease ${adopted.leaseId}, requested by lease ${leaseId})`
|
|
919
857
|
);
|
|
920
858
|
return adopted;
|
|
921
859
|
}
|
|
@@ -932,7 +870,7 @@ var BrowserbaseBroker = class {
|
|
|
932
870
|
if (cooldownRemaining > 0) {
|
|
933
871
|
await sleep(cooldownRemaining);
|
|
934
872
|
}
|
|
935
|
-
return
|
|
873
|
+
return this.mintSession(leaseId);
|
|
936
874
|
}
|
|
937
875
|
async mintSession(leaseId) {
|
|
938
876
|
const contextId = await this.ensureContext();
|
|
@@ -994,43 +932,21 @@ var BrowserbaseBroker = class {
|
|
|
994
932
|
});
|
|
995
933
|
});
|
|
996
934
|
}
|
|
997
|
-
holdForAdoption(session,
|
|
935
|
+
holdForAdoption(session, opts = {}) {
|
|
998
936
|
const reason = opts.reason ?? "adoption";
|
|
999
937
|
const windowMs = opts.windowMs ?? ADOPTION_WINDOW_MS;
|
|
1000
938
|
this.logger.info?.(
|
|
1001
|
-
`holding session ${session.sessionId} (${reason}, ${Math.round(windowMs / 1e3)}s window
|
|
939
|
+
`holding session ${session.sessionId} (${reason}, ${Math.round(windowMs / 1e3)}s window) for re-dial`
|
|
1002
940
|
);
|
|
1003
941
|
const timer = setTimeout(() => {
|
|
1004
942
|
if (this.adoptable?.session.sessionId === session.sessionId) {
|
|
1005
|
-
const held = this.adoptable;
|
|
1006
943
|
this.adoptable = null;
|
|
1007
|
-
held.upstream?.terminate();
|
|
1008
944
|
this.cooldownUntil = Date.now() + this.config.context.cooldownSeconds * 1e3;
|
|
1009
945
|
void this.releaseQuietly(session.sessionId, `${reason}-expired`);
|
|
1010
946
|
}
|
|
1011
947
|
}, windowMs);
|
|
1012
948
|
timer.unref();
|
|
1013
|
-
|
|
1014
|
-
if (upstream) {
|
|
1015
|
-
onUpstreamLost = () => {
|
|
1016
|
-
if (this.adoptable?.session.sessionId === session.sessionId && this.adoptable.upstream === upstream) {
|
|
1017
|
-
clearTimeout(this.adoptable.timer);
|
|
1018
|
-
this.adoptable = null;
|
|
1019
|
-
this.cooldownUntil = Date.now() + this.config.context.cooldownSeconds * 1e3;
|
|
1020
|
-
void this.releaseQuietly(session.sessionId, `${reason}-upstream-lost`);
|
|
1021
|
-
}
|
|
1022
|
-
};
|
|
1023
|
-
upstream.once("close", onUpstreamLost);
|
|
1024
|
-
upstream.once("error", onUpstreamLost);
|
|
1025
|
-
}
|
|
1026
|
-
this.adoptable = {
|
|
1027
|
-
session,
|
|
1028
|
-
heldReason: reason,
|
|
1029
|
-
...upstream ? { upstream } : {},
|
|
1030
|
-
...opts.staleClientIds?.length ? { staleClientIds: opts.staleClientIds } : {},
|
|
1031
|
-
...onUpstreamLost ? { onUpstreamLost } : {},
|
|
1032
|
-
timer
|
|
1033
|
-
};
|
|
949
|
+
this.adoptable = { session, heldReason: reason, timer };
|
|
1034
950
|
}
|
|
1035
951
|
takeAdoptable() {
|
|
1036
952
|
const held = this.adoptable;
|
|
@@ -1038,16 +954,8 @@ var BrowserbaseBroker = class {
|
|
|
1038
954
|
return null;
|
|
1039
955
|
}
|
|
1040
956
|
clearTimeout(held.timer);
|
|
1041
|
-
if (held.upstream && held.onUpstreamLost) {
|
|
1042
|
-
held.upstream.off("close", held.onUpstreamLost);
|
|
1043
|
-
held.upstream.off("error", held.onUpstreamLost);
|
|
1044
|
-
}
|
|
1045
957
|
this.adoptable = null;
|
|
1046
|
-
return
|
|
1047
|
-
session: held.session,
|
|
1048
|
-
...held.upstream ? { upstream: held.upstream } : {},
|
|
1049
|
-
...held.staleClientIds?.length ? { staleClientIds: held.staleClientIds } : {}
|
|
1050
|
-
};
|
|
958
|
+
return held.session;
|
|
1051
959
|
}
|
|
1052
960
|
// ------------------------------------------------------------- cleanup --
|
|
1053
961
|
async releaseQuietly(sessionId, reason) {
|
|
@@ -1118,7 +1026,7 @@ var BrowserbaseBroker = class {
|
|
|
1118
1026
|
};
|
|
1119
1027
|
}
|
|
1120
1028
|
async getLiveViewUrls() {
|
|
1121
|
-
const sessionId = this.active && !this.active.pipe.closed ? this.active.sessionId : this.adoptable
|
|
1029
|
+
const sessionId = this.active && !this.active.pipe.closed ? this.active.sessionId : this.adoptable ? this.adoptable.session.sessionId : null;
|
|
1122
1030
|
if (!sessionId) {
|
|
1123
1031
|
return null;
|
|
1124
1032
|
}
|
|
@@ -1133,9 +1041,8 @@ var BrowserbaseBroker = class {
|
|
|
1133
1041
|
}
|
|
1134
1042
|
const held = this.takeAdoptable();
|
|
1135
1043
|
if (held) {
|
|
1136
|
-
held.upstream?.terminate();
|
|
1137
1044
|
this.cooldownUntil = Date.now() + this.config.context.cooldownSeconds * 1e3;
|
|
1138
|
-
void this.releaseQuietly(held.
|
|
1045
|
+
void this.releaseQuietly(held.sessionId, "manual-release");
|
|
1139
1046
|
return true;
|
|
1140
1047
|
}
|
|
1141
1048
|
return false;
|
|
@@ -1165,7 +1072,7 @@ function createBrowserbaseTools(getBroker) {
|
|
|
1165
1072
|
const status = broker.getStatus();
|
|
1166
1073
|
if (!status.active) {
|
|
1167
1074
|
const lines2 = [
|
|
1168
|
-
status.held?.reason === "linger" ? `Browserbase session ${status.held.sessionId} is
|
|
1075
|
+
status.held?.reason === "linger" ? `Browserbase session ${status.held.sessionId} is paused (keepAlive) for resume \u2014 the next browser action reconnects to it with your tabs and login state intact.` : NO_SESSION,
|
|
1169
1076
|
`Region: ${status.region}. Recording: ${status.recordSession ? "on" : "off"}.`,
|
|
1170
1077
|
status.contextId ? `Browser context: ${status.contextId}` : "Browser context: created on first use."
|
|
1171
1078
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velanir/openclaw-browserbase",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "OpenClaw plugin that brokers Browserbase sessions behind a loopback CDP endpoint: explicit session creation with per-coworker persistent contexts, deterministic release, and stale-session reaping.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|