@pasko70/pibo 1.4.1 → 1.4.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.
Files changed (129) hide show
  1. package/README.md +183 -183
  2. package/context/codex-base-prompt.md +148 -148
  3. package/context/compute-worker.md +23 -23
  4. package/context/pibo-compaction-prompt.md +100 -100
  5. package/context/pibo-native-tooling.md +18 -18
  6. package/context/pibo-system-prompt.md +77 -77
  7. package/dist/apps/chat/agent-store.js +82 -82
  8. package/dist/apps/chat/data/project-service.js +166 -166
  9. package/dist/apps/chat/data/read-state-service.js +18 -18
  10. package/dist/apps/chat/data/timeline-query-service.js +8 -8
  11. package/dist/apps/chat/model-catalog.js +5 -1
  12. package/dist/apps/chat/static-assets.js +853 -853
  13. package/dist/apps/chat/workflow-persistence.js +255 -255
  14. package/dist/apps/chat-ui/assets/{dist-7YaJd19a.js → dist-B2BEpL7n.js} +1 -1
  15. package/dist/apps/chat-ui/assets/{dist-CiYeO8nN.js → dist-BAGS_xkV.js} +1 -1
  16. package/dist/apps/chat-ui/assets/{dist-BIvPnn_C.js → dist-BAXNalar.js} +1 -1
  17. package/dist/apps/chat-ui/assets/{dist-CjSI6y5z.js → dist-BwUvs6Ph.js} +1 -1
  18. package/dist/apps/chat-ui/assets/{dist-Ubstha8t.js → dist-C0zsJ8II.js} +1 -1
  19. package/dist/apps/chat-ui/assets/{dist-KXCMNKIL.js → dist-C3PnEkhb.js} +1 -1
  20. package/dist/apps/chat-ui/assets/{dist-CVQU42Fn.js → dist-CYPL-B2Z.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{dist-BbNE72h8.js → dist-CiDSXgtg.js} +1 -1
  22. package/dist/apps/chat-ui/assets/{dist-D7TCkoFT.js → dist-DnACFKyO.js} +1 -1
  23. package/dist/apps/chat-ui/assets/{dist-ChSZNqKE.js → dist-ZB1-ui2y.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-8Noo5eCN.js → dist-vKlxFkTa.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{index-CmxtUVG1.js → index-0x7tuTNX.js} +3 -3
  26. package/dist/apps/chat-ui/assets/{index-C25VYnyb.css → index-B-qaya1G.css} +1 -1
  27. package/dist/apps/chat-ui/index.html +18 -18
  28. package/dist/apps/chat-ui/manifest.webmanifest +25 -25
  29. package/dist/apps/chat-ui/sw.js +41 -41
  30. package/dist/apps/chat-vscode-web/index.html +12 -12
  31. package/dist/apps/cli-ui/cliSessionsCommand.js +23 -23
  32. package/dist/apps/context-files-ui/index.html +11 -11
  33. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  34. package/dist/apps/vscode-artifacts/{pibo-vscode-ext-1.4.1.vsix → pibo-vscode-ext-1.4.2.vsix} +0 -0
  35. package/dist/bin/pibo.js +0 -0
  36. package/dist/bin/rg.js +0 -0
  37. package/dist/cli.js +35 -35
  38. package/dist/compute/cli.js +54 -54
  39. package/dist/core/runtime.js +2 -0
  40. package/dist/core/session-router.js +1 -0
  41. package/dist/cron/cli.js +15 -15
  42. package/dist/cron/store.js +49 -49
  43. package/dist/data/cli.js +23 -23
  44. package/dist/data/event-log.js +23 -23
  45. package/dist/data/message-store.js +20 -20
  46. package/dist/data/navigation-store.js +9 -9
  47. package/dist/data/observation-store.js +4 -4
  48. package/dist/data/payload-store.js +17 -17
  49. package/dist/data/schema.js +429 -429
  50. package/dist/data/session-store.js +4 -4
  51. package/dist/data/telemetry-queries.js +54 -54
  52. package/dist/data/telemetry.js +156 -156
  53. package/dist/debug/events.js +12 -12
  54. package/dist/debug/failures.js +6 -6
  55. package/dist/debug/index.js +207 -207
  56. package/dist/debug/messages.js +6 -6
  57. package/dist/debug/pty.js +124 -124
  58. package/dist/debug/session.js +29 -29
  59. package/dist/debug/tools.js +5 -5
  60. package/dist/debug/web-snapshot-browser-scripts.js +294 -294
  61. package/dist/debug/web-streaming-browser-library.js +925 -925
  62. package/dist/debug/web-streaming-browser-scripts.js +232 -232
  63. package/dist/debug/web-streaming-provider-telemetry.js +4 -4
  64. package/dist/debug/web.js +93 -93
  65. package/dist/gateway/cli.js +19 -19
  66. package/dist/mcp/config-command.js +53 -53
  67. package/dist/mcp/index.js +21 -21
  68. package/dist/mcp/registry.js +11 -11
  69. package/dist/pi-packages/cli.js +11 -11
  70. package/dist/plugins/context-files-store.js +110 -110
  71. package/dist/plugins/context-files.js +4 -4
  72. package/dist/providers/glm.js +59 -0
  73. package/dist/ralph/cli.js +18 -18
  74. package/dist/ralph/templates.js +140 -140
  75. package/dist/reliability/store.js +226 -226
  76. package/dist/sessions/pibo-data-store.js +16 -16
  77. package/dist/sessions/sqlite-store.js +53 -53
  78. package/dist/setup/cli.js +58 -58
  79. package/dist/tools/agent-browser-wrapper.js +80 -80
  80. package/dist/tools/browser-use-cdp.js +12 -12
  81. package/dist/tools/browser-use-wrapper.js +762 -762
  82. package/dist/tools/guides.js +455 -455
  83. package/dist/tools/index.js +99 -99
  84. package/dist/tools/runtime/node-worker-source.js +205 -205
  85. package/dist/tools/runtime/python-worker-source.js +177 -177
  86. package/dist/vscode/cli.js +9 -9
  87. package/dist/web-annotations/cdp.js +900 -900
  88. package/dist/web-annotations/store.js +96 -96
  89. package/docs/README.md +23 -23
  90. package/docs/ops/install-developer-host.md +112 -112
  91. package/docs/ops/install-user-host.md +96 -96
  92. package/docs/ops/upgrade-user-to-developer-host.md +69 -69
  93. package/docs/ops/vscode-extension-release.md +160 -160
  94. package/package.json +1 -1
  95. package/skills/builtin/pi-agent-harness/SKILL.md +319 -319
  96. package/skills/builtin/pi-agent-harness/agents/openai.yaml +4 -4
  97. package/skills/builtin/pibo-docker-system/SKILL.md +170 -170
  98. package/skills/builtin/pibo-spec-writing/SKILL.md +330 -330
  99. package/skills/builtin/prd/SKILL.md +143 -143
  100. package/skills/builtin/ralph-loop/SKILL.md +359 -359
  101. package/skills/builtin/ralph-prd-json/SKILL.md +123 -123
  102. package/skills/builtin/skill-creator/LICENSE.txt +201 -201
  103. package/skills/builtin/skill-creator/SKILL.md +513 -513
  104. package/skills/builtin/skill-creator/agents/analyzer.md +274 -274
  105. package/skills/builtin/skill-creator/agents/comparator.md +202 -202
  106. package/skills/builtin/skill-creator/agents/grader.md +223 -223
  107. package/skills/builtin/skill-creator/assets/eval_review.html +146 -146
  108. package/skills/builtin/skill-creator/eval-viewer/generate_review.py +471 -471
  109. package/skills/builtin/skill-creator/eval-viewer/viewer.html +1325 -1325
  110. package/skills/builtin/skill-creator/references/schemas.md +430 -430
  111. package/skills/builtin/skill-creator/scripts/aggregate_benchmark.py +401 -401
  112. package/skills/builtin/skill-creator/scripts/generate_report.py +326 -326
  113. package/skills/builtin/skill-creator/scripts/improve_description.py +247 -247
  114. package/skills/builtin/skill-creator/scripts/package_skill.py +136 -136
  115. package/skills/builtin/skill-creator/scripts/quick_validate.py +102 -102
  116. package/skills/builtin/skill-creator/scripts/run_eval.py +310 -310
  117. package/skills/builtin/skill-creator/scripts/run_loop.py +328 -328
  118. package/skills/builtin/skill-creator/scripts/utils.py +47 -47
  119. package/skills/builtin/web-annotations/SKILL.md +93 -93
  120. package/src/mcp/LICENSE.mcp-cli +21 -21
  121. package/dist/apps/vscode-artifacts/pibo-vscode-1.3.0.vsix +0 -0
  122. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.3.vsix +0 -0
  123. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.4.vsix +0 -0
  124. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.5.vsix +0 -0
  125. package/dist/core/shared-app.js +0 -17
  126. package/dist/data/final-app-space-cutover-migration.js +0 -728
  127. package/dist/data/shared-app-migration.js +0 -757
  128. package/dist/session-ui/ownerViewModel.js +0 -27
  129. package/dist/shared-app.js +0 -4
@@ -6,768 +6,768 @@ function shellQuote(value) {
6
6
  return `'${value.replaceAll("'", "'\\''")}'`;
7
7
  }
8
8
  function createBrowserUseWrapper(realExecutablePath) {
9
- return `#!/bin/sh
10
- set -eu
11
-
12
- real_browser_use=${shellQuote(realExecutablePath)}
13
- default_profile=\${PIBO_BROWSER_USE_DEFAULT_PROFILE:-${shellQuote(BROWSER_USE_DEFAULT_PROFILE)}}
14
- fresh_flag=${shellQuote(BROWSER_USE_FRESH_PROFILE_FLAG)}
15
- python_cmd=\${PIBO_BROWSER_USE_PYTHON:-$(dirname "$real_browser_use")/python}
16
- if [ ! -x "$python_cmd" ]; then
17
- python_cmd=$(command -v python3 || command -v python || printf '%s' "$python_cmd")
18
- fi
19
-
20
- session=\${PIBO_BROWSER_USE_SESSION:-default}
21
- explicit_profile=0
22
- fresh_profile=0
23
- starts_browser=0
24
- show_help=0
25
- headed=0
26
- pibo_ensure_chrome=0
27
- sanitized_args=
28
-
29
- append_arg() {
30
- if [ "$#" -eq 0 ]; then
31
- return
32
- fi
33
- if [ -z "$sanitized_args" ]; then
34
- sanitized_args=$(printf '%s\\n' "$1")
35
- else
36
- sanitized_args=$(printf '%s\\n%s\\n' "$sanitized_args" "$1")
37
- fi
38
- }
39
-
40
- find_chrome() {
41
- if [ -n "\${PIBO_BROWSER_USE_CHROME:-}" ]; then
42
- printf '%s\\n' "$PIBO_BROWSER_USE_CHROME"
43
- return
44
- fi
45
- for candidate in google-chrome chrome chromium chromium-browser /opt/google/chrome/chrome; do
46
- if command -v "$candidate" >/dev/null 2>&1; then
47
- command -v "$candidate"
48
- return
49
- fi
50
- if [ -x "$candidate" ]; then
51
- printf '%s\\n' "$candidate"
52
- return
53
- fi
54
- done
55
- }
56
-
57
- resolve_profile_directory() {
58
- user_data_dir=$1
59
- profile_name=$2
60
-
61
- if [ -n "\${PIBO_BROWSER_USE_CHROME_PROFILE_DIRECTORY:-}" ]; then
62
- printf '%s\\n' "$PIBO_BROWSER_USE_CHROME_PROFILE_DIRECTORY"
63
- return
64
- fi
65
-
66
- "$python_cmd" - "$user_data_dir" "$profile_name" <<'PY'
67
- import json
68
- import sys
69
- from pathlib import Path
70
-
71
- user_data_dir = Path(sys.argv[1])
72
- profile_name = sys.argv[2]
73
- local_state = user_data_dir / "Local State"
74
- try:
75
- data = json.loads(local_state.read_text())
76
- except Exception:
77
- print(profile_name if (user_data_dir / profile_name).is_dir() else "Default")
78
- raise SystemExit
79
-
80
- info_cache = data.get("profile", {}).get("info_cache", {})
81
- if profile_name in info_cache:
82
- print(profile_name)
83
- raise SystemExit
84
-
85
- profile_name_lower = profile_name.lower()
86
- for directory, info in info_cache.items():
87
- if str(info.get("name", directory)).lower() == profile_name_lower:
88
- print(directory)
89
- raise SystemExit
90
-
91
- for directory in info_cache:
92
- if directory.lower() == profile_name_lower:
93
- print(directory)
94
- raise SystemExit
95
-
96
- print(profile_name if (user_data_dir / profile_name).is_dir() else "Default")
97
- PY
98
- }
99
-
100
- find_free_port() {
101
- "$python_cmd" - <<'PY'
102
- import socket
103
-
104
- with socket.socket() as sock:
105
- sock.bind(("127.0.0.1", 0))
106
- print(sock.getsockname()[1])
107
- PY
108
- }
109
-
110
- pibo_cdp_is_ready() {
111
- "$python_cmd" - "$1" <<'PY'
112
- import socket
113
- import sys
114
-
115
- port = int(sys.argv[1])
116
- try:
117
- with socket.create_connection(("127.0.0.1", port), timeout=0.2) as sock:
118
- sock.sendall(b"GET /json/version HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\nConnection: close\\r\\n\\r\\n")
119
- data = sock.recv(128)
120
- raise SystemExit(0 if b"200" in data else 1)
121
- except Exception:
122
- raise SystemExit(1)
123
- PY
124
- }
125
-
126
- find_chrome_pids_for_profile() {
127
- user_data_dir=$1
128
- "$python_cmd" - "$user_data_dir" <<'PYFINDCHROME'
129
- import os
130
- import shlex
131
- import subprocess
132
- import sys
133
-
134
- managed_dir = os.path.normpath(os.path.abspath(sys.argv[1]))
135
- browser_names = {"chrome", "google-chrome", "chromium", "chromium-browser"}
136
-
137
- def normalize_path(value):
138
- return os.path.normpath(os.path.abspath(value))
139
-
140
- def has_managed_user_data_dir(args):
141
- for index, arg in enumerate(args):
142
- if arg.startswith("--user-data-dir="):
143
- return normalize_path(arg.split("=", 1)[1]) == managed_dir
144
- if arg == "--user-data-dir" and index + 1 < len(args):
145
- return normalize_path(args[index + 1]) == managed_dir
146
- return False
147
-
148
- try:
149
- output = subprocess.check_output(["ps", "-eo", "pid=,args="], text=True, stderr=subprocess.DEVNULL)
150
- except Exception:
151
- raise SystemExit(0)
152
-
153
- for line in output.splitlines():
154
- line = line.strip()
155
- if not line:
156
- continue
157
- pid_text, _, command = line.partition(" ")
158
- if not pid_text.isdigit() or not command:
159
- continue
160
- try:
161
- args = shlex.split(command)
162
- except ValueError:
163
- continue
164
- if not args:
165
- continue
166
- command_name = os.path.basename(args[0]).lower()
167
- if command_name not in browser_names:
168
- continue
169
- if has_managed_user_data_dir(args):
170
- print(pid_text)
171
- PYFINDCHROME
172
- }
173
-
174
- find_recorded_browser_targets_for_profile() {
175
- user_data_dir=$1
176
- pid_file=$2
177
- pool_state_file=$3
178
- "$python_cmd" - "$user_data_dir" "$pid_file" "$pool_state_file" <<'PYRECORDED'
179
- import json
180
- import os
181
- import sys
182
- from pathlib import Path
183
-
184
- managed_dir = os.path.normpath(os.path.abspath(sys.argv[1]))
185
- pid_file = Path(sys.argv[2])
186
- pool_state_file = Path(sys.argv[3])
187
-
188
- def normalize_path(value):
189
- return os.path.normpath(os.path.abspath(value))
190
-
191
- def print_target(source, pid, pgid=""):
192
- try:
193
- pid_text = str(int(str(pid).strip()))
194
- except Exception:
195
- return
196
- if int(pid_text) <= 0:
197
- return
198
- pgid_text = ""
199
- if pgid not in (None, ""):
200
- try:
201
- pgid_text = str(int(str(pgid).strip()))
202
- except Exception:
203
- pgid_text = ""
204
- print(f"{source}\t{pid_text}\t{pgid_text}")
205
-
206
- try:
207
- data = json.loads(pool_state_file.read_text())
208
- if normalize_path(str(data.get("userDataDir", ""))) == managed_dir:
209
- print_target("pool-state", data.get("pid"), data.get("processGroupId"))
210
- except FileNotFoundError:
211
- pass
212
- except Exception:
213
- pass
214
-
215
- try:
216
- print_target("cdp-pid-file", pid_file.read_text())
217
- except FileNotFoundError:
218
- pass
219
- except Exception:
220
- pass
221
- PYRECORDED
222
- }
223
-
224
- process_group_for_pid() {
225
- pid=$1
226
- ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' '
227
- }
228
-
229
- process_is_running() {
230
- pid=$1
231
- if ! kill -0 "$pid" 2>/dev/null; then
232
- return 1
233
- fi
234
- state=$(ps -o stat= -p "$pid" 2>/dev/null | tr -d ' ' | cut -c1)
235
- [ "$state" != "Z" ]
236
- }
237
-
238
- wait_for_pid_exit() {
239
- pid=$1
240
- attempts=0
241
- while [ "$attempts" -lt "\${PIBO_BROWSER_POOL_TERM_WAIT_ATTEMPTS:-10}" ]; do
242
- if ! process_is_running "$pid"; then
243
- return 0
244
- fi
245
- attempts=$((attempts + 1))
246
- sleep "\${PIBO_BROWSER_POOL_TERM_WAIT_SECONDS:-0.1}"
247
- done
248
- return 1
249
- }
250
-
251
- terminate_managed_browser_target() {
252
- source=$1
253
- pid=$2
254
- recorded_pgid=$3
255
- if [ -z "$pid" ] || ! process_is_running "$pid"; then
256
- return 0
257
- fi
258
-
259
- actual_pgid=$(process_group_for_pid "$pid")
260
- target_group=
261
- if [ -n "$recorded_pgid" ] && [ "$actual_pgid" = "$recorded_pgid" ]; then
262
- target_group=$recorded_pgid
263
- elif [ -n "$actual_pgid" ] && [ "$actual_pgid" = "$pid" ]; then
264
- target_group=$actual_pgid
265
- fi
266
-
267
- if [ -n "$target_group" ]; then
268
- echo "pibo browser-use: terminating stale Chrome process group $target_group for managed profile ($source pid $pid)..." >&2
269
- kill -TERM "-$target_group" 2>/dev/null || true
270
- if ! wait_for_pid_exit "$pid"; then
271
- kill -KILL "-$target_group" 2>/dev/null || true
272
- wait_for_pid_exit "$pid" || true
273
- fi
274
- return 0
275
- fi
276
-
277
- echo "pibo browser-use: terminating stale Chrome process $pid for managed profile ($source)..." >&2
278
- kill -TERM "$pid" 2>/dev/null || true
279
- if ! wait_for_pid_exit "$pid"; then
280
- kill -KILL "$pid" 2>/dev/null || true
281
- wait_for_pid_exit "$pid" || true
282
- fi
283
- }
284
-
285
- pid_file_process_is_dead() {
286
- pid_file=$1
287
- if [ ! -s "$pid_file" ]; then
288
- return 0
289
- fi
290
- recorded_pid=$(cat "$pid_file" 2>/dev/null || true)
291
- if [ -z "$recorded_pid" ]; then
292
- return 0
293
- fi
294
- ! process_is_running "$recorded_pid"
295
- }
296
-
297
- kill_stale_chrome_for_profile() {
298
- user_data_dir=$1
299
- safe_session=$(printf '%s' "$session" | tr -c 'A-Za-z0-9_.-' '_')
300
- state_dir=\${BROWSER_USE_HOME:-$HOME/.browser-use}/pibo-cdp
301
- pid_file=$state_dir/$safe_session.pid
302
- port_file=$state_dir/$safe_session.port
303
- paths=$(browser_pool_paths)
304
- pool_state_file=$(printf '%s\n' "$paths" | sed -n '1p')
305
-
306
- # Prefer recorded pool/CDP pid metadata before falling back to exact profile matching.
307
- recorded_targets=$(find_recorded_browser_targets_for_profile "$user_data_dir" "$pid_file" "$pool_state_file")
308
- if [ -n "$recorded_targets" ]; then
309
- while IFS='\t' read -r source recorded_pid recorded_pgid; do
310
- terminate_managed_browser_target "$source" "$recorded_pid" "$recorded_pgid"
311
- done <<EOFRECORDED
312
- $recorded_targets
313
- EOFRECORDED
314
- fi
315
-
316
- # Then terminate any Chrome/Chromium process using this exact managed profile directory.
317
- stale_pids=$(find_chrome_pids_for_profile "$user_data_dir")
318
- for stale_pid in $stale_pids; do
319
- terminate_managed_browser_target "profile-match" "$stale_pid" ""
320
- done
321
-
322
- # Remove stale lock files if the managed browser is confirmed gone.
323
- sleep 0.2
324
- if [ -z "$(find_chrome_pids_for_profile "$user_data_dir")" ]; then
325
- for stale_file in SingletonLock SingletonCookie SingletonSocket; do
326
- if [ -f "$user_data_dir/$stale_file" ]; then
327
- rm -f "$user_data_dir/$stale_file" 2>/dev/null || true
328
- fi
329
- done
330
- fi
331
-
332
- # Remove stale CDP pid/port files only after their recorded process is gone.
333
- if pid_file_process_is_dead "$pid_file"; then
334
- rm -f "$pid_file" "$port_file" 2>/dev/null || true
335
- fi
336
- }
337
-
338
- persistent_chrome_url_if_alive() {
339
- safe_session=$(printf '%s' "$session" | tr -c 'A-Za-z0-9_.-' '_')
340
- state_dir=\${BROWSER_USE_HOME:-$HOME/.browser-use}/pibo-cdp
341
- pid_file=$state_dir/$safe_session.pid
342
- port_file=$state_dir/$safe_session.port
343
-
344
- if [ -s "$pid_file" ] && [ -s "$port_file" ]; then
345
- chrome_pid=$(cat "$pid_file" 2>/dev/null || true)
346
- chrome_port=$(cat "$port_file" 2>/dev/null || true)
347
- if [ -n "$chrome_pid" ] && [ -n "$chrome_port" ] && kill -0 "$chrome_pid" 2>/dev/null && pibo_cdp_is_ready "$chrome_port"; then
348
- printf 'http://127.0.0.1:%s\\n' "$chrome_port"
349
- return 0
350
- fi
351
- # Clean up stale state files when process is dead or CDP unreachable
352
- rm -f "$pid_file" "$port_file" 2>/dev/null || true
353
- fi
354
-
355
- return 1
356
- }
357
-
358
- start_persistent_chrome() {
359
- safe_session=$(printf '%s' "$session" | tr -c 'A-Za-z0-9_.-' '_')
360
- state_dir=\${BROWSER_USE_HOME:-$HOME/.browser-use}/pibo-cdp
361
- mkdir -p "$state_dir"
362
- pid_file=$state_dir/$safe_session.pid
363
- port_file=$state_dir/$safe_session.port
364
- log_file=$state_dir/$safe_session.chrome.log
365
-
366
- chrome_bin=$(find_chrome)
367
- if [ -z "$chrome_bin" ]; then
368
- echo "pibo browser-use: could not find Chrome; managed browser-pool acquire cannot start a browser. Install Chrome/Chromium or use $fresh_flag for Browser Use managed Chromium." >&2
369
- return 1
370
- fi
371
-
372
- user_data_dir=\${PIBO_BROWSER_USE_CHROME_USER_DATA_DIR:-}
373
- if [ -z "$user_data_dir" ]; then
374
- user_data_dir=\${BROWSER_USE_HOME:-$HOME/.browser-use}/chrome-profiles/$default_profile
375
- fi
376
-
377
- mkdir -p "$user_data_dir"
378
-
379
- retry=0
380
- while [ "$retry" -lt 2 ]; do
381
- kill_stale_chrome_for_profile "$user_data_dir"
382
- profile_directory=$(resolve_profile_directory "$user_data_dir" "$default_profile")
383
- chrome_port=$(find_free_port)
384
- headless_arg=
385
- if [ "$headed" -eq 0 ]; then
386
- headless_arg=--headless=new
387
- fi
388
- rm -f "$log_file"
389
- if command -v setsid >/dev/null 2>&1; then
390
- setsid "$chrome_bin" \
391
- --user-data-dir="$user_data_dir" \
392
- --profile-directory="$profile_directory" \
393
- --remote-debugging-port="$chrome_port" \
394
- --no-first-run \
395
- --no-default-browser-check \
396
- --disable-default-apps \
397
- --no-sandbox \
398
- --disable-setuid-sandbox \
399
- --disable-dev-shm-usage \
400
- $headless_arg \
401
- about:blank >"$log_file" 2>&1 &
402
- else
403
- nohup "$chrome_bin" \
404
- --user-data-dir="$user_data_dir" \
405
- --profile-directory="$profile_directory" \
406
- --remote-debugging-port="$chrome_port" \
407
- --no-first-run \
408
- --no-default-browser-check \
409
- --disable-default-apps \
410
- --no-sandbox \
411
- --disable-setuid-sandbox \
412
- --disable-dev-shm-usage \
413
- $headless_arg \
414
- about:blank >"$log_file" 2>&1 &
415
- fi
416
- chrome_pid=$!
417
-
418
- ready=0
419
- if [ "\${PIBO_BROWSER_USE_SKIP_CDP_WAIT:-}" = "1" ]; then
420
- ready=1
421
- fi
422
- attempt=0
423
- while [ "$ready" -ne 1 ] && [ "$attempt" -lt 50 ]; do
424
- if pibo_cdp_is_ready "$chrome_port"; then
425
- ready=1
426
- break
427
- fi
428
- if ! kill -0 "$chrome_pid" 2>/dev/null; then
429
- break
430
- fi
431
- attempt=$((attempt + 1))
432
- sleep 0.1
433
- done
434
-
435
- if [ "$ready" -eq 1 ]; then
436
- printf '%s\n' "$chrome_pid" > "$pid_file"
437
- printf '%s\n' "$chrome_port" > "$port_file"
438
- printf 'pibo browser-use: started Chrome profile "%s" (%s/%s) on CDP port %s.\n' "$default_profile" "$user_data_dir" "$profile_directory" "$chrome_port" >&2
439
- printf '%s\n%s\nhttp://127.0.0.1:%s\n%s\n' "$chrome_pid" "$chrome_port" "$chrome_port" "$user_data_dir"
440
- return 0
441
- fi
442
-
443
- echo "pibo browser-use: Chrome did not expose CDP on port $chrome_port (attempt $retry)." >&2
444
- sed -n '1,80p' "$log_file" >&2 2>/dev/null || true
445
- if grep -q "SingletonLock" "$log_file" 2>/dev/null; then
446
- echo "pibo browser-use: retrying after lock cleanup..." >&2
447
- fi
448
- retry=$((retry + 1))
449
- sleep 0.5
450
- done
451
-
452
- echo "pibo browser-use: Chrome failed to start after retries." >&2
453
- return 1
454
- }
455
-
456
- browser_pool_safe_segment() {
457
- printf '%s' "$1" | tr -c 'A-Za-z0-9_.-' '_'
458
- }
459
-
460
- browser_pool_worker_id() {
461
- printf '%s\n' "\${PIBO_BROWSER_POOL_WORKER_ID:-\${PIBO_COMPUTE_WORKER_ID:-\${HOSTNAME:-local}}}"
462
- }
463
-
464
- browser_pool_id() {
465
- printf '%s\n' "\${PIBO_BROWSER_POOL_ID:-default}"
466
- }
467
-
468
- browser_pool_root() {
469
- printf '%s\n' "\${PIBO_BROWSER_POOL_ROOT:-\${BROWSER_USE_HOME:-$HOME/.browser-use}/pibo-browser-pool}"
470
- }
471
-
472
- browser_pool_paths() {
473
- worker=$(browser_pool_safe_segment "$(browser_pool_worker_id)")
474
- pool=$(browser_pool_safe_segment "$(browser_pool_id)")
475
- base=$(browser_pool_root)/browser-pools/$worker/$pool
476
- printf '%s\n%s\n' "$base/state.json" "$base/state.lock"
477
- }
478
-
479
- browser_pool_read_summary() {
480
- state_file=$1
481
- "$python_cmd" - "$state_file" <<'PYSUMMARY'
482
- import json
483
- import sys
484
- from pathlib import Path
485
- path = Path(sys.argv[1])
486
- try:
487
- data = json.loads(path.read_text())
488
- except FileNotFoundError:
489
- print("missing")
490
- raise SystemExit(0)
491
- except Exception as exc:
492
- print("malformed")
493
- print(str(exc))
494
- raise SystemExit(0)
495
- for key in ("state", "pid", "cdpPort", "cdpUrl", "userDataDir", "activeLeaseId", "activeLeaseCount", "idleExpiresAt", "holder"):
496
- value = data.get(key, "")
497
- print(value if value is not None else "")
498
- PYSUMMARY
499
- }
500
-
501
- browser_pool_lease_is_busy() {
502
- active_lease=$1
503
- requested_lease=$2
504
- idle_expires_at=$3
505
- if [ -z "$active_lease" ] || [ "$active_lease" = "$requested_lease" ]; then
506
- return 1
507
- fi
508
- "$python_cmd" - "$idle_expires_at" <<'PYBUSY'
509
- import sys
510
- from datetime import datetime, timezone
511
- value = sys.argv[1]
512
- if not value:
513
- raise SystemExit(0)
514
- try:
515
- expires_at = datetime.fromisoformat(value.replace("Z", "+00:00"))
516
- except Exception:
517
- raise SystemExit(0)
518
- if expires_at.tzinfo is None:
519
- expires_at = expires_at.replace(tzinfo=timezone.utc)
520
- raise SystemExit(1 if expires_at <= datetime.now(timezone.utc) else 0)
521
- PYBUSY
522
- }
523
-
524
- browser_pool_write_state() {
525
- state_file=$1
526
- lifecycle_state=$2
527
- chrome_pid=$3
528
- chrome_port=$4
529
- cdp_url=$5
530
- user_data_dir=$6
531
- active_lease_id=$7
532
- holder=$8
533
- last_error=$9
534
- "$python_cmd" - "$state_file" "$(browser_pool_worker_id)" "$(browser_pool_id)" "\${PIBO_BROWSER_POOL_MAX_PROCESSES:-1}" "$lifecycle_state" "$chrome_pid" "$chrome_port" "$cdp_url" "$user_data_dir" "$active_lease_id" "$holder" "$last_error" <<'PYSTATE'
535
- import json
536
- import os
537
- import sys
538
- from datetime import datetime, timedelta, timezone
539
- from pathlib import Path
540
- state_file = Path(sys.argv[1])
541
- worker_id, pool_id, max_processes, lifecycle_state = sys.argv[2:6]
542
- pid, port, cdp_url, user_data_dir, lease_id, holder, last_error = sys.argv[6:13]
543
- now = datetime.now(timezone.utc)
544
- record = {
545
- "workerId": worker_id,
546
- "poolId": pool_id,
547
- "maxBrowserProcesses": int(max_processes or "1"),
548
- "state": lifecycle_state,
549
- "lastUsedAt": now.isoformat(timespec="milliseconds").replace("+00:00", "Z"),
550
- }
551
- if pid:
552
- record["pid"] = int(pid)
553
- record["processGroupId"] = int(pid)
554
- if port:
555
- record["cdpPort"] = int(port)
556
- if cdp_url:
557
- record["cdpUrl"] = cdp_url
558
- if user_data_dir:
559
- record["userDataDir"] = user_data_dir
560
- if lease_id:
561
- record["activeLeaseId"] = lease_id
562
- record["activeLeaseCount"] = 1
563
- record["cleanupStatus"] = "not-attempted"
564
- record["idleExpiresAt"] = (now + timedelta(milliseconds=int(os.environ.get("PIBO_BROWSER_POOL_IDLE_TIMEOUT_MS", "600000")))).isoformat(timespec="milliseconds").replace("+00:00", "Z")
565
- else:
566
- record["activeLeaseCount"] = 0
567
- if holder:
568
- record["holder"] = holder
569
- if last_error:
570
- record["lastError"] = last_error
571
- state_file.parent.mkdir(parents=True, exist_ok=True)
572
- tmp = state_file.with_name(f"{state_file.name}.{os.getpid()}.tmp")
573
- tmp.write_text(json.dumps(record, indent=2) + "\\n")
574
- tmp.replace(state_file)
575
- PYSTATE
576
- }
577
-
578
- browser_pool_acquire() {
579
- paths=$(browser_pool_paths)
580
- state_file=$(printf '%s\n' "$paths" | sed -n '1p')
581
- lock_dir=$(printf '%s\n' "$paths" | sed -n '2p')
582
- mkdir -p "$(dirname "$state_file")"
583
- safe_session=$(browser_pool_safe_segment "$session")
584
- lease_id=\${PIBO_BROWSER_POOL_LEASE_ID:-browser-use:$safe_session}
585
- holder=\${PIBO_BROWSER_POOL_HOLDER:-browser-use:$session}
586
- attempts=0
587
- while ! mkdir "$lock_dir" 2>/dev/null; do
588
- attempts=$((attempts + 1))
589
- if [ "$attempts" -ge "\${PIBO_BROWSER_POOL_LOCK_ATTEMPTS:-100}" ]; then
590
- echo "pibo browser-use: managed browser-pool acquire timed out waiting for $lock_dir" >&2
591
- return 1
592
- fi
593
- sleep 0.05
594
- done
595
- trap 'rm -rf "$lock_dir"' EXIT INT TERM
596
-
597
- summary=$(browser_pool_read_summary "$state_file")
598
- summary_state=$(printf '%s\n' "$summary" | sed -n '1p')
599
- if [ "$summary_state" = "malformed" ]; then
600
- malformed_reason=$(printf '%s\n' "$summary" | sed -n '2p')
601
- browser_pool_write_state "$state_file" dirty "" "" "" "" "" "$holder" "Malformed browser pool state: $malformed_reason"
602
- elif [ "$summary_state" != "missing" ]; then
603
- recorded_state=$summary_state
604
- recorded_pid=$(printf '%s\n' "$summary" | sed -n '2p')
605
- recorded_port=$(printf '%s\n' "$summary" | sed -n '3p')
606
- recorded_cdp_url=$(printf '%s\n' "$summary" | sed -n '4p')
607
- recorded_user_data_dir=$(printf '%s\n' "$summary" | sed -n '5p')
608
- recorded_active_lease_id=$(printf '%s\n' "$summary" | sed -n '6p')
609
- recorded_idle_expires_at=$(printf '%s\n' "$summary" | sed -n '8p')
610
- recorded_holder=$(printf '%s\n' "$summary" | sed -n '9p')
611
- if [ "$recorded_state" = "leased" ] && browser_pool_lease_is_busy "$recorded_active_lease_id" "$lease_id" "$recorded_idle_expires_at" && [ -n "$recorded_pid" ] && [ -n "$recorded_port" ] && [ -n "$recorded_cdp_url" ] && kill -0 "$recorded_pid" 2>/dev/null && pibo_cdp_is_ready "$recorded_port"; then
612
- busy_reason="pool-exhausted: browser pool $(browser_pool_id) is already leased by $recorded_active_lease_id until \${recorded_idle_expires_at:-unknown}"
613
- echo "pibo browser-use: $busy_reason" >&2
614
- rm -rf "$lock_dir"
615
- trap - EXIT INT TERM
616
- return 1
617
- fi
618
- if [ "$recorded_state" != "dirty" ] && [ -n "$recorded_pid" ] && [ -n "$recorded_port" ] && [ -n "$recorded_cdp_url" ] && kill -0 "$recorded_pid" 2>/dev/null && pibo_cdp_is_ready "$recorded_port"; then
619
- browser_pool_write_state "$state_file" leased "$recorded_pid" "$recorded_port" "$recorded_cdp_url" "$recorded_user_data_dir" "$lease_id" "$holder" ""
620
- rm -rf "$lock_dir"
621
- trap - EXIT INT TERM
622
- printf '%s\n' "$recorded_cdp_url"
623
- return 0
624
- fi
625
- browser_pool_write_state "$state_file" stale "$recorded_pid" "$recorded_port" "$recorded_cdp_url" "$recorded_user_data_dir" "" "$holder" "Recorded browser is not alive or CDP is unreachable"
626
- fi
627
-
628
- start_result=$(start_persistent_chrome) || {
629
- browser_pool_write_state "$state_file" dirty "" "" "" "" "" "$holder" "Managed browser start failed"
630
- rm -rf "$lock_dir"
631
- trap - EXIT INT TERM
632
- return 1
633
- }
634
- chrome_pid=$(printf '%s\n' "$start_result" | sed -n '1p')
635
- chrome_port=$(printf '%s\n' "$start_result" | sed -n '2p')
636
- cdp_url=$(printf '%s\n' "$start_result" | sed -n '3p')
637
- user_data_dir=$(printf '%s\n' "$start_result" | sed -n '4p')
638
- browser_pool_write_state "$state_file" leased "$chrome_pid" "$chrome_port" "$cdp_url" "$user_data_dir" "$lease_id" "$holder" ""
639
- rm -rf "$lock_dir"
640
- trap - EXIT INT TERM
641
- printf '%s\n' "$cdp_url"
642
- }
643
-
644
- ensure_persistent_chrome() {
645
- if cdp_url=$(browser_pool_acquire); then
646
- printf '%s\n' "$cdp_url"
647
- return 0
648
- fi
649
- echo "pibo browser-use: managed browser-pool acquire failed; refusing to start an unmanaged browser." >&2
650
- exit 1
651
- }
652
-
653
- while [ "$#" -gt 0 ]; do
654
- arg=$1
655
- shift
656
-
657
- case "$arg" in
658
- "$fresh_flag")
659
- fresh_profile=1
660
- continue
661
- ;;
662
- --help|-h)
663
- show_help=1
664
- ;;
665
- --headed)
666
- headed=1
667
- ;;
668
- --profile)
669
- explicit_profile=1
670
- append_arg "$arg"
671
- continue
672
- ;;
673
- --cdp-url)
674
- explicit_profile=1
675
- append_arg "$arg"
676
- if [ "$#" -gt 0 ]; then
677
- append_arg "$1"
678
- shift
679
- fi
680
- continue
681
- ;;
682
- --profile=*|--cdp-url=*|--connect|--mcp|--use-cloud|--cloud-profile-id|--cloud-profile-id=*)
683
- explicit_profile=1
684
- ;;
685
- --session)
686
- append_arg "$arg"
687
- if [ "$#" -gt 0 ]; then
688
- session=$1
689
- append_arg "$1"
690
- shift
691
- fi
692
- continue
693
- ;;
694
- --session=*)
695
- session=\${arg#--session=}
696
- ;;
697
- --pibo-ensure-chrome)
698
- pibo_ensure_chrome=1
699
- continue
700
- ;;
701
- -*)
702
- ;;
703
- open|click|type|input|scroll|back|screenshot|state|switch|close-tab|keys|select|upload|eval|extract|hover|dblclick|rightclick|cookies|wait|get|python)
704
- starts_browser=1
705
- ;;
706
- close|sessions|install|init|setup|doctor|cloud|profile)
707
- ;;
708
- esac
709
-
710
- append_arg "$arg"
711
- done
712
-
713
- if [ "$pibo_ensure_chrome" -eq 1 ]; then
714
- cdp_url=$(ensure_persistent_chrome)
715
- printf '%s\\n' "$cdp_url"
716
- exit 0
717
- fi
718
-
719
- if [ "$show_help" -eq 1 ]; then
720
- {
721
- echo "Pibo browser-use wrapper:"
722
- echo " New browser-use daemon sessions default to persistent Chrome profile \\"$default_profile\\" via CDP."
723
- echo " Use $fresh_flag to start a fresh temporary browser profile."
724
- echo " Use --pibo-ensure-chrome to only start Chrome and print the CDP URL."
725
- echo ""
726
- } >&2
727
- fi
728
-
729
- set --
730
- if [ -n "$sanitized_args" ]; then
731
- while IFS= read -r item; do
732
- set -- "$@" "$item"
733
- done <<EOF
734
- $sanitized_args
735
- EOF
736
- fi
737
-
738
- # Prepend --session if the user did not pass it explicitly and a non-default session is set.
739
- has_session_arg=0
740
- for a in "$@"; do
741
- case "$a" in
742
- --session|--session=*) has_session_arg=1; break ;;
743
- esac
744
- done
745
- if [ "$has_session_arg" -eq 0 ] && [ "$session" != "default" ]; then
746
- set -- --session "$session" "$@"
747
- fi
748
-
749
- session_pid_file=\${BROWSER_USE_HOME:-$HOME/.browser-use}/$session.pid
750
- session_alive=0
751
- if [ -s "$session_pid_file" ]; then
752
- session_pid=$(cat "$session_pid_file" 2>/dev/null || true)
753
- if [ -n "$session_pid" ] && kill -0 "$session_pid" 2>/dev/null; then
754
- session_alive=1
755
- else
756
- # Clean up stale session pid file
757
- rm -f "$session_pid_file" 2>/dev/null || true
758
- fi
759
- fi
760
-
761
- if [ "$starts_browser" -eq 1 ] && [ "$explicit_profile" -eq 0 ] && [ "$fresh_profile" -eq 0 ]; then
762
- if [ "$session_alive" -eq 1 ]; then
763
- "$real_browser_use" --session "$session" close >/dev/null 2>&1 || true
764
- sleep 0.2
765
- fi
766
- cdp_url=$(ensure_persistent_chrome)
767
- exec "$real_browser_use" --cdp-url "$cdp_url" "$@"
768
- fi
769
-
770
- exec "$real_browser_use" "$@"
9
+ return `#!/bin/sh
10
+ set -eu
11
+
12
+ real_browser_use=${shellQuote(realExecutablePath)}
13
+ default_profile=\${PIBO_BROWSER_USE_DEFAULT_PROFILE:-${shellQuote(BROWSER_USE_DEFAULT_PROFILE)}}
14
+ fresh_flag=${shellQuote(BROWSER_USE_FRESH_PROFILE_FLAG)}
15
+ python_cmd=\${PIBO_BROWSER_USE_PYTHON:-$(dirname "$real_browser_use")/python}
16
+ if [ ! -x "$python_cmd" ]; then
17
+ python_cmd=$(command -v python3 || command -v python || printf '%s' "$python_cmd")
18
+ fi
19
+
20
+ session=\${PIBO_BROWSER_USE_SESSION:-default}
21
+ explicit_profile=0
22
+ fresh_profile=0
23
+ starts_browser=0
24
+ show_help=0
25
+ headed=0
26
+ pibo_ensure_chrome=0
27
+ sanitized_args=
28
+
29
+ append_arg() {
30
+ if [ "$#" -eq 0 ]; then
31
+ return
32
+ fi
33
+ if [ -z "$sanitized_args" ]; then
34
+ sanitized_args=$(printf '%s\\n' "$1")
35
+ else
36
+ sanitized_args=$(printf '%s\\n%s\\n' "$sanitized_args" "$1")
37
+ fi
38
+ }
39
+
40
+ find_chrome() {
41
+ if [ -n "\${PIBO_BROWSER_USE_CHROME:-}" ]; then
42
+ printf '%s\\n' "$PIBO_BROWSER_USE_CHROME"
43
+ return
44
+ fi
45
+ for candidate in google-chrome chrome chromium chromium-browser /opt/google/chrome/chrome; do
46
+ if command -v "$candidate" >/dev/null 2>&1; then
47
+ command -v "$candidate"
48
+ return
49
+ fi
50
+ if [ -x "$candidate" ]; then
51
+ printf '%s\\n' "$candidate"
52
+ return
53
+ fi
54
+ done
55
+ }
56
+
57
+ resolve_profile_directory() {
58
+ user_data_dir=$1
59
+ profile_name=$2
60
+
61
+ if [ -n "\${PIBO_BROWSER_USE_CHROME_PROFILE_DIRECTORY:-}" ]; then
62
+ printf '%s\\n' "$PIBO_BROWSER_USE_CHROME_PROFILE_DIRECTORY"
63
+ return
64
+ fi
65
+
66
+ "$python_cmd" - "$user_data_dir" "$profile_name" <<'PY'
67
+ import json
68
+ import sys
69
+ from pathlib import Path
70
+
71
+ user_data_dir = Path(sys.argv[1])
72
+ profile_name = sys.argv[2]
73
+ local_state = user_data_dir / "Local State"
74
+ try:
75
+ data = json.loads(local_state.read_text())
76
+ except Exception:
77
+ print(profile_name if (user_data_dir / profile_name).is_dir() else "Default")
78
+ raise SystemExit
79
+
80
+ info_cache = data.get("profile", {}).get("info_cache", {})
81
+ if profile_name in info_cache:
82
+ print(profile_name)
83
+ raise SystemExit
84
+
85
+ profile_name_lower = profile_name.lower()
86
+ for directory, info in info_cache.items():
87
+ if str(info.get("name", directory)).lower() == profile_name_lower:
88
+ print(directory)
89
+ raise SystemExit
90
+
91
+ for directory in info_cache:
92
+ if directory.lower() == profile_name_lower:
93
+ print(directory)
94
+ raise SystemExit
95
+
96
+ print(profile_name if (user_data_dir / profile_name).is_dir() else "Default")
97
+ PY
98
+ }
99
+
100
+ find_free_port() {
101
+ "$python_cmd" - <<'PY'
102
+ import socket
103
+
104
+ with socket.socket() as sock:
105
+ sock.bind(("127.0.0.1", 0))
106
+ print(sock.getsockname()[1])
107
+ PY
108
+ }
109
+
110
+ pibo_cdp_is_ready() {
111
+ "$python_cmd" - "$1" <<'PY'
112
+ import socket
113
+ import sys
114
+
115
+ port = int(sys.argv[1])
116
+ try:
117
+ with socket.create_connection(("127.0.0.1", port), timeout=0.2) as sock:
118
+ sock.sendall(b"GET /json/version HTTP/1.1\\r\\nHost: 127.0.0.1\\r\\nConnection: close\\r\\n\\r\\n")
119
+ data = sock.recv(128)
120
+ raise SystemExit(0 if b"200" in data else 1)
121
+ except Exception:
122
+ raise SystemExit(1)
123
+ PY
124
+ }
125
+
126
+ find_chrome_pids_for_profile() {
127
+ user_data_dir=$1
128
+ "$python_cmd" - "$user_data_dir" <<'PYFINDCHROME'
129
+ import os
130
+ import shlex
131
+ import subprocess
132
+ import sys
133
+
134
+ managed_dir = os.path.normpath(os.path.abspath(sys.argv[1]))
135
+ browser_names = {"chrome", "google-chrome", "chromium", "chromium-browser"}
136
+
137
+ def normalize_path(value):
138
+ return os.path.normpath(os.path.abspath(value))
139
+
140
+ def has_managed_user_data_dir(args):
141
+ for index, arg in enumerate(args):
142
+ if arg.startswith("--user-data-dir="):
143
+ return normalize_path(arg.split("=", 1)[1]) == managed_dir
144
+ if arg == "--user-data-dir" and index + 1 < len(args):
145
+ return normalize_path(args[index + 1]) == managed_dir
146
+ return False
147
+
148
+ try:
149
+ output = subprocess.check_output(["ps", "-eo", "pid=,args="], text=True, stderr=subprocess.DEVNULL)
150
+ except Exception:
151
+ raise SystemExit(0)
152
+
153
+ for line in output.splitlines():
154
+ line = line.strip()
155
+ if not line:
156
+ continue
157
+ pid_text, _, command = line.partition(" ")
158
+ if not pid_text.isdigit() or not command:
159
+ continue
160
+ try:
161
+ args = shlex.split(command)
162
+ except ValueError:
163
+ continue
164
+ if not args:
165
+ continue
166
+ command_name = os.path.basename(args[0]).lower()
167
+ if command_name not in browser_names:
168
+ continue
169
+ if has_managed_user_data_dir(args):
170
+ print(pid_text)
171
+ PYFINDCHROME
172
+ }
173
+
174
+ find_recorded_browser_targets_for_profile() {
175
+ user_data_dir=$1
176
+ pid_file=$2
177
+ pool_state_file=$3
178
+ "$python_cmd" - "$user_data_dir" "$pid_file" "$pool_state_file" <<'PYRECORDED'
179
+ import json
180
+ import os
181
+ import sys
182
+ from pathlib import Path
183
+
184
+ managed_dir = os.path.normpath(os.path.abspath(sys.argv[1]))
185
+ pid_file = Path(sys.argv[2])
186
+ pool_state_file = Path(sys.argv[3])
187
+
188
+ def normalize_path(value):
189
+ return os.path.normpath(os.path.abspath(value))
190
+
191
+ def print_target(source, pid, pgid=""):
192
+ try:
193
+ pid_text = str(int(str(pid).strip()))
194
+ except Exception:
195
+ return
196
+ if int(pid_text) <= 0:
197
+ return
198
+ pgid_text = ""
199
+ if pgid not in (None, ""):
200
+ try:
201
+ pgid_text = str(int(str(pgid).strip()))
202
+ except Exception:
203
+ pgid_text = ""
204
+ print(f"{source}\t{pid_text}\t{pgid_text}")
205
+
206
+ try:
207
+ data = json.loads(pool_state_file.read_text())
208
+ if normalize_path(str(data.get("userDataDir", ""))) == managed_dir:
209
+ print_target("pool-state", data.get("pid"), data.get("processGroupId"))
210
+ except FileNotFoundError:
211
+ pass
212
+ except Exception:
213
+ pass
214
+
215
+ try:
216
+ print_target("cdp-pid-file", pid_file.read_text())
217
+ except FileNotFoundError:
218
+ pass
219
+ except Exception:
220
+ pass
221
+ PYRECORDED
222
+ }
223
+
224
+ process_group_for_pid() {
225
+ pid=$1
226
+ ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' '
227
+ }
228
+
229
+ process_is_running() {
230
+ pid=$1
231
+ if ! kill -0 "$pid" 2>/dev/null; then
232
+ return 1
233
+ fi
234
+ state=$(ps -o stat= -p "$pid" 2>/dev/null | tr -d ' ' | cut -c1)
235
+ [ "$state" != "Z" ]
236
+ }
237
+
238
+ wait_for_pid_exit() {
239
+ pid=$1
240
+ attempts=0
241
+ while [ "$attempts" -lt "\${PIBO_BROWSER_POOL_TERM_WAIT_ATTEMPTS:-10}" ]; do
242
+ if ! process_is_running "$pid"; then
243
+ return 0
244
+ fi
245
+ attempts=$((attempts + 1))
246
+ sleep "\${PIBO_BROWSER_POOL_TERM_WAIT_SECONDS:-0.1}"
247
+ done
248
+ return 1
249
+ }
250
+
251
+ terminate_managed_browser_target() {
252
+ source=$1
253
+ pid=$2
254
+ recorded_pgid=$3
255
+ if [ -z "$pid" ] || ! process_is_running "$pid"; then
256
+ return 0
257
+ fi
258
+
259
+ actual_pgid=$(process_group_for_pid "$pid")
260
+ target_group=
261
+ if [ -n "$recorded_pgid" ] && [ "$actual_pgid" = "$recorded_pgid" ]; then
262
+ target_group=$recorded_pgid
263
+ elif [ -n "$actual_pgid" ] && [ "$actual_pgid" = "$pid" ]; then
264
+ target_group=$actual_pgid
265
+ fi
266
+
267
+ if [ -n "$target_group" ]; then
268
+ echo "pibo browser-use: terminating stale Chrome process group $target_group for managed profile ($source pid $pid)..." >&2
269
+ kill -TERM "-$target_group" 2>/dev/null || true
270
+ if ! wait_for_pid_exit "$pid"; then
271
+ kill -KILL "-$target_group" 2>/dev/null || true
272
+ wait_for_pid_exit "$pid" || true
273
+ fi
274
+ return 0
275
+ fi
276
+
277
+ echo "pibo browser-use: terminating stale Chrome process $pid for managed profile ($source)..." >&2
278
+ kill -TERM "$pid" 2>/dev/null || true
279
+ if ! wait_for_pid_exit "$pid"; then
280
+ kill -KILL "$pid" 2>/dev/null || true
281
+ wait_for_pid_exit "$pid" || true
282
+ fi
283
+ }
284
+
285
+ pid_file_process_is_dead() {
286
+ pid_file=$1
287
+ if [ ! -s "$pid_file" ]; then
288
+ return 0
289
+ fi
290
+ recorded_pid=$(cat "$pid_file" 2>/dev/null || true)
291
+ if [ -z "$recorded_pid" ]; then
292
+ return 0
293
+ fi
294
+ ! process_is_running "$recorded_pid"
295
+ }
296
+
297
+ kill_stale_chrome_for_profile() {
298
+ user_data_dir=$1
299
+ safe_session=$(printf '%s' "$session" | tr -c 'A-Za-z0-9_.-' '_')
300
+ state_dir=\${BROWSER_USE_HOME:-$HOME/.browser-use}/pibo-cdp
301
+ pid_file=$state_dir/$safe_session.pid
302
+ port_file=$state_dir/$safe_session.port
303
+ paths=$(browser_pool_paths)
304
+ pool_state_file=$(printf '%s\n' "$paths" | sed -n '1p')
305
+
306
+ # Prefer recorded pool/CDP pid metadata before falling back to exact profile matching.
307
+ recorded_targets=$(find_recorded_browser_targets_for_profile "$user_data_dir" "$pid_file" "$pool_state_file")
308
+ if [ -n "$recorded_targets" ]; then
309
+ while IFS='\t' read -r source recorded_pid recorded_pgid; do
310
+ terminate_managed_browser_target "$source" "$recorded_pid" "$recorded_pgid"
311
+ done <<EOFRECORDED
312
+ $recorded_targets
313
+ EOFRECORDED
314
+ fi
315
+
316
+ # Then terminate any Chrome/Chromium process using this exact managed profile directory.
317
+ stale_pids=$(find_chrome_pids_for_profile "$user_data_dir")
318
+ for stale_pid in $stale_pids; do
319
+ terminate_managed_browser_target "profile-match" "$stale_pid" ""
320
+ done
321
+
322
+ # Remove stale lock files if the managed browser is confirmed gone.
323
+ sleep 0.2
324
+ if [ -z "$(find_chrome_pids_for_profile "$user_data_dir")" ]; then
325
+ for stale_file in SingletonLock SingletonCookie SingletonSocket; do
326
+ if [ -f "$user_data_dir/$stale_file" ]; then
327
+ rm -f "$user_data_dir/$stale_file" 2>/dev/null || true
328
+ fi
329
+ done
330
+ fi
331
+
332
+ # Remove stale CDP pid/port files only after their recorded process is gone.
333
+ if pid_file_process_is_dead "$pid_file"; then
334
+ rm -f "$pid_file" "$port_file" 2>/dev/null || true
335
+ fi
336
+ }
337
+
338
+ persistent_chrome_url_if_alive() {
339
+ safe_session=$(printf '%s' "$session" | tr -c 'A-Za-z0-9_.-' '_')
340
+ state_dir=\${BROWSER_USE_HOME:-$HOME/.browser-use}/pibo-cdp
341
+ pid_file=$state_dir/$safe_session.pid
342
+ port_file=$state_dir/$safe_session.port
343
+
344
+ if [ -s "$pid_file" ] && [ -s "$port_file" ]; then
345
+ chrome_pid=$(cat "$pid_file" 2>/dev/null || true)
346
+ chrome_port=$(cat "$port_file" 2>/dev/null || true)
347
+ if [ -n "$chrome_pid" ] && [ -n "$chrome_port" ] && kill -0 "$chrome_pid" 2>/dev/null && pibo_cdp_is_ready "$chrome_port"; then
348
+ printf 'http://127.0.0.1:%s\\n' "$chrome_port"
349
+ return 0
350
+ fi
351
+ # Clean up stale state files when process is dead or CDP unreachable
352
+ rm -f "$pid_file" "$port_file" 2>/dev/null || true
353
+ fi
354
+
355
+ return 1
356
+ }
357
+
358
+ start_persistent_chrome() {
359
+ safe_session=$(printf '%s' "$session" | tr -c 'A-Za-z0-9_.-' '_')
360
+ state_dir=\${BROWSER_USE_HOME:-$HOME/.browser-use}/pibo-cdp
361
+ mkdir -p "$state_dir"
362
+ pid_file=$state_dir/$safe_session.pid
363
+ port_file=$state_dir/$safe_session.port
364
+ log_file=$state_dir/$safe_session.chrome.log
365
+
366
+ chrome_bin=$(find_chrome)
367
+ if [ -z "$chrome_bin" ]; then
368
+ echo "pibo browser-use: could not find Chrome; managed browser-pool acquire cannot start a browser. Install Chrome/Chromium or use $fresh_flag for Browser Use managed Chromium." >&2
369
+ return 1
370
+ fi
371
+
372
+ user_data_dir=\${PIBO_BROWSER_USE_CHROME_USER_DATA_DIR:-}
373
+ if [ -z "$user_data_dir" ]; then
374
+ user_data_dir=\${BROWSER_USE_HOME:-$HOME/.browser-use}/chrome-profiles/$default_profile
375
+ fi
376
+
377
+ mkdir -p "$user_data_dir"
378
+
379
+ retry=0
380
+ while [ "$retry" -lt 2 ]; do
381
+ kill_stale_chrome_for_profile "$user_data_dir"
382
+ profile_directory=$(resolve_profile_directory "$user_data_dir" "$default_profile")
383
+ chrome_port=$(find_free_port)
384
+ headless_arg=
385
+ if [ "$headed" -eq 0 ]; then
386
+ headless_arg=--headless=new
387
+ fi
388
+ rm -f "$log_file"
389
+ if command -v setsid >/dev/null 2>&1; then
390
+ setsid "$chrome_bin" \
391
+ --user-data-dir="$user_data_dir" \
392
+ --profile-directory="$profile_directory" \
393
+ --remote-debugging-port="$chrome_port" \
394
+ --no-first-run \
395
+ --no-default-browser-check \
396
+ --disable-default-apps \
397
+ --no-sandbox \
398
+ --disable-setuid-sandbox \
399
+ --disable-dev-shm-usage \
400
+ $headless_arg \
401
+ about:blank >"$log_file" 2>&1 &
402
+ else
403
+ nohup "$chrome_bin" \
404
+ --user-data-dir="$user_data_dir" \
405
+ --profile-directory="$profile_directory" \
406
+ --remote-debugging-port="$chrome_port" \
407
+ --no-first-run \
408
+ --no-default-browser-check \
409
+ --disable-default-apps \
410
+ --no-sandbox \
411
+ --disable-setuid-sandbox \
412
+ --disable-dev-shm-usage \
413
+ $headless_arg \
414
+ about:blank >"$log_file" 2>&1 &
415
+ fi
416
+ chrome_pid=$!
417
+
418
+ ready=0
419
+ if [ "\${PIBO_BROWSER_USE_SKIP_CDP_WAIT:-}" = "1" ]; then
420
+ ready=1
421
+ fi
422
+ attempt=0
423
+ while [ "$ready" -ne 1 ] && [ "$attempt" -lt 50 ]; do
424
+ if pibo_cdp_is_ready "$chrome_port"; then
425
+ ready=1
426
+ break
427
+ fi
428
+ if ! kill -0 "$chrome_pid" 2>/dev/null; then
429
+ break
430
+ fi
431
+ attempt=$((attempt + 1))
432
+ sleep 0.1
433
+ done
434
+
435
+ if [ "$ready" -eq 1 ]; then
436
+ printf '%s\n' "$chrome_pid" > "$pid_file"
437
+ printf '%s\n' "$chrome_port" > "$port_file"
438
+ printf 'pibo browser-use: started Chrome profile "%s" (%s/%s) on CDP port %s.\n' "$default_profile" "$user_data_dir" "$profile_directory" "$chrome_port" >&2
439
+ printf '%s\n%s\nhttp://127.0.0.1:%s\n%s\n' "$chrome_pid" "$chrome_port" "$chrome_port" "$user_data_dir"
440
+ return 0
441
+ fi
442
+
443
+ echo "pibo browser-use: Chrome did not expose CDP on port $chrome_port (attempt $retry)." >&2
444
+ sed -n '1,80p' "$log_file" >&2 2>/dev/null || true
445
+ if grep -q "SingletonLock" "$log_file" 2>/dev/null; then
446
+ echo "pibo browser-use: retrying after lock cleanup..." >&2
447
+ fi
448
+ retry=$((retry + 1))
449
+ sleep 0.5
450
+ done
451
+
452
+ echo "pibo browser-use: Chrome failed to start after retries." >&2
453
+ return 1
454
+ }
455
+
456
+ browser_pool_safe_segment() {
457
+ printf '%s' "$1" | tr -c 'A-Za-z0-9_.-' '_'
458
+ }
459
+
460
+ browser_pool_worker_id() {
461
+ printf '%s\n' "\${PIBO_BROWSER_POOL_WORKER_ID:-\${PIBO_COMPUTE_WORKER_ID:-\${HOSTNAME:-local}}}"
462
+ }
463
+
464
+ browser_pool_id() {
465
+ printf '%s\n' "\${PIBO_BROWSER_POOL_ID:-default}"
466
+ }
467
+
468
+ browser_pool_root() {
469
+ printf '%s\n' "\${PIBO_BROWSER_POOL_ROOT:-\${BROWSER_USE_HOME:-$HOME/.browser-use}/pibo-browser-pool}"
470
+ }
471
+
472
+ browser_pool_paths() {
473
+ worker=$(browser_pool_safe_segment "$(browser_pool_worker_id)")
474
+ pool=$(browser_pool_safe_segment "$(browser_pool_id)")
475
+ base=$(browser_pool_root)/browser-pools/$worker/$pool
476
+ printf '%s\n%s\n' "$base/state.json" "$base/state.lock"
477
+ }
478
+
479
+ browser_pool_read_summary() {
480
+ state_file=$1
481
+ "$python_cmd" - "$state_file" <<'PYSUMMARY'
482
+ import json
483
+ import sys
484
+ from pathlib import Path
485
+ path = Path(sys.argv[1])
486
+ try:
487
+ data = json.loads(path.read_text())
488
+ except FileNotFoundError:
489
+ print("missing")
490
+ raise SystemExit(0)
491
+ except Exception as exc:
492
+ print("malformed")
493
+ print(str(exc))
494
+ raise SystemExit(0)
495
+ for key in ("state", "pid", "cdpPort", "cdpUrl", "userDataDir", "activeLeaseId", "activeLeaseCount", "idleExpiresAt", "holder"):
496
+ value = data.get(key, "")
497
+ print(value if value is not None else "")
498
+ PYSUMMARY
499
+ }
500
+
501
+ browser_pool_lease_is_busy() {
502
+ active_lease=$1
503
+ requested_lease=$2
504
+ idle_expires_at=$3
505
+ if [ -z "$active_lease" ] || [ "$active_lease" = "$requested_lease" ]; then
506
+ return 1
507
+ fi
508
+ "$python_cmd" - "$idle_expires_at" <<'PYBUSY'
509
+ import sys
510
+ from datetime import datetime, timezone
511
+ value = sys.argv[1]
512
+ if not value:
513
+ raise SystemExit(0)
514
+ try:
515
+ expires_at = datetime.fromisoformat(value.replace("Z", "+00:00"))
516
+ except Exception:
517
+ raise SystemExit(0)
518
+ if expires_at.tzinfo is None:
519
+ expires_at = expires_at.replace(tzinfo=timezone.utc)
520
+ raise SystemExit(1 if expires_at <= datetime.now(timezone.utc) else 0)
521
+ PYBUSY
522
+ }
523
+
524
+ browser_pool_write_state() {
525
+ state_file=$1
526
+ lifecycle_state=$2
527
+ chrome_pid=$3
528
+ chrome_port=$4
529
+ cdp_url=$5
530
+ user_data_dir=$6
531
+ active_lease_id=$7
532
+ holder=$8
533
+ last_error=$9
534
+ "$python_cmd" - "$state_file" "$(browser_pool_worker_id)" "$(browser_pool_id)" "\${PIBO_BROWSER_POOL_MAX_PROCESSES:-1}" "$lifecycle_state" "$chrome_pid" "$chrome_port" "$cdp_url" "$user_data_dir" "$active_lease_id" "$holder" "$last_error" <<'PYSTATE'
535
+ import json
536
+ import os
537
+ import sys
538
+ from datetime import datetime, timedelta, timezone
539
+ from pathlib import Path
540
+ state_file = Path(sys.argv[1])
541
+ worker_id, pool_id, max_processes, lifecycle_state = sys.argv[2:6]
542
+ pid, port, cdp_url, user_data_dir, lease_id, holder, last_error = sys.argv[6:13]
543
+ now = datetime.now(timezone.utc)
544
+ record = {
545
+ "workerId": worker_id,
546
+ "poolId": pool_id,
547
+ "maxBrowserProcesses": int(max_processes or "1"),
548
+ "state": lifecycle_state,
549
+ "lastUsedAt": now.isoformat(timespec="milliseconds").replace("+00:00", "Z"),
550
+ }
551
+ if pid:
552
+ record["pid"] = int(pid)
553
+ record["processGroupId"] = int(pid)
554
+ if port:
555
+ record["cdpPort"] = int(port)
556
+ if cdp_url:
557
+ record["cdpUrl"] = cdp_url
558
+ if user_data_dir:
559
+ record["userDataDir"] = user_data_dir
560
+ if lease_id:
561
+ record["activeLeaseId"] = lease_id
562
+ record["activeLeaseCount"] = 1
563
+ record["cleanupStatus"] = "not-attempted"
564
+ record["idleExpiresAt"] = (now + timedelta(milliseconds=int(os.environ.get("PIBO_BROWSER_POOL_IDLE_TIMEOUT_MS", "600000")))).isoformat(timespec="milliseconds").replace("+00:00", "Z")
565
+ else:
566
+ record["activeLeaseCount"] = 0
567
+ if holder:
568
+ record["holder"] = holder
569
+ if last_error:
570
+ record["lastError"] = last_error
571
+ state_file.parent.mkdir(parents=True, exist_ok=True)
572
+ tmp = state_file.with_name(f"{state_file.name}.{os.getpid()}.tmp")
573
+ tmp.write_text(json.dumps(record, indent=2) + "\\n")
574
+ tmp.replace(state_file)
575
+ PYSTATE
576
+ }
577
+
578
+ browser_pool_acquire() {
579
+ paths=$(browser_pool_paths)
580
+ state_file=$(printf '%s\n' "$paths" | sed -n '1p')
581
+ lock_dir=$(printf '%s\n' "$paths" | sed -n '2p')
582
+ mkdir -p "$(dirname "$state_file")"
583
+ safe_session=$(browser_pool_safe_segment "$session")
584
+ lease_id=\${PIBO_BROWSER_POOL_LEASE_ID:-browser-use:$safe_session}
585
+ holder=\${PIBO_BROWSER_POOL_HOLDER:-browser-use:$session}
586
+ attempts=0
587
+ while ! mkdir "$lock_dir" 2>/dev/null; do
588
+ attempts=$((attempts + 1))
589
+ if [ "$attempts" -ge "\${PIBO_BROWSER_POOL_LOCK_ATTEMPTS:-100}" ]; then
590
+ echo "pibo browser-use: managed browser-pool acquire timed out waiting for $lock_dir" >&2
591
+ return 1
592
+ fi
593
+ sleep 0.05
594
+ done
595
+ trap 'rm -rf "$lock_dir"' EXIT INT TERM
596
+
597
+ summary=$(browser_pool_read_summary "$state_file")
598
+ summary_state=$(printf '%s\n' "$summary" | sed -n '1p')
599
+ if [ "$summary_state" = "malformed" ]; then
600
+ malformed_reason=$(printf '%s\n' "$summary" | sed -n '2p')
601
+ browser_pool_write_state "$state_file" dirty "" "" "" "" "" "$holder" "Malformed browser pool state: $malformed_reason"
602
+ elif [ "$summary_state" != "missing" ]; then
603
+ recorded_state=$summary_state
604
+ recorded_pid=$(printf '%s\n' "$summary" | sed -n '2p')
605
+ recorded_port=$(printf '%s\n' "$summary" | sed -n '3p')
606
+ recorded_cdp_url=$(printf '%s\n' "$summary" | sed -n '4p')
607
+ recorded_user_data_dir=$(printf '%s\n' "$summary" | sed -n '5p')
608
+ recorded_active_lease_id=$(printf '%s\n' "$summary" | sed -n '6p')
609
+ recorded_idle_expires_at=$(printf '%s\n' "$summary" | sed -n '8p')
610
+ recorded_holder=$(printf '%s\n' "$summary" | sed -n '9p')
611
+ if [ "$recorded_state" = "leased" ] && browser_pool_lease_is_busy "$recorded_active_lease_id" "$lease_id" "$recorded_idle_expires_at" && [ -n "$recorded_pid" ] && [ -n "$recorded_port" ] && [ -n "$recorded_cdp_url" ] && kill -0 "$recorded_pid" 2>/dev/null && pibo_cdp_is_ready "$recorded_port"; then
612
+ busy_reason="pool-exhausted: browser pool $(browser_pool_id) is already leased by $recorded_active_lease_id until \${recorded_idle_expires_at:-unknown}"
613
+ echo "pibo browser-use: $busy_reason" >&2
614
+ rm -rf "$lock_dir"
615
+ trap - EXIT INT TERM
616
+ return 1
617
+ fi
618
+ if [ "$recorded_state" != "dirty" ] && [ -n "$recorded_pid" ] && [ -n "$recorded_port" ] && [ -n "$recorded_cdp_url" ] && kill -0 "$recorded_pid" 2>/dev/null && pibo_cdp_is_ready "$recorded_port"; then
619
+ browser_pool_write_state "$state_file" leased "$recorded_pid" "$recorded_port" "$recorded_cdp_url" "$recorded_user_data_dir" "$lease_id" "$holder" ""
620
+ rm -rf "$lock_dir"
621
+ trap - EXIT INT TERM
622
+ printf '%s\n' "$recorded_cdp_url"
623
+ return 0
624
+ fi
625
+ browser_pool_write_state "$state_file" stale "$recorded_pid" "$recorded_port" "$recorded_cdp_url" "$recorded_user_data_dir" "" "$holder" "Recorded browser is not alive or CDP is unreachable"
626
+ fi
627
+
628
+ start_result=$(start_persistent_chrome) || {
629
+ browser_pool_write_state "$state_file" dirty "" "" "" "" "" "$holder" "Managed browser start failed"
630
+ rm -rf "$lock_dir"
631
+ trap - EXIT INT TERM
632
+ return 1
633
+ }
634
+ chrome_pid=$(printf '%s\n' "$start_result" | sed -n '1p')
635
+ chrome_port=$(printf '%s\n' "$start_result" | sed -n '2p')
636
+ cdp_url=$(printf '%s\n' "$start_result" | sed -n '3p')
637
+ user_data_dir=$(printf '%s\n' "$start_result" | sed -n '4p')
638
+ browser_pool_write_state "$state_file" leased "$chrome_pid" "$chrome_port" "$cdp_url" "$user_data_dir" "$lease_id" "$holder" ""
639
+ rm -rf "$lock_dir"
640
+ trap - EXIT INT TERM
641
+ printf '%s\n' "$cdp_url"
642
+ }
643
+
644
+ ensure_persistent_chrome() {
645
+ if cdp_url=$(browser_pool_acquire); then
646
+ printf '%s\n' "$cdp_url"
647
+ return 0
648
+ fi
649
+ echo "pibo browser-use: managed browser-pool acquire failed; refusing to start an unmanaged browser." >&2
650
+ exit 1
651
+ }
652
+
653
+ while [ "$#" -gt 0 ]; do
654
+ arg=$1
655
+ shift
656
+
657
+ case "$arg" in
658
+ "$fresh_flag")
659
+ fresh_profile=1
660
+ continue
661
+ ;;
662
+ --help|-h)
663
+ show_help=1
664
+ ;;
665
+ --headed)
666
+ headed=1
667
+ ;;
668
+ --profile)
669
+ explicit_profile=1
670
+ append_arg "$arg"
671
+ continue
672
+ ;;
673
+ --cdp-url)
674
+ explicit_profile=1
675
+ append_arg "$arg"
676
+ if [ "$#" -gt 0 ]; then
677
+ append_arg "$1"
678
+ shift
679
+ fi
680
+ continue
681
+ ;;
682
+ --profile=*|--cdp-url=*|--connect|--mcp|--use-cloud|--cloud-profile-id|--cloud-profile-id=*)
683
+ explicit_profile=1
684
+ ;;
685
+ --session)
686
+ append_arg "$arg"
687
+ if [ "$#" -gt 0 ]; then
688
+ session=$1
689
+ append_arg "$1"
690
+ shift
691
+ fi
692
+ continue
693
+ ;;
694
+ --session=*)
695
+ session=\${arg#--session=}
696
+ ;;
697
+ --pibo-ensure-chrome)
698
+ pibo_ensure_chrome=1
699
+ continue
700
+ ;;
701
+ -*)
702
+ ;;
703
+ open|click|type|input|scroll|back|screenshot|state|switch|close-tab|keys|select|upload|eval|extract|hover|dblclick|rightclick|cookies|wait|get|python)
704
+ starts_browser=1
705
+ ;;
706
+ close|sessions|install|init|setup|doctor|cloud|profile)
707
+ ;;
708
+ esac
709
+
710
+ append_arg "$arg"
711
+ done
712
+
713
+ if [ "$pibo_ensure_chrome" -eq 1 ]; then
714
+ cdp_url=$(ensure_persistent_chrome)
715
+ printf '%s\\n' "$cdp_url"
716
+ exit 0
717
+ fi
718
+
719
+ if [ "$show_help" -eq 1 ]; then
720
+ {
721
+ echo "Pibo browser-use wrapper:"
722
+ echo " New browser-use daemon sessions default to persistent Chrome profile \\"$default_profile\\" via CDP."
723
+ echo " Use $fresh_flag to start a fresh temporary browser profile."
724
+ echo " Use --pibo-ensure-chrome to only start Chrome and print the CDP URL."
725
+ echo ""
726
+ } >&2
727
+ fi
728
+
729
+ set --
730
+ if [ -n "$sanitized_args" ]; then
731
+ while IFS= read -r item; do
732
+ set -- "$@" "$item"
733
+ done <<EOF
734
+ $sanitized_args
735
+ EOF
736
+ fi
737
+
738
+ # Prepend --session if the user did not pass it explicitly and a non-default session is set.
739
+ has_session_arg=0
740
+ for a in "$@"; do
741
+ case "$a" in
742
+ --session|--session=*) has_session_arg=1; break ;;
743
+ esac
744
+ done
745
+ if [ "$has_session_arg" -eq 0 ] && [ "$session" != "default" ]; then
746
+ set -- --session "$session" "$@"
747
+ fi
748
+
749
+ session_pid_file=\${BROWSER_USE_HOME:-$HOME/.browser-use}/$session.pid
750
+ session_alive=0
751
+ if [ -s "$session_pid_file" ]; then
752
+ session_pid=$(cat "$session_pid_file" 2>/dev/null || true)
753
+ if [ -n "$session_pid" ] && kill -0 "$session_pid" 2>/dev/null; then
754
+ session_alive=1
755
+ else
756
+ # Clean up stale session pid file
757
+ rm -f "$session_pid_file" 2>/dev/null || true
758
+ fi
759
+ fi
760
+
761
+ if [ "$starts_browser" -eq 1 ] && [ "$explicit_profile" -eq 0 ] && [ "$fresh_profile" -eq 0 ]; then
762
+ if [ "$session_alive" -eq 1 ]; then
763
+ "$real_browser_use" --session "$session" close >/dev/null 2>&1 || true
764
+ sleep 0.2
765
+ fi
766
+ cdp_url=$(ensure_persistent_chrome)
767
+ exec "$real_browser_use" --cdp-url "$cdp_url" "$@"
768
+ fi
769
+
770
+ exec "$real_browser_use" "$@"
771
771
  `;
772
772
  }
773
773
  export function ensureBrowserUseWrapper(status) {