Compare commits

...

22 Commits

Author SHA1 Message Date
shivammittal274
8c62697a47 fix: remove unreachable CSS branch in recording waveform div 2026-03-17 18:27:53 +05:30
shivammittal274
b038d772e8 fix: keep mic button always visible inside input alongside send
Both mic and send buttons are always visible inside the input field,
positioned on the right side (ChatGPT-style). Mic is disabled while
AI is streaming. Send is disabled during recording/transcribing.
2026-03-17 18:16:29 +05:30
shivammittal274
c3229bd5f3 fix: keep mic button always visible alongside send button
Mic and send are now separate buttons, both always visible.
Mic is disabled while AI is streaming. Send is disabled during
recording/transcribing. Buttons are no longer absolutely positioned
inside the textarea — they sit beside it in the flex row.
2026-03-17 18:14:53 +05:30
shivammittal274
62f8107405 fix: analytics only tracks on success, clean up stream on failure, type API response
- startRecording returns boolean; track(RECORDING_STARTED) only fires on success
- Catch block cleans up MediaStream tracks and AudioContext on partial failure
- Type transcription API response with TranscribeResponse interface
2026-03-17 17:46:55 +05:30
shivammittal274
45da6526ab fix: await startRecording before tracking, narrow SurveyChat effect deps
- Await startRecording() so analytics only fires after mic permission granted
- Narrow SurveyChat useEffect dependency from [voice] to [voice.transcript, voice.isTranscribing]
2026-03-17 17:25:35 +05:30
shivammittal274
41e8461f01 fix: address review — add fetch timeout, await stopRecording, deduplicate VoiceInputState
- Add AbortSignal.timeout(30s) to transcription fetch
- Await stopRecording() and track analytics after completion
- Export VoiceInputState from useVoiceInput, import in consumers
2026-03-17 17:08:46 +05:30
shivammittal274
62440de783 feat: add voice input to agent chat sidebar
Allow users to record voice and transcribe to text in the chat input.
Mic button shows when input is empty, waveform visualizer during recording,
transcription via OpenAI (llm.browseros.com/api/transcribe).

- Extract shared useVoiceInput hook to lib/voice/
- Time-domain waveform bars that bounce per-frequency-band
- Bar height capped to fit input container
- Analytics events for recording lifecycle
2026-03-17 16:09:33 +05:30
shivammittal274
94e3f99adb feat: add test-ui skill for visual testing of agent extension via CDP (#464)
* feat: add CDP UI inspector script for dev self-testing

* fix: address code review feedback for inspect-ui script

- Use Delete key (not Backspace) to match server's keyboard.ts clearField
- Add windowId resolution to open-sidepanel (chrome.sidePanel.open requires it)
- Make target matching case-insensitive
- Replace process.exit(1) in eval with thrown error for proper cleanup
- Add comment referencing DEV_PORTS source of truth

* docs: add self-testing workflow for UI changes via CDP inspector

* fix: runtime fixes for inspect-ui discovered during live testing

- Remove Input.enable (domain has no enable method)
- Add DOM.getDocument before DOM operations (required by protocol)
- Use BrowserOS-specific sidePanel.browserosToggle API instead of
  standard chrome.sidePanel.open (side panel starts disabled)
- Enable side panel with setOptions before toggling

* feat: add test-ui skill for visual testing of agent extension UI

Adds a Claude Code skill that lets the agent visually test both
surfaces of the BrowserOS extension:
- New tab page (app.html) — left sidebar with Home, Scheduled Tasks,
  Settings, Skills, Memory, Soul, Connect Apps
- Right side panel (sidepanel.html) — chat interface

Includes all gotchas discovered through real testing: randomized ports,
fresh profile onboarding redirect, stale element IDs after navigation,
BrowserOS-specific sidePanel APIs, DOM.getDocument requirement.

* feat: add press_key, scroll, hover, select_option, wait_for to inspect-ui

Brings inspect-ui.ts to parity with server's MCP input tools:
- press_key: key combos like Enter, Control+A, Meta+Shift+P
  (ported from keyboard.ts pressCombo)
- scroll: up/down/left/right with configurable amount
- hover: hover over element by ID for tooltip/hover state testing
- select_option: select dropdown option by value or visible text
  (ported from browser.ts selectOption)
- wait_for: poll for text or CSS selector with 10s timeout

Updated skill documentation with new commands and examples.

* docs: prefer snapshot over screenshot, add holistic debugging guidance

- Add snapshot vs screenshot guidance table — prefer snapshot for
  structural checks, screenshot only for visual/layout verification
- Add server log checking instructions ([agent], [server], [build] tags)
- Add JS error checking via eval
- Add API connectivity verification
- Add common issues troubleshooting table
- Update all examples to use snapshot as default verification

* fix: address Greptile review feedback

- Replace process.exit(1) with process.exitCode + return in cmdWaitFor
  to allow async CDP cleanup in finally blocks
- Fix cmdScroll enabling Runtime instead of Page domain
- Add BROWSEROS_EXTENSION_ID env var override for extension ID
- Align CLAUDE.md dev server command with SKILL.md canonical command
2026-03-17 15:18:00 +05:30
Nikhil
e2069bc999 chore: bump server version (#459) 2026-03-16 16:42:54 -07:00
shivammittal274
2d51c82722 fix: detect custom clickable elements in take_snapshot (#452)
take_snapshot only used the AX tree, which misses custom components
(cursor:pointer divs, onclick handlers, etc.) that lack ARIA roles.
These elements appeared as role="generic" and were invisible to the agent.

Changes:
- Merge findCursorInteractiveElements into snapshot() so take_snapshot
  catches cursor:pointer, onclick, and tabindex elements
- Add DisclosureTriangle to INTERACTIVE_ROLES for <summary> elements
- Use aria-label as text fallback in cursor detection for icon-only buttons
- Fix dedup bug in enhancedSnapshot that was silently dropping all
  cursor-detected elements by checking against all AX node IDs instead
  of only already-included output IDs
2026-03-17 02:01:15 +05:30
shivammittal274
29056226bb feat: add eval framework and coordinate-based input tools (#453)
- Add hover_at, type_at, drag_at coordinate tools to server
- Add hoverAt, typeAt, dragAt methods to Browser class
- Export server internals (browser, tool-loop, registry) for eval imports
- Copy eval app from enterprise repo with agents, graders, runner, dashboard
- Nest eval-targets inside apps/eval
- Adapt sessionExecutionDir → workingDir for current server API
- Add biome ignore for dashboard HTML to prevent lint breaking onclick handlers
2026-03-16 23:12:23 +05:30
shivammittal274
d1d2074abc feat: add get_console_logs tool for browser console output (#454)
* feat: add get_console_logs tool to surface browser console output

Captures Runtime.consoleAPICalled, Runtime.exceptionThrown, and
Log.entryAdded CDP events per page with a FIFO ring buffer (500 entries).

- ConsoleCollector: per-page buffers with O(1) session routing via Map lookup
- Session-aware CDP event dispatching (onSessionEvent) in CdpBackend
- Log.enable() added alongside Runtime.enable() in attachToPage
- Single tool with level hierarchy, text search, limit, and clear params
- Buffer clears on main-frame navigation, cleaned up on page close

* fix: address review — handle session re-attach, remove dead code

- ConsoleCollector.attach() now updates session mapping on re-attach
  instead of early-returning, preventing silent event drops after
  target detach/re-attach (e.g. tab crash, cross-process navigation)
- Remove unused clearConsoleLogs() and ConsoleCollector.clear()
2026-03-16 22:20:40 +05:30
shivammittal274
41c9b1547c feat: add per-task LLM provider selection for scheduled tasks (#450)
* feat: add per-task LLM provider selection for scheduled tasks

Allow users to choose which AI provider a scheduled task runs with,
using the same ChatProviderSelector component from the new-tab page.
Falls back to the global default provider when none is selected or
if the selected provider has been deleted.

* fix: lint issues

* chore: updated to latest schema.graphql file

---------

Co-authored-by: Dani Akash <DaniAkash@users.noreply.github.com>
2026-03-16 18:03:21 +05:30
shivammittal274
8b0e6dbfd3 Merge pull request #448 from browseros-ai/fix/filter-empty-conversation-messages
fix: filter empty messages from conversation history
2026-03-16 13:30:42 +05:30
github-actions[bot]
07a2d13f16 docs: shivammittal274 signed the CLA in browseros-ai/BrowserOS#$pullRequestNo 2026-03-15 12:27:03 +00:00
shivammittal274
46031ed573 fix: filter empty messages from conversation history to prevent validation errors
The AI SDK can produce assistant messages with empty parts (parts:[]) when
a stream is aborted, and providers reject assistant messages with empty text
content. This adds a validation utility that filters both cases before
sending messages to createAgentUIStreamResponse and when persisting them.
2026-03-15 17:42:34 +05:30
Nikhil
ecd31efcb0 fix: remove Git LFS tracking for docs images so Mintlify can serve them (#446)
Mintlify deploys docs by cloning the repo but does not run `git lfs
pull`. The `.gitattributes` rule `docs/images/** filter=lfs` caused
all doc images to be stored as ~130-byte LFS pointer files, which
Mintlify served as-is — breaking every image on the site.

Removing the LFS rule and re-adding the files as regular git blobs
fixes all images without changing any paths or MDX files.

Also fixes broken Slack link placeholder in troubleshooting page.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 21:29:26 -07:00
Nikhil
c79c775fb8 fix: co-locate troubleshooting images to fix broken CDN rendering (#444)
Images in docs/images/ are served as broken 130-byte placeholders by
Mintlify CDN. Co-locating images with the MDX file (matching the
working pattern in features/workflow/ and features/cowork/) bypasses
this issue. Also fixes the Slack link placeholder.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 21:12:26 -07:00
Felarof
4bee76253d fix: prevent undefined provider in chat requests on fresh install (#442)
* fix: fallback to default BrowserOS provider when provider is null

When the extension first loads, provider config is loaded async from
storage. If a chat request fires before loading completes (race
condition), provider is null and the server receives provider: undefined,
causing a Zod validation error. This adds a fallback to
createDefaultBrowserOSProvider() in both chat paths (sidepanel and
scheduled tasks) so provider.type is always defined.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: fallback to first provider when default provider ID is stale

When defaultProviderId in storage doesn't match any loaded provider
(e.g. after Kimi/Moonshot rollout), selectedProvider was null causing
provider: undefined in chat requests. Now falls back to providers[0].

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: repair stale defaultProviderId in storage on load

When the stored default provider ID doesn't match any loaded provider,
write back the corrected ID (providers[0].id) to storage so it doesn't
silently persist across sessions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 09:05:27 -07:00
Felarof
5b1b4e22cb chore: disable Canva and Exa from Klavis MCP server list
Comment out non-working Canva and Exa integrations from the OAuth MCP
servers list and remove their imports/icon mappings from the UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 15:30:50 -07:00
Felarof
439acc8b12 feat: add worktrunk setup for browseros-agent development
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:17:08 -07:00
Felarof
95c855a091 feat: replace rate limit CTAs with Kimi/Moonshot partnership links (#437)
* feat: replace rate limit CTAs with Kimi/Moonshot partnership links

Comment out old "Learn more" and "take a quick survey" links on the
daily limit error banner. Replace with Kimi API key docs link and
direct Moonshot AI platform link for conversion tracking.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove partnership tagline from rate limit banner

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 12:45:41 -07:00
249 changed files with 57293 additions and 122 deletions

34
.config/README.md Normal file
View File

@@ -0,0 +1,34 @@
# Worktrunk Setup
This repo uses [Worktrunk](https://github.com/max-sixty/worktrunk) for running multiple Claude Code agents in parallel on different branches.
## Install Worktrunk
```bash
brew install max-sixty/worktrunk/wt
wt config shell install
# restart terminal
```
## Quick Commands
| Task | Command |
|------|---------|
| Create worktree + start Claude | `wt switch -c -x claude feat-name` |
| Switch to existing worktree | `wt switch feat-name` |
| List all worktrees | `wt list` |
| Create PR | `gh pr create` |
| Remove worktree | `wt remove feat-name` |
## What happens on `wt switch -c`
1. Creates new worktree at `../browseros-main.feat-name/`
2. Runs `bun install` in `packages/browseros-agent/`
3. Copies `.env.*` files from main worktree's `packages/browseros-agent/apps/`
## Hooks
Hooks are configured in `.config/wt.toml`:
- **post-create**: Runs `bun install` in the agent package, copies env files and `.llm/` from the main worktree
- **pre-remove**: Syncs `.llm/` back to the main worktree before deletion

View File

@@ -1,4 +1,6 @@
[post-create]
install = "cd packages/browseros-agent && bun install"
env = "for f in {{ repo_root }}/packages/browseros-agent/apps/*/.env.*; do [ -f \"$f\" ] && cp \"$f\" \"${f#{{ repo_root }}/}\"; done 2>/dev/null || true"
llm = "cp -r {{ repo_root }}/.llm . 2>/dev/null || true"
[pre-remove]

1
.gitattributes vendored
View File

@@ -9,5 +9,4 @@ packages/browseros/chromium_patches/**/*.py linguist-generated
scripts/*.py linguist-generated
# Mark build directories as generated
build/* linguist-generated
docs/images/** filter=lfs diff=lfs merge=lfs -text
docs/videos/** filter=lfs diff=lfs merge=lfs -text

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 843 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 892 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 702 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 604 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 948 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@@ -6,7 +6,7 @@ description: "How to fix connection issues when the BrowserOS agent is not respo
If you see **"Failed to Fetch"** or **"Unable to connect to BrowserOS agent"**, your agent has been killed.
<Frame>
<img src="/images/troubleshooting/failed-to-fetch-error.png" alt="Failed to fetch error" />
<img src="/troubleshooting/failed-to-fetch-error.png" alt="Failed to fetch error" />
</Frame>
## Windows: Did you allow the BrowserOS Agent?
@@ -14,7 +14,7 @@ If you see **"Failed to Fetch"** or **"Unable to connect to BrowserOS agent"**,
When BrowserOS starts for the first time on Windows, a popup will ask if you want to allow **BrowserOS Agent** to access the network. Click **Allow** — the agent runs as a separate process alongside the browser and needs this permission to work. If you clicked **Cancel** or missed this prompt, the agent won't be able to connect.
<Frame>
<img src="/images/windows-installation/windows-installation-allow-browseros-agent.png" alt="Allow BrowserOS Agent network access prompt" />
<img src="/troubleshooting/windows-installation-allow-browseros-agent.png" alt="Allow BrowserOS Agent network access prompt" />
</Frame>
To fix this, go to **Windows Security → Allow an app through your network settings**, and make sure **BrowserOS Agent** is enabled.
@@ -26,7 +26,7 @@ To fix this, go to **Windows Security → Allow an app through your network sett
Open **Settings** from the sidebar, scroll to the MCP Server section, and click **Restart**. Wait a couple of minutes.
<Frame>
<img src="/images/troubleshooting/mcp-server-restart.png" alt="Click Restart in MCP Server Settings" />
<img src="/troubleshooting/mcp-server-restart.png" alt="Click Restart in MCP Server Settings" />
</Frame>
</Step>
@@ -39,7 +39,7 @@ To fix this, go to **Windows Security → Allow an app through your network sett
4. Reopen **BrowserOS** — the agent will restart automatically.
<Frame>
<img src="/images/windows-installation/troubleshoot-search-restart-agent-task-manager.png" alt="Search for BrowserOS Agent in Task Manager and restart it" />
<img src="/troubleshooting/troubleshoot-search-restart-agent-task-manager.png" alt="Search for BrowserOS Agent in Task Manager and restart it" />
</Frame>
<Note>If the agent still doesn't reconnect, end both **BrowserOS** and **BrowserOS Agent** in Task Manager, then reopen the app.</Note>
@@ -100,7 +100,7 @@ If you're still experiencing issues, reach out:
<Card title="Discord" icon="discord" href="https://discord.gg/YKwjt5vuKr">
Get help from the community
</Card>
<Card title="Slack" icon="slack" href="SLACK_LINK_HERE">
<Card title="Slack" icon="slack" href="https://dub.sh/browserOS-slack">
Join our Slack
</Card>
</CardGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,286 @@
---
name: test-ui
description: Test the BrowserOS agent extension UI by starting the dev environment and visually verifying changes via CDP. Covers the new tab page (left sidebar — Home, Scheduled Tasks, Settings, etc.) and the right side panel (chat interface). Use after making UI changes to apps/agent/.
argument-hint: [what to test, e.g. "verify the new settings page renders correctly"]
---
# Test Agent UI
Visually test the BrowserOS agent extension UI — both the new tab page (left sidebar) and the right side panel (chat) — by starting the dev environment and inspecting via CDP.
## When to use
After making code changes to `apps/agent/` (the Chrome extension), use this skill to:
- Verify new UI components render correctly
- Check navigation between views works
- Confirm layout/styling changes look right
- Test interactive elements (buttons, inputs, forms)
## Prerequisites
- **Go** must be installed (`brew install go`) — the dev tool is written in Go
- **BrowserOS.app** must be installed at `/Applications/BrowserOS.app/`
- The `scripts/dev/inspect-ui.ts` utility must exist (CDP inspector script)
## Step 1: Start the dev environment
```bash
bun run dev:watch -- --new
```
This single command handles everything:
- Builds the Go dev CLI tool
- Picks random available ports (avoids conflicts)
- Creates a fresh browser profile
- Builds controller-ext
- Runs GraphQL codegen if `apps/agent/generated/graphql/` doesn't exist
- Starts the agent extension with WXT HMR (hot module replacement)
- Waits for CDP to be ready
- Starts the MCP server
Run it in the background and **read the output to find the CDP port**:
```
[info] Ports: CDP=9552 Server=9065 Extension=9929
```
The CDP port is randomized. You MUST extract it from the output and set it for all subsequent commands:
```bash
export BROWSEROS_CDP_PORT=<port from output>
```
Wait for these messages before proceeding:
1. `[server] CDP ready`
2. `[server] HTTP server listening`
## Step 2: Discover targets
```bash
bun scripts/dev/inspect-ui.ts targets
```
You will see targets like:
- `[service_worker]` — extension background scripts (not directly testable for UI)
- `[page] chrome-extension://bflpfmnmnokmjhmgnolecpppdbdophmk/app.html#/...`**New tab page (left sidebar)**
- `[page] sidepanel.html`**Right side panel (chat)**
The two main testable surfaces:
- **`app.html`** — the new tab page with left sidebar (Home, Connect Apps, Scheduled Tasks, Skills, Memory, Soul, Settings)
- **`sidepanel.html`** — the right side panel chat interface
## Step 3: Navigate to the main UI
A fresh profile opens the **onboarding page** (`app.html#/onboarding`). Navigate to the home page first:
```bash
bun scripts/dev/inspect-ui.ts eval app.html "window.location.hash = '#/home'"
```
Verify with a snapshot (not screenshot — snapshot is faster and sufficient for structural checks):
```bash
bun scripts/dev/inspect-ui.ts snapshot app.html
```
## Snapshot vs Screenshot
**Prefer `snapshot` for most checks** — it's fast, text-based, and tells you what elements exist, their text, and their IDs. Use it after every navigation or interaction to verify state.
**Use `screenshot` only when you need visual verification** — layout changes, CSS/styling, colors, images, or a final "does it look right" check. Screenshots are expensive (capture → save → read image).
| Check | Use |
|-------|-----|
| Did the page navigate? | `snapshot` — look for new elements |
| Does my new component render? | `snapshot` — look for its text/role |
| Did a click change state? | `snapshot` — check element names/values |
| Is the layout correct? | `screenshot` — visual check needed |
| Do CSS changes look right? | `screenshot` — visual check needed |
| Final verification before committing | `screenshot` — one visual confirmation |
## Step 4: Test the new tab page (left sidebar)
### Get element IDs
```bash
bun scripts/dev/inspect-ui.ts snapshot app.html
```
Output shows interactive elements with IDs:
```
[52] link "Home"
[57] link "Connect Apps"
[65] link "Scheduled Tasks"
[74] link "Skills"
[103] link "Settings"
```
### Navigate via click or hash routing
**Click-based** (use element IDs from snapshot):
```bash
bun scripts/dev/inspect-ui.ts click app.html 65 # Click "Scheduled Tasks"
```
**Hash routing** (faster, no snapshot needed):
```bash
bun scripts/dev/inspect-ui.ts eval app.html "window.location.hash = '#/settings'"
bun scripts/dev/inspect-ui.ts eval app.html "window.location.hash = '#/scheduled-tasks'"
bun scripts/dev/inspect-ui.ts eval app.html "window.location.hash = '#/home'"
```
### Verify navigation
```bash
# Snapshot to confirm the page changed (fast, preferred)
bun scripts/dev/inspect-ui.ts snapshot app.html
# Screenshot only if you need to check visual layout
bun scripts/dev/inspect-ui.ts screenshot app.html /tmp/settings.png
```
### CRITICAL: Re-snapshot after every navigation
React re-renders change element IDs. **Always run snapshot again** before clicking/filling after navigating to a new view. Using stale IDs will fail.
## Step 5: Open and test the right side panel
The side panel starts **disabled** in a fresh profile. Open it using BrowserOS-specific APIs:
```bash
bun scripts/dev/inspect-ui.ts open-sidepanel
```
Wait 2 seconds for it to appear as a target, then:
```bash
bun scripts/dev/inspect-ui.ts screenshot sidepanel /tmp/panel.png
bun scripts/dev/inspect-ui.ts snapshot sidepanel
```
### Interact with the side panel
```bash
# Get element IDs
bun scripts/dev/inspect-ui.ts snapshot sidepanel
# Output: [37] textbox "What should I do?"
# [124] button "Send"
# [60] link "Chat history"
# [99] button "Agent Mode ON"
# Fill the chat input and press Enter to send
bun scripts/dev/inspect-ui.ts fill sidepanel 37 "Hello world"
bun scripts/dev/inspect-ui.ts press_key sidepanel Enter
# Or click the Send button
bun scripts/dev/inspect-ui.ts click sidepanel 124
# Wait for a response to appear
bun scripts/dev/inspect-ui.ts wait_for sidepanel text "response text"
# Scroll down to see more content
bun scripts/dev/inspect-ui.ts scroll sidepanel down 3
# Hover over an element to test hover states
bun scripts/dev/inspect-ui.ts hover sidepanel 99
# Snapshot to verify state changed (fast, preferred)
bun scripts/dev/inspect-ui.ts snapshot sidepanel
# Screenshot only for visual/layout verification
bun scripts/dev/inspect-ui.ts screenshot sidepanel /tmp/result.png
```
## Step 6: Verify and iterate
### The core loop
```
snapshot → identify element IDs → click/fill/press_key → snapshot → verify
```
Use `screenshot` only when visual layout verification is needed (CSS changes, final check).
### After making code changes
1. Fix the code in `apps/agent/`
2. WXT HMR will hot-reload the extension automatically (watch mode)
3. Wait 2-3 seconds for the reload to complete
4. **Re-snapshot** — element IDs WILL change after HMR reload
5. Verify the fix with snapshot (or screenshot if visual)
### Check server logs
The dev server output (running in background) contains useful diagnostics:
- `[agent]` — WXT build/HMR status, compilation errors
- `[server]` — MCP server logs, tool execution, errors
- `[build]` — Extension build output
If the UI isn't rendering, check for build errors in the `[agent]` output.
### Check for JavaScript errors
```bash
bun scripts/dev/inspect-ui.ts eval sidepanel "JSON.stringify(window.__errors || 'no errors')"
```
Or check the console for React errors:
```bash
bun scripts/dev/inspect-ui.ts eval app.html "document.querySelector('#root')?.innerHTML?.substring(0, 200)"
```
### Verify API connectivity
The extension talks to the MCP server. Verify the server is reachable:
```bash
bun scripts/dev/inspect-ui.ts eval sidepanel "fetch('http://127.0.0.1:<serverPort>/health').then(r => r.ok).catch(() => false)"
```
### Common issues
| Symptom | Cause | Fix |
|---------|-------|-----|
| Blank page after navigation | React render error | Check `eval` for JS errors |
| Element IDs don't match | Page re-rendered (HMR/navigation) | Re-run `snapshot` before interacting |
| `open-sidepanel` fails | Extension not fully loaded | Wait longer after dev server starts |
| Click does nothing | Element not visible (below fold) | Use `scroll` first, then re-snapshot |
| `wait_for` times out | Content hasn't loaded yet | Check server logs for API errors |
## Available commands reference
| Command | Description |
|---------|-------------|
| `targets` | List all CDP targets, marks extension pages with `[EXTENSION]` |
| `screenshot <target> [file]` | Capture PNG screenshot (default: `screenshot.png`) |
| `snapshot <target>` | Print accessibility tree with `[elementId] role "name"` |
| `click <target> <elementId>` | Click element by ID (3-tier coordinate fallback + JS click) |
| `fill <target> <elementId> <text>` | Focus element, clear, type text |
| `press_key <target> <key>` | Press key or combo: `Enter`, `Escape`, `Tab`, `Control+A`, `Meta+Shift+P` |
| `scroll <target> <dir> [amount]` | Scroll `up`/`down`/`left`/`right`, amount in ticks (default 3) |
| `hover <target> <elementId>` | Hover over element (for tooltips, hover states) |
| `select_option <target> <id> <val>` | Select dropdown option by value or visible text |
| `wait_for <target> text\|selector <v>` | Wait up to 10s for text or CSS selector to appear |
| `eval <target> <expression>` | Run JavaScript in the target's context |
| `open-sidepanel` | Enable and open the right side panel |
`<target>` is a URL substring (e.g., `sidepanel`, `app.html`) or numeric index from `targets` output.
## Known app.html routes
These can be used with `eval app.html "window.location.hash = '#/<route>'"`:
| Route | View |
|-------|------|
| `/home` | Home page with search bar and top sites |
| `/settings` | Settings (LLM providers, customization, workflows, MCP) |
| `/scheduled-tasks` | Scheduled Tasks management |
| `/onboarding` | Onboarding flow (first-run experience) |
## Gotchas learned from real testing
1. **Ports are randomized** with `--new` — always extract from dev server output
2. **Fresh profile = onboarding page** — navigate to `#/home` to see the main UI
3. **Element IDs change after navigation** — always re-snapshot before clicking
4. **Side panel starts disabled**`open-sidepanel` handles the BrowserOS-specific enable + toggle API
5. **`Input.enable` does not exist** — the CDP Input domain has no enable method (already handled in the script)
6. **`DOM.getDocument` required** — must be called before DOM operations like `pushNodesByBackendIdsToFrontend` (already handled in the script)
7. **Settings sub-navigation** — the settings page has its own left sidebar (BrowserOS AI, Chat & Council Provider, Search Provider, Customize BrowserOS, BrowserOS as MCP, Workflows) — use snapshot + click to navigate within settings

View File

@@ -165,3 +165,68 @@ Tests are in `apps/server/tests/`:
- `agent/` - Agent tests (compaction, rate limiter)
- `sdk/` - Agent SDK tests
- `__helpers__/` - Test utilities and fixtures
## Self-Testing UI Changes
After making UI changes to the agent extension (`apps/agent/`), you can visually verify them using the CDP inspector script. This connects directly to the browser via Chrome DevTools Protocol and can inspect extension pages (side panel, new tab, etc.) that the agent's own tools cannot see.
### Prerequisites
The dev server must be running:
```bash
bun run dev:watch -- --new
```
Read the output to find the randomized CDP port, then:
```bash
export BROWSEROS_CDP_PORT=<port from output>
```
### Workflow
1. **List all targets** to see what's available:
```bash
bun scripts/dev/inspect-ui.ts targets
```
2. **Open the side panel** if it's not already open:
```bash
bun scripts/dev/inspect-ui.ts open-sidepanel
```
3. **Take a screenshot** of the side panel:
```bash
bun scripts/dev/inspect-ui.ts screenshot sidepanel /tmp/panel.png
```
Then read `/tmp/panel.png` to view the result.
4. **Get the accessibility tree** for structural verification:
```bash
bun scripts/dev/inspect-ui.ts snapshot sidepanel
```
5. **Click an element** by its ID from the snapshot:
```bash
bun scripts/dev/inspect-ui.ts click sidepanel 142
```
6. **Fill a text input** by its ID from the snapshot:
```bash
bun scripts/dev/inspect-ui.ts fill sidepanel 85 "search query"
```
7. **Evaluate JavaScript** in the extension context:
```bash
bun scripts/dev/inspect-ui.ts eval sidepanel "document.title"
```
### Interaction workflow
The typical loop is: snapshot → identify element IDs → click/fill → screenshot to verify.
Element IDs come from the `[number]` in snapshot output (these are `backendDOMNodeId` values).
This uses the same element resolution as the server's MCP tools — no coordinate guessing.
### Target selection
The `<target>` argument can be:
- An **index** from the `targets` output (e.g., `3`)
- A **URL substring** (e.g., `sidepanel`, `newtab`, `chrome-extension://`)

View File

@@ -5,13 +5,11 @@ import AsanaSvg from '@/assets/mcp-icons/asana.svg'
import BoxSvg from '@/assets/mcp-icons/box.svg'
import BraveSearchSvg from '@/assets/mcp-icons/brave_search.svg'
import CalComSvg from '@/assets/mcp-icons/cal_com.svg'
import CanvaSvg from '@/assets/mcp-icons/canva.svg'
import ClickUpSvg from '@/assets/mcp-icons/clickup.svg'
import CloudflareSvg from '@/assets/mcp-icons/cloudflare.svg'
import ConfluenceSvg from '@/assets/mcp-icons/confluence.svg'
import DiscordSvg from '@/assets/mcp-icons/discord.svg'
import DropboxSvg from '@/assets/mcp-icons/dropbox.svg'
import ExaPng from '@/assets/mcp-icons/exa.png'
import FigmaSvg from '@/assets/mcp-icons/figma.svg'
import GithubSvg from '@/assets/mcp-icons/github.svg'
import GitlabSvg from '@/assets/mcp-icons/gitlab.svg'
@@ -64,7 +62,6 @@ const mcpIconMap: Record<string, string> = {
Linear: LinearSvg,
Jira: JiraSvg,
Figma: FigmaSvg,
Canva: CanvaSvg,
Salesforce: SalesforceSvg,
ClickUp: ClickUpSvg,
Asana: AsanaSvg,
@@ -79,7 +76,6 @@ const mcpIconMap: Record<string, string> = {
Cloudflare: CloudflareSvg,
'Brave Search': BraveSearchSvg,
Mem0: Mem0Webp,
Exa: ExaPng,
Dropbox: DropboxSvg,
OneDrive: OneDriveSvg,
WordPress: WordPressSvg,

View File

@@ -4,8 +4,8 @@ import { MessageResponse } from '@/components/ai-elements/message'
import { Button } from '@/components/ui/button'
import { Textarea } from '@/components/ui/textarea'
import { cn } from '@/lib/utils'
import { useVoiceInput } from '@/lib/voice/useVoiceInput'
import type { Message } from './useSurveyChat'
import { useVoiceInput } from './useVoiceInput'
import { VoiceInputButton } from './VoiceInputButton'
interface Props {
@@ -81,6 +81,7 @@ export const Chat: FC<Props> = ({
}, [messagesLength])
// Insert transcript into input when transcription completes
// biome-ignore lint/correctness/useExhaustiveDependencies: only trigger on transcript/transcribing change
useEffect(() => {
if (voice.transcript && !voice.isTranscribing) {
setInput((prev) => {
@@ -89,7 +90,7 @@ export const Chat: FC<Props> = ({
})
voice.clearTranscript()
}
}, [voice])
}, [voice.transcript, voice.isTranscribing])
const handleSubmit = (e: FormEvent) => {
e.preventDefault()

View File

@@ -1,8 +1,11 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { ChevronDown } from 'lucide-react'
import type { FC } from 'react'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import { z } from 'zod/v3'
import { ChatProviderSelector } from '@/components/chat/ChatProviderSelector'
import type { Provider } from '@/components/chat/chatComponentTypes'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import {
@@ -31,6 +34,12 @@ import {
SelectValue,
} from '@/components/ui/select'
import { Textarea } from '@/components/ui/textarea'
import { BrowserOSIcon, ProviderIcon } from '@/lib/llm-providers/providerIcons'
import {
defaultProviderIdStorage,
providersStorage,
} from '@/lib/llm-providers/storage'
import type { LlmProviderConfig, ProviderType } from '@/lib/llm-providers/types'
import type { ScheduledJob } from './types'
const formSchema = z
@@ -43,6 +52,7 @@ const formSchema = z
scheduleType: z.enum(['daily', 'hourly', 'minutes']),
scheduleTime: z.string().optional(),
scheduleInterval: z.number().int().min(1).max(60).optional(),
providerId: z.string().optional(),
enabled: z.boolean(),
})
.superRefine((data, ctx) => {
@@ -81,6 +91,8 @@ export const NewScheduledTaskDialog: FC<NewScheduledTaskDialogProps> = ({
onSave,
}) => {
const isEditing = !!initialValues
const [providers, setProviders] = useState<LlmProviderConfig[]>([])
const [defaultProviderId, setDefaultProviderId] = useState<string>('')
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
@@ -90,11 +102,25 @@ export const NewScheduledTaskDialog: FC<NewScheduledTaskDialogProps> = ({
scheduleType: 'daily',
scheduleTime: '09:00',
scheduleInterval: 1,
providerId: undefined,
enabled: true,
},
})
const scheduleType = form.watch('scheduleType')
const selectedProviderId = form.watch('providerId')
// Load providers from storage
useEffect(() => {
if (!open) return
Promise.all([
providersStorage.getValue(),
defaultProviderIdStorage.getValue(),
]).then(([providerList, defId]) => {
setProviders(providerList ?? [])
setDefaultProviderId(defId ?? '')
})
}, [open])
useEffect(() => {
if (open) {
@@ -105,6 +131,7 @@ export const NewScheduledTaskDialog: FC<NewScheduledTaskDialogProps> = ({
scheduleType: initialValues.scheduleType,
scheduleTime: initialValues.scheduleTime || '09:00',
scheduleInterval: initialValues.scheduleInterval || 1,
providerId: initialValues.providerId,
enabled: initialValues.enabled,
})
} else {
@@ -114,12 +141,33 @@ export const NewScheduledTaskDialog: FC<NewScheduledTaskDialogProps> = ({
scheduleType: 'daily',
scheduleTime: '09:00',
scheduleInterval: 1,
providerId: undefined,
enabled: true,
})
}
}
}, [open, initialValues, form])
// Resolve the currently selected provider for the selector display
const resolvedProvider: Provider | null = (() => {
const id = selectedProviderId ?? defaultProviderId
const found = providers.find((p) => p.id === id)
if (found) return { id: found.id, name: found.name, type: found.type }
if (providers[0])
return {
id: providers[0].id,
name: providers[0].name,
type: providers[0].type,
}
return null
})()
const providerOptions: Provider[] = providers.map((p) => ({
id: p.id,
name: p.name,
type: p.type,
}))
const onSubmit = (values: FormValues) => {
onSave({
name: values.name.trim(),
@@ -129,6 +177,7 @@ export const NewScheduledTaskDialog: FC<NewScheduledTaskDialogProps> = ({
values.scheduleType === 'daily' ? values.scheduleTime : undefined,
scheduleInterval:
values.scheduleType !== 'daily' ? values.scheduleInterval : undefined,
providerId: values.providerId,
enabled: values.enabled,
})
form.reset()
@@ -185,6 +234,43 @@ export const NewScheduledTaskDialog: FC<NewScheduledTaskDialogProps> = ({
)}
/>
{providers.length > 0 && resolvedProvider && (
<FormItem>
<FormLabel>AI Provider</FormLabel>
<ChatProviderSelector
providers={providerOptions}
selectedProvider={resolvedProvider}
onSelectProvider={(provider) =>
form.setValue('providerId', provider.id)
}
>
<Button
type="button"
variant="outline"
className="w-full justify-between"
>
<span className="flex items-center gap-2">
<span className="text-muted-foreground">
{resolvedProvider.type === 'browseros' ? (
<BrowserOSIcon size={16} />
) : (
<ProviderIcon
type={resolvedProvider.type as ProviderType}
size={16}
/>
)}
</span>
{resolvedProvider.name}
</span>
<ChevronDown className="h-4 w-4 opacity-50" />
</Button>
</ChatProviderSelector>
<FormDescription>
The AI provider used to run this task
</FormDescription>
</FormItem>
)}
<div className="grid gap-4 sm:grid-cols-2">
<FormField
control={form.control}

View File

@@ -12,7 +12,7 @@ import {
Trash2,
XCircle,
} from 'lucide-react'
import { type FC, useMemo, useState } from 'react'
import { type FC, useEffect, useMemo, useState } from 'react'
import { Button } from '@/components/ui/button'
import {
Collapsible,
@@ -20,6 +20,9 @@ import {
CollapsibleTrigger,
} from '@/components/ui/collapsible'
import { Switch } from '@/components/ui/switch'
import { BrowserOSIcon, ProviderIcon } from '@/lib/llm-providers/providerIcons'
import { providersStorage } from '@/lib/llm-providers/storage'
import type { ProviderType } from '@/lib/llm-providers/types'
import { useScheduledJobRuns } from '@/lib/schedules/scheduleStorage'
import type { ScheduledJob, ScheduledJobRun } from './types'
@@ -80,9 +83,25 @@ export const ScheduledTaskCard: FC<ScheduledTaskCardProps> = ({
onRetryRun,
}) => {
const [isOpen, setIsOpen] = useState(false)
const [providerInfo, setProviderInfo] = useState<{
name: string
type: ProviderType
} | null>(null)
const { jobRuns } = useScheduledJobRuns()
// Load provider info for display
useEffect(() => {
if (!job.providerId) {
setProviderInfo(null)
return
}
providersStorage.getValue().then((providers) => {
const match = providers?.find((p) => p.id === job.providerId)
setProviderInfo(match ? { name: match.name, type: match.type } : null)
})
}, [job.providerId])
const runs = useMemo(
() =>
jobRuns
@@ -117,6 +136,19 @@ export const ScheduledTaskCard: FC<ScheduledTaskCardProps> = ({
</p>
<div className="flex items-center gap-2 text-muted-foreground text-xs">
<span>{formatSchedule(job)}</span>
{providerInfo && (
<>
<span></span>
<span className="flex items-center gap-1">
{providerInfo.type === 'browseros' ? (
<BrowserOSIcon size={12} />
) : (
<ProviderIcon type={providerInfo.type} size={12} />
)}
{providerInfo.name}
</span>
</>
)}
{job.lastRunAt && (
<>
<span></span>

View File

@@ -117,6 +117,7 @@ export const scheduledJobRuns = async () => {
const response = await getChatServerResponse({
message: job.query,
signal: abortController.signal,
providerId: job.providerId,
})
await updateJobRun(jobRun.id, {

Some files were not shown because too many files have changed in this diff Show More