@velanir/openclaw-browserbase 0.1.0 → 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 +15 -5
- package/dist/index.js +79 -70
- package/openclaw.plugin.json +64 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,9 +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
|
-
|
|
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).
|
|
33
43
|
- One session per coworker context at a time (Browserbase forbids concurrent
|
|
34
44
|
sessions on one context), with a short cooldown after release.
|
|
35
45
|
|
|
@@ -90,8 +100,8 @@ Notes:
|
|
|
90
100
|
See `openclaw.plugin.json` for the full schema. Defaults implement the locked
|
|
91
101
|
v1 policy: timeout 3600 s, viewport 1280×900, captcha solving on, recording on,
|
|
92
102
|
`ignoreCertificateErrors: false`, per-coworker context with `persist: true`,
|
|
93
|
-
cooldown
|
|
94
|
-
(`proxies: []` passes through to session creation when set).
|
|
103
|
+
linger 60 s, cooldown 3 s, idle disconnect 15 min, reaper every 5 min, no
|
|
104
|
+
proxies (`proxies: []` passes through to session creation when set).
|
|
95
105
|
|
|
96
106
|
## Agent tools
|
|
97
107
|
|
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 } : {},
|
|
@@ -250,8 +256,9 @@ var DEFAULT_TIMEOUT_SECONDS = 3600;
|
|
|
250
256
|
var MIN_TIMEOUT_SECONDS = 60;
|
|
251
257
|
var MAX_TIMEOUT_SECONDS = 21600;
|
|
252
258
|
var DEFAULT_VIEWPORT = { width: 1280, height: 900 };
|
|
253
|
-
var DEFAULT_CONTEXT_COOLDOWN_SECONDS =
|
|
259
|
+
var DEFAULT_CONTEXT_COOLDOWN_SECONDS = 3;
|
|
254
260
|
var DEFAULT_CONTEXT_WAIT_MS = 1e4;
|
|
261
|
+
var DEFAULT_LINGER_SECONDS = 60;
|
|
255
262
|
var DEFAULT_IDLE_DISCONNECT_MINUTES = 15;
|
|
256
263
|
var DEFAULT_REAPER_INTERVAL_MINUTES = 5;
|
|
257
264
|
var DEFAULT_API_BASE_URL = "https://api.browserbase.com";
|
|
@@ -368,6 +375,7 @@ function normalizeConfig(raw) {
|
|
|
368
375
|
waitMs: readNumber(context.waitMs, DEFAULT_CONTEXT_WAIT_MS, 0, 6e4)
|
|
369
376
|
},
|
|
370
377
|
proxies: readProxies(root.proxies),
|
|
378
|
+
lingerSeconds: readNumber(root.lingerSeconds, DEFAULT_LINGER_SECONDS, 0, 600),
|
|
371
379
|
idleDisconnectMinutes: readNumber(root.idleDisconnectMinutes, DEFAULT_IDLE_DISCONNECT_MINUTES, 0, 1440),
|
|
372
380
|
reaperIntervalMinutes: readNumber(root.reaperIntervalMinutes, DEFAULT_REAPER_INTERVAL_MINUTES, 0, 1440),
|
|
373
381
|
apiBaseUrl: readString(root.apiBaseUrl) ?? DEFAULT_API_BASE_URL,
|
|
@@ -522,6 +530,9 @@ var BrowserbaseBroker = class {
|
|
|
522
530
|
idleSweepIntervalMs;
|
|
523
531
|
server = null;
|
|
524
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.
|
|
525
536
|
adoptable = null;
|
|
526
537
|
cooldownUntil = 0;
|
|
527
538
|
timers = [];
|
|
@@ -581,8 +592,7 @@ var BrowserbaseBroker = class {
|
|
|
581
592
|
clearTimeout(this.adoptable.timer);
|
|
582
593
|
const held = this.adoptable;
|
|
583
594
|
this.adoptable = null;
|
|
584
|
-
held.
|
|
585
|
-
await this.releaseQuietly(held.session.sessionId, "shutdown-unattached");
|
|
595
|
+
await this.releaseQuietly(held.session.sessionId, "shutdown-resumable");
|
|
586
596
|
}
|
|
587
597
|
const active = this.active;
|
|
588
598
|
if (active) {
|
|
@@ -734,9 +744,9 @@ var BrowserbaseBroker = class {
|
|
|
734
744
|
socket.destroy();
|
|
735
745
|
return;
|
|
736
746
|
}
|
|
737
|
-
let
|
|
747
|
+
let minted;
|
|
738
748
|
try {
|
|
739
|
-
|
|
749
|
+
minted = await this.acquireSession(leaseId);
|
|
740
750
|
} catch (err) {
|
|
741
751
|
const message = err instanceof Error ? err.message : String(err);
|
|
742
752
|
this.logger.warn?.(`session acquire failed: ${message}`);
|
|
@@ -748,36 +758,33 @@ var BrowserbaseBroker = class {
|
|
|
748
758
|
}
|
|
749
759
|
return;
|
|
750
760
|
}
|
|
751
|
-
const minted = held.session;
|
|
752
761
|
if (liveness.gone) {
|
|
753
762
|
liveness.detach();
|
|
754
763
|
socket.destroy();
|
|
755
|
-
this.holdForAdoption(minted
|
|
764
|
+
this.holdForAdoption(minted);
|
|
756
765
|
return;
|
|
757
766
|
}
|
|
758
|
-
let upstream
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
} else {
|
|
772
|
-
destroyWithHttpError(socket, 502, "Bad Gateway", `Browserbase connect failed: ${message}`);
|
|
773
|
-
}
|
|
774
|
-
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}`);
|
|
775
780
|
}
|
|
781
|
+
return;
|
|
776
782
|
}
|
|
777
783
|
if (liveness.gone) {
|
|
778
784
|
liveness.detach();
|
|
779
785
|
socket.destroy();
|
|
780
|
-
|
|
786
|
+
upstream.terminate();
|
|
787
|
+
this.holdForAdoption(minted);
|
|
781
788
|
return;
|
|
782
789
|
}
|
|
783
790
|
liveness.detach();
|
|
@@ -804,6 +811,7 @@ var BrowserbaseBroker = class {
|
|
|
804
811
|
sessionId: minted.sessionId,
|
|
805
812
|
leaseId: minted.leaseId,
|
|
806
813
|
contextId: minted.contextId,
|
|
814
|
+
connectUrl: minted.connectUrl,
|
|
807
815
|
...minted.expiresAt ? { expiresAt: minted.expiresAt } : {},
|
|
808
816
|
startedAt: Date.now(),
|
|
809
817
|
pipe
|
|
@@ -813,10 +821,27 @@ var BrowserbaseBroker = class {
|
|
|
813
821
|
);
|
|
814
822
|
}
|
|
815
823
|
onPipeClosed(sessionId, reason) {
|
|
816
|
-
|
|
824
|
+
const active = this.active;
|
|
825
|
+
if (active?.sessionId !== sessionId) {
|
|
817
826
|
return;
|
|
818
827
|
}
|
|
819
828
|
this.active = null;
|
|
829
|
+
const downstreamGone = reason === "downstream-closed" || reason === "downstream-error";
|
|
830
|
+
if (downstreamGone && !this.stopping && this.config.lingerSeconds > 0) {
|
|
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;
|
|
844
|
+
}
|
|
820
845
|
this.cooldownUntil = Date.now() + this.config.context.cooldownSeconds * 1e3;
|
|
821
846
|
this.logger.info?.(`session ${sessionId} disconnected (${reason}); releasing`);
|
|
822
847
|
void this.releaseQuietly(sessionId, reason).finally(() => {
|
|
@@ -828,7 +853,7 @@ var BrowserbaseBroker = class {
|
|
|
828
853
|
const adopted = this.takeAdoptable();
|
|
829
854
|
if (adopted) {
|
|
830
855
|
this.logger.info?.(
|
|
831
|
-
`
|
|
856
|
+
`resuming held session ${adopted.sessionId} (minted for lease ${adopted.leaseId}, requested by lease ${leaseId})`
|
|
832
857
|
);
|
|
833
858
|
return adopted;
|
|
834
859
|
}
|
|
@@ -845,7 +870,7 @@ var BrowserbaseBroker = class {
|
|
|
845
870
|
if (cooldownRemaining > 0) {
|
|
846
871
|
await sleep(cooldownRemaining);
|
|
847
872
|
}
|
|
848
|
-
return
|
|
873
|
+
return this.mintSession(leaseId);
|
|
849
874
|
}
|
|
850
875
|
async mintSession(leaseId) {
|
|
851
876
|
const contextId = await this.ensureContext();
|
|
@@ -907,37 +932,21 @@ var BrowserbaseBroker = class {
|
|
|
907
932
|
});
|
|
908
933
|
});
|
|
909
934
|
}
|
|
910
|
-
holdForAdoption(session,
|
|
935
|
+
holdForAdoption(session, opts = {}) {
|
|
936
|
+
const reason = opts.reason ?? "adoption";
|
|
937
|
+
const windowMs = opts.windowMs ?? ADOPTION_WINDOW_MS;
|
|
911
938
|
this.logger.info?.(
|
|
912
|
-
`holding session ${session.sessionId}
|
|
939
|
+
`holding session ${session.sessionId} (${reason}, ${Math.round(windowMs / 1e3)}s window) for re-dial`
|
|
913
940
|
);
|
|
914
941
|
const timer = setTimeout(() => {
|
|
915
942
|
if (this.adoptable?.session.sessionId === session.sessionId) {
|
|
916
|
-
const held = this.adoptable;
|
|
917
943
|
this.adoptable = null;
|
|
918
|
-
|
|
919
|
-
void this.releaseQuietly(session.sessionId,
|
|
944
|
+
this.cooldownUntil = Date.now() + this.config.context.cooldownSeconds * 1e3;
|
|
945
|
+
void this.releaseQuietly(session.sessionId, `${reason}-expired`);
|
|
920
946
|
}
|
|
921
|
-
},
|
|
947
|
+
}, windowMs);
|
|
922
948
|
timer.unref();
|
|
923
|
-
|
|
924
|
-
if (upstream) {
|
|
925
|
-
onUpstreamLost = () => {
|
|
926
|
-
if (this.adoptable?.session.sessionId === session.sessionId && this.adoptable.upstream === upstream) {
|
|
927
|
-
clearTimeout(this.adoptable.timer);
|
|
928
|
-
this.adoptable = null;
|
|
929
|
-
void this.releaseQuietly(session.sessionId, "adoption-upstream-lost");
|
|
930
|
-
}
|
|
931
|
-
};
|
|
932
|
-
upstream.once("close", onUpstreamLost);
|
|
933
|
-
upstream.once("error", onUpstreamLost);
|
|
934
|
-
}
|
|
935
|
-
this.adoptable = {
|
|
936
|
-
session,
|
|
937
|
-
...upstream ? { upstream } : {},
|
|
938
|
-
...onUpstreamLost ? { onUpstreamLost } : {},
|
|
939
|
-
timer
|
|
940
|
-
};
|
|
949
|
+
this.adoptable = { session, heldReason: reason, timer };
|
|
941
950
|
}
|
|
942
951
|
takeAdoptable() {
|
|
943
952
|
const held = this.adoptable;
|
|
@@ -945,15 +954,8 @@ var BrowserbaseBroker = class {
|
|
|
945
954
|
return null;
|
|
946
955
|
}
|
|
947
956
|
clearTimeout(held.timer);
|
|
948
|
-
if (held.upstream && held.onUpstreamLost) {
|
|
949
|
-
held.upstream.off("close", held.onUpstreamLost);
|
|
950
|
-
held.upstream.off("error", held.onUpstreamLost);
|
|
951
|
-
}
|
|
952
957
|
this.adoptable = null;
|
|
953
|
-
return
|
|
954
|
-
session: held.session,
|
|
955
|
-
...held.upstream ? { upstream: held.upstream } : {}
|
|
956
|
-
};
|
|
958
|
+
return held.session;
|
|
957
959
|
}
|
|
958
960
|
// ------------------------------------------------------------- cleanup --
|
|
959
961
|
async releaseQuietly(sessionId, reason) {
|
|
@@ -1019,24 +1021,31 @@ var BrowserbaseBroker = class {
|
|
|
1019
1021
|
startedAt: new Date(active.startedAt).toISOString(),
|
|
1020
1022
|
...active.expiresAt ? { expiresAt: active.expiresAt } : {},
|
|
1021
1023
|
idleMs: Date.now() - active.pipe.lastActivityAt
|
|
1022
|
-
} : null
|
|
1024
|
+
} : null,
|
|
1025
|
+
held: this.adoptable ? { sessionId: this.adoptable.session.sessionId, reason: this.adoptable.heldReason } : null
|
|
1023
1026
|
};
|
|
1024
1027
|
}
|
|
1025
1028
|
async getLiveViewUrls() {
|
|
1026
|
-
const
|
|
1027
|
-
if (!
|
|
1029
|
+
const sessionId = this.active && !this.active.pipe.closed ? this.active.sessionId : this.adoptable ? this.adoptable.session.sessionId : null;
|
|
1030
|
+
if (!sessionId) {
|
|
1028
1031
|
return null;
|
|
1029
1032
|
}
|
|
1030
|
-
return this.client.getDebugUrls(
|
|
1033
|
+
return this.client.getDebugUrls(sessionId);
|
|
1031
1034
|
}
|
|
1032
1035
|
/** Returns true when there was an active session to release. */
|
|
1033
1036
|
releaseActiveSession() {
|
|
1034
1037
|
const active = this.active;
|
|
1035
|
-
if (
|
|
1036
|
-
|
|
1038
|
+
if (active && !active.pipe.closed) {
|
|
1039
|
+
active.pipe.close("manual-release");
|
|
1040
|
+
return true;
|
|
1037
1041
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1042
|
+
const held = this.takeAdoptable();
|
|
1043
|
+
if (held) {
|
|
1044
|
+
this.cooldownUntil = Date.now() + this.config.context.cooldownSeconds * 1e3;
|
|
1045
|
+
void this.releaseQuietly(held.sessionId, "manual-release");
|
|
1046
|
+
return true;
|
|
1047
|
+
}
|
|
1048
|
+
return false;
|
|
1040
1049
|
}
|
|
1041
1050
|
};
|
|
1042
1051
|
|
|
@@ -1063,7 +1072,7 @@ function createBrowserbaseTools(getBroker) {
|
|
|
1063
1072
|
const status = broker.getStatus();
|
|
1064
1073
|
if (!status.active) {
|
|
1065
1074
|
const lines2 = [
|
|
1066
|
-
NO_SESSION,
|
|
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,
|
|
1067
1076
|
`Region: ${status.region}. Recording: ${status.recordSession ? "on" : "off"}.`,
|
|
1068
1077
|
status.contextId ? `Browser context: ${status.contextId}` : "Browser context: created on first use."
|
|
1069
1078
|
];
|
package/openclaw.plugin.json
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
"configSchema": {
|
|
9
9
|
"type": "object",
|
|
10
10
|
"additionalProperties": false,
|
|
11
|
-
"required": ["projectId", "apiKey", "listenPort"],
|
|
12
11
|
"properties": {
|
|
13
12
|
"projectId": {
|
|
14
13
|
"type": "string",
|
|
@@ -20,7 +19,12 @@
|
|
|
20
19
|
},
|
|
21
20
|
"region": {
|
|
22
21
|
"type": "string",
|
|
23
|
-
"enum": [
|
|
22
|
+
"enum": [
|
|
23
|
+
"us-west-2",
|
|
24
|
+
"us-east-1",
|
|
25
|
+
"eu-central-1",
|
|
26
|
+
"ap-southeast-1"
|
|
27
|
+
],
|
|
24
28
|
"default": "us-west-2"
|
|
25
29
|
},
|
|
26
30
|
"listenPort": {
|
|
@@ -38,7 +42,9 @@
|
|
|
38
42
|
},
|
|
39
43
|
"metadata": {
|
|
40
44
|
"type": "object",
|
|
41
|
-
"additionalProperties": {
|
|
45
|
+
"additionalProperties": {
|
|
46
|
+
"type": "string"
|
|
47
|
+
}
|
|
42
48
|
},
|
|
43
49
|
"defaults": {
|
|
44
50
|
"type": "object",
|
|
@@ -54,28 +60,63 @@
|
|
|
54
60
|
"type": "object",
|
|
55
61
|
"additionalProperties": false,
|
|
56
62
|
"properties": {
|
|
57
|
-
"width": {
|
|
58
|
-
|
|
63
|
+
"width": {
|
|
64
|
+
"type": "number",
|
|
65
|
+
"minimum": 320,
|
|
66
|
+
"maximum": 3840
|
|
67
|
+
},
|
|
68
|
+
"height": {
|
|
69
|
+
"type": "number",
|
|
70
|
+
"minimum": 320,
|
|
71
|
+
"maximum": 2160
|
|
72
|
+
}
|
|
59
73
|
}
|
|
60
74
|
},
|
|
61
|
-
"solveCaptchas": {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
"solveCaptchas": {
|
|
76
|
+
"type": "boolean",
|
|
77
|
+
"default": true
|
|
78
|
+
},
|
|
79
|
+
"recordSession": {
|
|
80
|
+
"type": "boolean",
|
|
81
|
+
"default": true
|
|
82
|
+
},
|
|
83
|
+
"logSession": {
|
|
84
|
+
"type": "boolean",
|
|
85
|
+
"default": true
|
|
86
|
+
},
|
|
87
|
+
"ignoreCertificateErrors": {
|
|
88
|
+
"type": "boolean",
|
|
89
|
+
"default": false
|
|
90
|
+
}
|
|
65
91
|
}
|
|
66
92
|
},
|
|
67
93
|
"context": {
|
|
68
94
|
"type": "object",
|
|
69
95
|
"additionalProperties": false,
|
|
70
96
|
"properties": {
|
|
71
|
-
"persist": {
|
|
72
|
-
|
|
73
|
-
|
|
97
|
+
"persist": {
|
|
98
|
+
"type": "boolean",
|
|
99
|
+
"default": true
|
|
100
|
+
},
|
|
101
|
+
"cooldownSeconds": {
|
|
102
|
+
"type": "number",
|
|
103
|
+
"default": 3,
|
|
104
|
+
"minimum": 0,
|
|
105
|
+
"maximum": 120
|
|
106
|
+
},
|
|
107
|
+
"waitMs": {
|
|
108
|
+
"type": "number",
|
|
109
|
+
"default": 10000,
|
|
110
|
+
"minimum": 0,
|
|
111
|
+
"maximum": 60000
|
|
112
|
+
}
|
|
74
113
|
}
|
|
75
114
|
},
|
|
76
115
|
"proxies": {
|
|
77
116
|
"type": "array",
|
|
78
|
-
"items": {
|
|
117
|
+
"items": {
|
|
118
|
+
"type": "object"
|
|
119
|
+
},
|
|
79
120
|
"default": []
|
|
80
121
|
},
|
|
81
122
|
"idleDisconnectMinutes": {
|
|
@@ -87,6 +128,12 @@
|
|
|
87
128
|
"type": "number",
|
|
88
129
|
"default": 5,
|
|
89
130
|
"minimum": 0
|
|
131
|
+
},
|
|
132
|
+
"lingerSeconds": {
|
|
133
|
+
"type": "number",
|
|
134
|
+
"default": 60,
|
|
135
|
+
"minimum": 0,
|
|
136
|
+
"maximum": 600
|
|
90
137
|
}
|
|
91
138
|
}
|
|
92
139
|
},
|
|
@@ -120,6 +167,10 @@
|
|
|
120
167
|
"idleDisconnectMinutes": {
|
|
121
168
|
"label": "Idle Disconnect (min)",
|
|
122
169
|
"help": "Close and release the Browserbase session after this many minutes without CDP traffic. 0 disables. Login state survives via the persistent context."
|
|
170
|
+
},
|
|
171
|
+
"lingerSeconds": {
|
|
172
|
+
"label": "Linger (s)",
|
|
173
|
+
"help": "Keep the session and its connection alive after a disconnect so the next browser action re-attaches instantly instead of minting a new session. 0 releases immediately."
|
|
123
174
|
}
|
|
124
175
|
}
|
|
125
176
|
}
|
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",
|