Commit Graph

8766 Commits

Author SHA1 Message Date
ShawnPana a568f45979 feat(cli): add cloud task/session management and subagent support
Cloud API Integration:
- Add cloud_task.py with full Cloud API client (tasks, sessions)
- Support for task creation, status polling, stopping, and logs
- Session management: create, list, get, stop, delete

Agent Task Options (remote mode):
- --llm: specify model (gpt-4o, claude-sonnet-4-20250514, gemini-2.0-flash)
- --proxy-country: proxy location (default: us)
- --session-id: reuse existing cloud session
- --keep-alive: preserve session after task
- --no-wait: async execution, return task_id immediately
- --stream: stream status updates
- --flash: fast execution mode
- --thinking: extended reasoning
- --vision/--no-vision: control vision capability

CLI Commands:
- task list [--limit N] [--status running|finished] [--json]
- task status <task-id> [--json]
- task stop <task-id>
- task logs <task-id>
- session list [--limit N] [--status active|stopped] [--json]
- session get <session-id> [--json]
- session stop <session-id> | --all

Documentation:
- Add comprehensive subagent examples to SKILL.md files
- Document session=agent, task=work mental model
- Examples for parallel agents, session reuse, agent management
2026-02-10 12:33:05 -08:00
Saurav Panda a2337815c1 fix: remove double-counting of reasoning_tokens in OpenAI usage (#4075)
## Summary
Fixes #4065 - OpenAI reasoning tokens were being double-counted in
`_get_usage()`, inflating reported token counts by ~2x for all reasoning
models (o1, o3, o4-mini, etc.).

## Problem
The `_get_usage()` method was incorrectly adding `reasoning_tokens` to
`completion_tokens`:

```python
completion_tokens = response.usage.completion_tokens
if completion_token_details is not None:
    reasoning_tokens = completion_token_details.reasoning_tokens
    if reasoning_tokens is not None:
        completion_tokens += reasoning_tokens  # ← Double counting!
```

## Root Cause
According to OpenAI's API documentation, `completion_tokens` already
**includes** `reasoning_tokens` as a subset. This is explicitly stated
in the `CompletionTokensDetails` field description:

> reasoning tokens are "counted in the total completion tokens for
purposes of billing, output, and context window limits"

This differs from Google Gemini where `thinking_tokens` are reported
separately and need to be added.

## Solution
Removed the `reasoning_tokens` addition and used `completion_tokens`
directly, with a clarifying comment explaining the difference from
Gemini.

## Impact
- ✅ Fixes inflated cost calculations for users of o1, o1-pro, o3,
o3-mini, o3-pro, o4-mini
- ✅ Fixes inflated usage logging and telemetry  
- ✅ Aligns with other OpenAI-compatible providers in the codebase
(OpenRouter, Vercel, Cerebras, Groq)
- ✅ No behavioral impact (token counts aren't used for budget limits or
context window management)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes double-counting of OpenAI reasoning_tokens in _get_usage(), which
inflated completion token and cost numbers for reasoning models. Aligns
token reporting with OpenAI docs and fixes #4065.

- **Bug Fixes**
- Use response.usage.completion_tokens directly; remove added
reasoning_tokens.
- Corrects usage and cost logs for o1, o1-pro, o3, o3-mini, o3-pro,
o4-mini.

<sup>Written for commit 993a4c1f5b.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-02-10 11:56:29 -08:00
Chase Xu 993a4c1f5b fix: remove double-counting of reasoning_tokens in OpenAI usage
OpenAI's completion_tokens already includes reasoning_tokens as a subset,
so adding them again was incorrectly inflating token counts by ~2x for
all reasoning models (o1, o3, o4-mini, etc.).

This aligns with other OpenAI-compatible providers (OpenRouter, Vercel,
Cerebras, Groq) which use completion_tokens directly.

Note: This is different from Google Gemini where thinking_tokens ARE
reported separately and need to be added.

Fixes #4065
2026-02-10 05:32:13 -06:00
ShawnPana b2641ea3b7 fix(cli): redirect cloudflared stderr to file to prevent SIGPIPE
The pipe-based stderr was causing cloudflared to get SIGPIPE when
the parent Python process exited (pipe closed). Now stderr goes to
a log file at ~/.browser-use/tunnels/<port>.log which cloudflared
can write to indefinitely without blocking or dying.
2026-02-09 21:51:24 -08:00
ShawnPana 9420c90569 fix(cli): tunnel processes now survive CLI exit
- Spawn cloudflared as daemon with start_new_session=True
- Track tunnels via PID files in ~/.browser-use/tunnels/ instead of memory
- Properly add --all flag to tunnel argparser (fixes unrecognized args error)
- Clean up stale PID files when tunnel process has died

This fixes the issue where cloudflared would become a zombie/defunct process
immediately after browser-use tunnel returned, causing 530 errors.
2026-02-09 21:44:31 -08:00
ShawnPana be0803f5eb fix(install): add height to gum choose to show full list 2026-02-09 21:15:04 -08:00
ShawnPana aad00d0014 fix(install): correct gum tarball extraction path 2026-02-09 21:13:22 -08:00
ShawnPana ca96a51dc2 feat(install): add gum TUI with proper checkbox selection
- Auto-install gum for beautiful interactive prompts
- Checkbox-style mode selection with arrow keys + space to select
- Styled headers and instructions
- Falls back to basic bash menu if gum install fails
2026-02-09 21:08:01 -08:00
ShawnPana 56b2fd8e31 fix(install): use if statements to avoid set -e with pattern matching 2026-02-09 21:02:31 -08:00
ShawnPana ccdec4f33f fix(install): disable set -e around read for tty input 2026-02-09 20:59:53 -08:00
ShawnPana 38482b505e fix(install): use head instead of read to avoid set -e issues 2026-02-09 20:59:02 -08:00
ShawnPana 5242beabf9 fix(install): read from /dev/tty for interactive menu when piped 2026-02-09 20:58:05 -08:00
ShawnPana c0dd7875b8 feat(install): mode-based installation with interactive TUI
Add installation mode selection that determines which browser modes are
available at runtime. This enables lightweight remote-only installs for
sandboxed agents (skipping ~300MB Chromium download).

## Install Modes

- `--remote-only`: Only remote mode, installs cloudflared, skips Chromium
- `--local-only`: Only local modes (chromium/real), skips cloudflared
- `--full`: All modes available (default interactive selection)

## Runtime Behavior

- Single installed mode becomes the default (no --browser flag needed)
- Requesting unavailable mode shows clear error with reinstall instructions
- No config file = all modes available (backward compat for pip users)

## Changes

**New: browser_use/skill_cli/install_config.py**
- Manages ~/.browser-use/install-config.json
- get_config(), save_config(), is_mode_available(), get_default_mode()
- get_mode_unavailable_error() for helpful reinstall guidance

**Modified: browser_use/skill_cli/main.py**
- build_parser() uses config for --browser choices and default
- Validates mode availability before starting session server

**Modified: browser_use/skill_cli/sessions.py**
- create_browser_session() validates mode before creation

**Modified: install.sh**
- Command-line flags: --full, --remote-only, --local-only, --api-key
- Interactive TUI with fallback: gum → whiptail → pure bash menu
- Conditional dependency installation based on selected modes
- Writes install-config.json with installed modes

**Updated: skills/remote-browser/SKILL.md**
- Simplified examples (no --browser remote needed with --remote-only)
- Updated setup instructions for mode-based installation

**New: tests/ci/test_install_config.py**
- 13 tests covering config module
2026-02-09 20:36:08 -08:00
Chase Xu d96fff3816 fix: only disable proxy for localhost CDP requests
Address review feedback: trust_env=False now only applies to localhost/127.0.0.1/::1.
Remote CDP URLs will still respect HTTP_PROXY/HTTPS_PROXY environment variables.
2026-02-09 20:55:30 -06:00
Chase Xu 2bd5211eb0 fix: disable proxy for localhost CDP requests (#4050)
On Windows with HTTP_PROXY/HTTPS_PROXY env vars set, httpx respects
these for localhost requests, causing 502 responses when querying
the CDP /json/version endpoint.

Adding trust_env=False prevents httpx from using proxy env vars,
ensuring localhost CDP communication works regardless of system
proxy configuration.
2026-02-09 20:55:30 -06:00
ShawnPana 94c336407d fix(cli): make tunnel command independent of browser sessions
Previously, `browser-use tunnel 3000` would start a session server with
the default browser mode (chromium), which poisoned subsequent
`--browser remote` commands with a mode mismatch error.

Now tunnel commands are handled directly in main.py without starting a
browser session:

  npm run dev &
  browser-use tunnel 3000                          # No session created
  browser-use --browser remote open <tunnel-url>   # Works!

Changes:
- Move tunnel logic to standalone functions in tunnel_manager.py
- Handle tunnel commands in main.py before ensure_server()
- Tunnels persist across browser-use close (independent lifecycle)
- Add `browser-use tunnel stop --all` command

Breaking behavior change:
- `browser-use close` no longer stops tunnels
- Use `browser-use tunnel stop <port>` or `tunnel stop --all` explicitly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-09 18:38:39 -08:00
ShawnPana 9f51461bbd fix(cli): error when requesting remote browser on local session + improve docs
When a session is started without --browser remote and a subsequent command
uses --browser remote, the CLI now errors with guidance instead of silently
using the local browser (which loses cloud features like live_url).

Code changes:
- Add session metadata file to track browser_mode per session
- Error only when remote requested but session is local
- Allow local-on-remote (user gets more features than requested)
- Clean up metadata on session close

SKILL.md improvements:
- Clarify that --browser remote only needed on FIRST command
- Add table showing session modes
- Add "What happens if you forget" section with error example
- Add troubleshooting entry for mode mismatch error

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-09 18:20:06 -08:00
ShawnPana 3c9b2501ac fix(cli): detect browser mode mismatch between existing session and requested mode
When a session is started with --browser chromium and a subsequent command
uses --browser remote (or vice versa), the CLI now detects the mismatch and
provides actionable error guidance instead of silently using the wrong mode.

Changes:
- Add get_session_metadata_path() to track session config in temp files
- Write browser_mode, headed, profile to metadata on server start
- Check metadata in ensure_server() and error if modes don't match
- Clean up metadata file on session close

Example error:
  Error: Session 'default' is running with --browser chromium,
  but --browser remote was requested.

  Options:
    1. Use existing browser: browser-use --browser chromium <command>
    2. Use different session: browser-use --browser remote --session other <command>
    3. Close existing session: browser-use close

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-09 18:10:46 -08:00
Saurav Panda f653419870 Fixed structured output issue with simple judge (#4064)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Fixed JSON corruption in structured outputs by changing how the simple
judge records its note. When a schema is expected, the note is stored in
metadata instead of appended to extracted_content.

- **Bug Fixes**
- When output_model_schema is set, write the simple judge note to
result.metadata['simple_judge'] (initializing metadata if needed).
- Preserve previous behavior (append to extracted_content) when no
structured output is expected.

<sup>Written for commit 07817c2c5b.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-02-09 17:13:23 -08:00
Saurav Panda 07817c2c5b Fixed structured output issue with simple judge 2026-02-09 17:09:35 -08:00
ShawnPana f40235242a refactor: remove pycloudflared, install cloudflared directly in install.sh
- Remove pycloudflared dependency from pyproject.toml
- Simplify tunnel_manager.py to just use shutil.which('cloudflared')
- Add install_cloudflared() to install.sh that downloads binary from
  official Cloudflare GitHub releases based on platform/arch
- Update SKILL.md with manual cloudflared install instructions
- Update setup.py messages for missing cloudflared

install.sh supported platforms:
- Linux (x86_64, arm64) - full support, downloads binary to ~/.local/bin
- macOS (Intel, Apple Silicon) - full support, uses brew or direct download
- Windows WSL - works (treated as Linux)
- Native Windows - not supported (bash script), use manual install
2026-02-09 17:03:56 -08:00
ShawnPana 7e13c3a40b docs: add manual install option with correct GitHub URL for testing 2026-02-09 16:32:02 -08:00
ShawnPana b50845a564 fix: add ~/.local/bin to PATH for uv installation 2026-02-09 16:19:53 -08:00
ShawnPana 530b47a2ad fix: use correct GitHub repo in install.sh for dev testing 2026-02-09 16:15:13 -08:00
ShawnPana 1cb8ab2cf1 docs: update SKILL.md to use install.sh for setup 2026-02-09 16:11:48 -08:00
ShawnPana 9520a0b04e feat: frictionless installation system with setup/doctor commands
- Add TunnelManager for auto-install cloudflared via pycloudflared
- Add 'browser-use setup' command with profile-based configuration
- Add 'browser-use doctor' command for health checks
- Add install.sh bootstrap script (dev testing version)
- Add pycloudflared dependency
- Add comprehensive tests for new functionality
2026-02-09 16:09:38 -08:00
laithrw a8a9891704 fix: improve sensitive_data placeholder instructions for LLM clarity (#4048)
## Summary
Fixes #3970

The LLM was typing literal placeholder names (e.g., `user_name`) instead
of wrapping them in `<secret>` tags (e.g.,
`<secret>user_name</secret>`), causing the password/sensitive data
replacement mechanism to fail.

## Changes

Updated
[_get_sensitive_data_description()](cci:1://file:///Users/ashishkumarcheruku/Desktop/BrowserUse/browser_use/agent/message_manager/service.py:372:1-401:11)
in
[browser_use/agent/message_manager/service.py](cci:7://file:///Users/ashishkumarcheruku/Desktop/BrowserUse/browser_use/agent/message_manager/service.py:0:0-0:0):

- Added clear "SENSITIVE DATA" header to draw attention
- Changed placeholder list format from Python list representation to
bullet points
- Added explicit "MUST" instruction emphasizing the required format
- Added a concrete example using the first actual placeholder name
- Added explanation that the system will replace tags with actual values

## Before
Here are placeholders for sensitive data: ['user_name', 'pass_word'] To
use them, write the placeholder name

## After
SENSITIVE DATA - Use these placeholders for secure input:

pass_word
user_name
IMPORTANT: When entering sensitive values, you MUST wrap the placeholder
name in tags. Example: To enter the value for "pass_word", use:
pass_word The system will automatically replace these tags with the
actual secret values.


## Testing
- All 10 existing sensitive_data tests pass
(`tests/ci/security/test_sensitive_data.py`)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Clarifies sensitive-data guidance so placeholder names must be wrapped
in <secret> tags, preventing secret replacement failures and addressing
#3970.

- **Bug Fixes**
  - Added a "SENSITIVE DATA" header and bullet list of placeholders.
- Added a MUST instruction with an example to wrap names in <secret>
tags.
  - Explained that tags are auto-replaced with real secrets.
- Limited to _get_sensitive_data_description; synced with main, no
functional changes; all sensitive_data tests pass.

<sup>Written for commit f3ed2acb11.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-02-09 14:42:35 -05:00
laithrw f3ed2acb11 Merge branch 'main' into fix/sensitive-data-placeholder-instructions 2026-02-09 14:40:45 -05:00
laithrw 718213e34f Fix: Improve sensitive data replacement when LLM omits <secret> tags (#3971)
### Summary
This PR improves the robustness of sensitive data handling by allowing
the agent to recognize and replace placeholders even when the LLM
outputs them as literal strings without `<secret>` tags.

### Problem
In some cases (observed with certain LLMs), the model omits `<secret>`
tags and instead outputs the literal sensitive key name (e.g.
`user_name`, `pass_word`).
Previously, the library only scanned for tagged content, causing these
placeholder names to be typed into form fields instead of the actual
credentials.

### Solution
- Updated `Registry._replace_sensitive_data` in  
`browser_use/tools/registry/service.py` to detect **exact string
matches** against registered sensitive data keys.
- Added support for **2FA / TOTP generation** for these literal matches.
- Preserved full backward compatibility with the existing tag-based
replacement logic.

### Impact
This prevents failed automation and accidental exposure of placeholder
names when LLMs "forget" to include `<secret>` tags, making credential
handling more reliable across different models.

### Related issue
Closes #3970

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Replaces exact literal secret names when the LLM omits <secret> tags,
including generating TOTP for bu_2fa_code. Stops placeholder strings
from being typed into fields and fixes #3970.

<sup>Written for commit f469617aa7.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-02-09 14:40:30 -05:00
laithrw 7f3e26bef0 Merge branch 'main' into fix/sensitive-data-placeholder-instructions 2026-02-09 14:30:09 -05:00
laithrw f469617aa7 Merge branch 'main' into fix/issue-3970-browser-behavior 2026-02-09 14:28:52 -05:00
Gregor Žunič 9d7a0c74ed Add bu tunnel command and remote-browser skill
- New `tunnel` command: expose localhost ports via Cloudflare quick tunnels
  (`bu tunnel 3000` → trycloudflare.com URL, idempotent, auto-cleanup on session close)
- New `skills/remote-browser/SKILL.md`: guides sandboxed agents through
  cloud browser + tunnel workflow
- TunnelInfo dataclass on SessionInfo for tunnel lifecycle management
- Tunnel dispatch in both SessionServer and CLI arg parser

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 10:14:42 -08:00
laithrw c640ba5d43 Fix: consecutive_failures counter resetting on multi-action steps (#4059)
When the agent runs multiple actions per step, steps where all actions
fail weren't being counted as failures, so the failure limit could never
be reached. Now, any step ending in an error counts as a failure. May
help with #3615.

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes failure counting for multi-action steps so the agent stops after
hitting the failure limit. Any step whose last action errors now
increments consecutive_failures, preventing the counter from being
skipped on multi-action error steps.

- **Bug Fixes**
- Count a step as a failure when the last result has an error,
regardless of action count.
  - Prevents runaway loops where the failure limit was never reached.

<sup>Written for commit e1e170a5fd.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-02-08 17:35:25 -05:00
laithrw 9131c88f98 Merge branch 'main' into 3615 2026-02-08 17:34:07 -05:00
Laith Weinberger e1e170a5fd fix agent not stopping: consecutive_failures counter was resetting on multi-action error steps 2026-02-08 17:25:39 -05:00
laithrw aa44fc060a fix: redirect logging to stderr so MCP stdio doesn't break (#2748) (#4058)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Redirect all logs to stderr to keep MCP stdio clean and avoid protocol
breakage. Set env vars early and adjust imports to stop early logs.
Fixes #2748.

- **Bug Fixes**
- Route all logs (app, CDP, MCP server) to stderr; MCP server runs at
WARNING.
  - Print CLI addon warning to stderr.
- Set logging env vars before imports and reorder mcp/server imports to
prevent early logs.

<sup>Written for commit 53c1b0dfb9.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-02-08 16:41:17 -05:00
Laith Weinberger 53c1b0dfb9 Merge origin/issue-2748 into HEAD 2026-02-08 16:37:44 -05:00
Laith Weinberger 1737c5bea4 lint 2026-02-08 16:36:45 -05:00
laithrw 1724d314de Merge branch 'main' into issue-2748 2026-02-08 16:30:56 -05:00
Laith Weinberger 4d05218d7d fix: redirect logging to stderr so MCP stdio doesn't break (#2748) 2026-02-08 16:29:05 -05:00
laithrw af46bfe921 fix(llm): handle missing choices in openai proxy response (#3899)
Fixes: #3897
**The Problem**
When using browser-use with certain custom LLM proxies (configured via 
ChatOpenAI
), the agent would crash unexpectedly during the execution loop. The
error traceback pointed to
browser_use/llm/openai/chat.py
 with TypeError: 'NoneType' object is not subscriptable.

This happened because some proxies return a JSON response where the
choices field is either missing or explicitly set to None (likely due to
internal errors or non-standard formatting). The existing code assumed
response.choices always contained a valid list and attempted to access
response.choices[0] directly, causing the crash when the list was None.

**The Fix**
This PR modifies 
browser_use/llm/openai/chat.py
 to add robust validation for the LLM response structure.

**Identified the Failure Point:** Located the exact lines in 
ainvoke
where response.choices[0] was accessed for both standard string
completions and structured output.
Added Guard Clauses: Implemented an explicit check: if not
response.choices:.
Improved Error Handling: Instead of letting the application crash with a
confusing Python TypeError, the code now raises a
**ModelProviderErro**r
with a specific message: "Model response missing 'choices' field. This
can happen with incompatible proxies."
This change allows the agent to handle these malformed proxy responses
gracefully, providing users with a clear error message that points to
the proxy configuration rather than a bug in the library code.

**Verification/ testing** 
**Reproduction**: Created a reproduction script that mocked AsyncOpenAI
to return a ChatCompletion object with choices=None, successfully
replicating the TypeError.
**Validation**: Ran the same reproduction script with the fix applied;
confirmed that
ModelProviderError
 was raised with the correct message.
**Regression Testing**: Verified that standard OpenAI usage remains
unaffected.


<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fix crash when some OpenAI-compatible proxies return chat completions
without choices. We now validate choices in ainvoke and raise a clear
ModelProviderError (with base_url hint) instead of a TypeError;
structured-output calls no longer depend on schema injection.

- **Bug Fixes**
- Validate response.choices in both text and structured paths; if
missing/empty, raise ModelProviderError (502) with a base_url hint and
preserve status/message.
- Use the validated first choice for content and finish_reason; keep the
structured output parse check.
- Structured-output request runs even when add_schema_to_system_prompt
is false; removed a temporary debug regression test and reverted the
prior CI timeout change.

<sup>Written for commit 668f51ead9.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
2026-02-08 16:11:02 -05:00
Laith Weinberger 668f51ead9 chore: remove debug regression test
Drop tests/ci/test_openai_invalid_response.py from the PR; keep only the runtime fix in ChatOpenAI.
2026-02-08 16:07:17 -05:00
Laith Weinberger 1629799dd3 fix(openai): structured output call shouldn't depend on schema injection
Fix indentation so structured-output request executes even when add_schema_to_system_prompt is false; add regression tests for choices=null in both structured and non-structured paths.
2026-02-08 16:03:21 -05:00
laithrw d15f0a32c7 Merge branch 'main' into fix/3897-llm-response-parsing 2026-02-08 15:43:05 -05:00
Laith Weinberger 62d19f0c9a fix(openai): handle proxy responses with missing/null choices; undo test change
Avoid indexing response.choices[0] when proxies return choices=null/empty; raise a clear ModelProviderError (with base_url hint) and use the validated first choice for content/finish_reason
2026-02-08 15:37:59 -05:00
laithrw b7cf8cf0cd fix(#3895): stop agent if browser closed (#4056)
Resolves #3895, stopping the agent if the browser is gone (CDP client
reference gets set to none).
2026-02-08 11:18:58 -05:00
laithrw 880b636422 Merge branch 'main' into 3895 2026-02-08 11:15:44 -05:00
Laith Weinberger 9e95241bbb stop agent if browser closed 2026-02-08 11:14:49 -05:00
ashish-cheruku ab83ee7a8a fix: improve sensitive_data placeholder instructions for LLM clarity
Fixes #3970

The LLM was typing literal placeholder names (e.g., 'user_name') instead
of wrapping them in <secret> tags (e.g., '<secret>user_name</secret>').

Changes to _get_sensitive_data_description():
- Added clear 'SENSITIVE DATA' header to draw attention
- Changed placeholder list format from Python list to bullet points
- Added explicit 'MUST' instruction emphasizing the required format
- Added concrete example using the first actual placeholder name
- Added explanation that the system will replace tags with actual values

This makes it much clearer to the LLM that it needs to wrap placeholder
names in <secret> tags for the automatic replacement mechanism to work.
2026-02-06 18:10:14 +05:30
Saurav Panda c011d07e7e Update pyproject.toml (#4045)
<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Bumps package version from 0.11.8 to 0.11.9 in pyproject.toml to prepare
the next patch release.

<sup>Written for commit d795dbe0f8.
Summary will update on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
0.11.9
2026-02-05 21:03:24 -08:00