mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-14 08:03:58 +00:00
Compare commits
12 Commits
fix/browse
...
fix/eval-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
950071aecf | ||
|
|
cafed57832 | ||
|
|
f157436e7d | ||
|
|
ba7892322b | ||
|
|
4e90b4561a | ||
|
|
86eed82350 | ||
|
|
be6ed22af4 | ||
|
|
149cde118d | ||
|
|
9bc5e666c4 | ||
|
|
2271277b4d | ||
|
|
f865d301a2 | ||
|
|
6f398f0b36 |
91
.github/workflows/eval-weekly.yml
vendored
Normal file
91
.github/workflows/eval-weekly.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
name: Weekly Eval
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Every Saturday at 06:00 UTC
|
||||
- cron: '0 6 * * 6'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
config:
|
||||
description: 'Eval config file (relative to apps/eval/)'
|
||||
required: false
|
||||
default: 'configs/browseros-agent-weekly.json'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
eval:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 360
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install BrowserOS
|
||||
run: |
|
||||
wget -q https://github.com/browseros-ai/BrowserOS/releases/download/v0.44.0.1/BrowserOS_v0.44.0.1_amd64.deb
|
||||
sudo dpkg -i BrowserOS_v0.44.0.1_amd64.deb
|
||||
browseros --version || echo "BrowserOS installed at $(which browseros)"
|
||||
|
||||
- name: Install Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: packages/browseros-agent
|
||||
run: bun install --ignore-scripts && bun run build:agent-sdk
|
||||
|
||||
- name: Install captcha solver extension
|
||||
working-directory: packages/browseros-agent/apps/eval
|
||||
run: |
|
||||
mkdir -p extensions
|
||||
curl -sL -o /tmp/nopecha.zip https://github.com/NopeCHALLC/nopecha-extension/releases/latest/download/chromium_automation.zip
|
||||
unzip -qo /tmp/nopecha.zip -d extensions/nopecha
|
||||
|
||||
- name: Run eval
|
||||
working-directory: packages/browseros-agent/apps/eval
|
||||
env:
|
||||
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
BROWSEROS_BINARY: /usr/bin/browseros
|
||||
EVAL_CONFIG: ${{ github.event.inputs.config || 'configs/browseros-agent-weekly.json' }}
|
||||
run: |
|
||||
echo "Running eval with config: $EVAL_CONFIG"
|
||||
bun run src/index.ts -c "$EVAL_CONFIG"
|
||||
|
||||
- name: Upload runs to R2
|
||||
if: success()
|
||||
working-directory: packages/browseros-agent/apps/eval
|
||||
env:
|
||||
EVAL_R2_ACCOUNT_ID: ${{ secrets.EVAL_R2_ACCOUNT_ID }}
|
||||
EVAL_R2_ACCESS_KEY_ID: ${{ secrets.EVAL_R2_ACCESS_KEY_ID }}
|
||||
EVAL_R2_SECRET_ACCESS_KEY: ${{ secrets.EVAL_R2_SECRET_ACCESS_KEY }}
|
||||
EVAL_R2_BUCKET: ${{ secrets.EVAL_R2_BUCKET }}
|
||||
EVAL_R2_CDN_BASE_URL: ${{ secrets.EVAL_R2_CDN_BASE_URL }}
|
||||
EVAL_CONFIG: ${{ github.event.inputs.config || 'configs/browseros-agent-weekly.json' }}
|
||||
run: |
|
||||
CONFIG_NAME=$(basename "$EVAL_CONFIG" .json)
|
||||
bun scripts/upload-run.ts "results/$CONFIG_NAME"
|
||||
|
||||
- name: Generate trend report
|
||||
if: success()
|
||||
working-directory: packages/browseros-agent
|
||||
env:
|
||||
EVAL_R2_ACCOUNT_ID: ${{ secrets.EVAL_R2_ACCOUNT_ID }}
|
||||
EVAL_R2_ACCESS_KEY_ID: ${{ secrets.EVAL_R2_ACCESS_KEY_ID }}
|
||||
EVAL_R2_SECRET_ACCESS_KEY: ${{ secrets.EVAL_R2_SECRET_ACCESS_KEY }}
|
||||
EVAL_R2_BUCKET: ${{ secrets.EVAL_R2_BUCKET }}
|
||||
EVAL_R2_CDN_BASE_URL: ${{ secrets.EVAL_R2_CDN_BASE_URL }}
|
||||
run: bun apps/eval/scripts/weekly-report.ts /tmp/eval-report.html
|
||||
|
||||
- name: Upload report as artifact
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: eval-report-${{ github.run_id }}
|
||||
path: /tmp/eval-report.html
|
||||
127
.github/workflows/test.yml
vendored
127
.github/workflows/test.yml
vendored
@@ -1,15 +1,44 @@
|
||||
name: Tests
|
||||
|
||||
on: []
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- ready_for_review
|
||||
paths:
|
||||
- .github/workflows/test.yml
|
||||
- packages/browseros-agent/**
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
BROWSEROS_APPIMAGE_URL: https://files.browseros.com/download/BrowserOS.AppImage
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run Tests
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 10
|
||||
name: Tests / ${{ matrix.suite }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
defaults:
|
||||
run:
|
||||
working-directory: packages/browseros-agent
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- suite: tools
|
||||
test_path: tests/tools
|
||||
junit_path: test-results/tools.xml
|
||||
- suite: integration
|
||||
test_path: tests/server.integration.test.ts
|
||||
junit_path: test-results/integration.xml
|
||||
- suite: sdk
|
||||
test_path: tests/sdk
|
||||
junit_path: test-results/sdk.xml
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -21,7 +50,91 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: bun ci
|
||||
|
||||
- name: Run all tests
|
||||
run: bun test:all
|
||||
- name: Resolve BrowserOS cache key
|
||||
id: browseros-cache-key
|
||||
run: |
|
||||
set -euo pipefail
|
||||
headers="$(curl -fsSI "$BROWSEROS_APPIMAGE_URL")"
|
||||
etag="$(printf '%s\n' "$headers" | awk 'BEGIN{IGNORECASE=1} /^etag:/ {sub(/\r$/, "", $2); gsub(/"/, "", $2); print $2; exit}')"
|
||||
last_modified="$(printf '%s\n' "$headers" | awk 'BEGIN{IGNORECASE=1} /^last-modified:/ {$1=""; sub(/^ /, ""); sub(/\r$/, ""); print; exit}')"
|
||||
raw_key="${etag:-$last_modified}"
|
||||
if [ -z "$raw_key" ]; then
|
||||
raw_key="$BROWSEROS_APPIMAGE_URL"
|
||||
fi
|
||||
cache_key="$(printf '%s' "$raw_key" | shasum -a 256 | awk '{print $1}')"
|
||||
echo "key=browseros-appimage-${{ runner.os }}-$cache_key" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Restore BrowserOS cache
|
||||
id: browseros-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: packages/browseros-agent/.ci/bin/BrowserOS.AppImage
|
||||
key: ${{ steps.browseros-cache-key.outputs.key }}
|
||||
|
||||
- name: Download BrowserOS
|
||||
if: steps.browseros-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p .ci/bin
|
||||
curl -fsSL "$BROWSEROS_APPIMAGE_URL" -o .ci/bin/BrowserOS.AppImage
|
||||
chmod +x .ci/bin/BrowserOS.AppImage
|
||||
|
||||
- name: Prepare BrowserOS wrapper
|
||||
run: |
|
||||
mkdir -p .ci/bin
|
||||
cat > .ci/bin/browseros <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
export APPIMAGE_EXTRACT_AND_RUN=1
|
||||
exec "$(dirname "$0")/BrowserOS.AppImage" "$@"
|
||||
EOF
|
||||
chmod +x .ci/bin/browseros
|
||||
|
||||
- name: Create server env file
|
||||
working-directory: packages/browseros-agent/apps/server
|
||||
run: cp .env.example .env.development
|
||||
|
||||
- name: Run ${{ matrix.suite }} tests
|
||||
id: test
|
||||
env:
|
||||
PUPPETEER_EXECUTABLE_PATH: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
|
||||
BROWSEROS_BINARY: ${{ github.workspace }}/packages/browseros-agent/.ci/bin/browseros
|
||||
BROWSEROS_TEST_HEADLESS: "true"
|
||||
BROWSEROS_TEST_EXTRA_ARGS: --no-sandbox --disable-dev-shm-usage
|
||||
run: |
|
||||
set +e
|
||||
mkdir -p test-results
|
||||
cd apps/server
|
||||
bun run test:cleanup
|
||||
bun --env-file=.env.development test "${{ matrix.test_path }}" --reporter=junit --reporter-outfile="../../${{ matrix.junit_path }}"
|
||||
exit_code=$?
|
||||
cd ../..
|
||||
if [ ! -f "${{ matrix.junit_path }}" ]; then
|
||||
cat > "${{ matrix.junit_path }}" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites tests="1" failures="1">
|
||||
<testsuite name="${{ matrix.suite }}" tests="1" failures="1">
|
||||
<testcase classname="workflow" name="${{ matrix.suite }} setup">
|
||||
<failure message="Test run failed before JUnit output was written">See workflow logs for details.</failure>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
EOF
|
||||
fi
|
||||
echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload JUnit XML
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: junit-${{ matrix.suite }}
|
||||
path: packages/browseros-agent/${{ matrix.junit_path }}
|
||||
|
||||
- name: Summarize suite result
|
||||
if: always()
|
||||
run: |
|
||||
if [ "${{ steps.test.outputs.exit_code }}" = "0" ]; then
|
||||
echo "### :white_check_mark: ${{ matrix.suite }} suite passed" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "### :x: ${{ matrix.suite }} suite failed (exit code ${{ steps.test.outputs.exit_code }})" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "See the uploaded \`junit-${{ matrix.suite }}\` artifact for details." >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
4
packages/browseros-agent/.gitignore
vendored
4
packages/browseros-agent/.gitignore
vendored
@@ -187,6 +187,10 @@ log.txt
|
||||
# Testing iteration temp files
|
||||
tmp/
|
||||
|
||||
# CI artifacts
|
||||
.ci/
|
||||
test-results/
|
||||
|
||||
# Coding agent artifacts
|
||||
.agent/
|
||||
.llm/
|
||||
|
||||
@@ -5,8 +5,11 @@ import {
|
||||
Folder,
|
||||
Globe,
|
||||
Layers,
|
||||
Loader2,
|
||||
Mic,
|
||||
PlugZap,
|
||||
Search,
|
||||
Square,
|
||||
X,
|
||||
} from 'lucide-react'
|
||||
import { AnimatePresence, motion } from 'motion/react'
|
||||
@@ -44,6 +47,10 @@ import {
|
||||
NEWTAB_TAB_REMOVED_EVENT,
|
||||
NEWTAB_TAB_TOGGLED_EVENT,
|
||||
NEWTAB_TABS_OPENED_EVENT,
|
||||
NEWTAB_VOICE_ERROR_EVENT,
|
||||
NEWTAB_VOICE_RECORDING_STARTED_EVENT,
|
||||
NEWTAB_VOICE_RECORDING_STOPPED_EVENT,
|
||||
NEWTAB_VOICE_TRANSCRIPTION_COMPLETED_EVENT,
|
||||
NEWTAB_WORKSPACE_OPENED_EVENT,
|
||||
} from '@/lib/constants/analyticsEvents'
|
||||
import { BrowserOSIcon, ProviderIcon } from '@/lib/llm-providers/providerIcons'
|
||||
@@ -53,6 +60,7 @@ import { useSyncRemoteIntegrations } from '@/lib/mcp/useSyncRemoteIntegrations'
|
||||
import { openSidePanelWithSearch } from '@/lib/messaging/sidepanel/openSidepanelWithSearch'
|
||||
import { track } from '@/lib/metrics/track'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useVoiceInput } from '@/lib/voice/useVoiceInput'
|
||||
import { useWorkspace } from '@/lib/workspace/use-workspace'
|
||||
import { ImportDataHint } from './ImportDataHint'
|
||||
import type { SuggestionItem } from './lib/suggestions/types'
|
||||
@@ -100,6 +108,36 @@ export const NewTab = () => {
|
||||
const { data: userMCPIntegrations } = useGetUserMCPIntegrations()
|
||||
useSyncRemoteIntegrations()
|
||||
|
||||
const voice = useVoiceInput()
|
||||
|
||||
// Voice transcript → populate search input
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: only trigger on transcript/transcribing change
|
||||
useEffect(() => {
|
||||
if (voice.transcript && !voice.isTranscribing) {
|
||||
setComboboxInputValue(voice.transcript)
|
||||
track(NEWTAB_VOICE_TRANSCRIPTION_COMPLETED_EVENT)
|
||||
voice.clearTranscript()
|
||||
}
|
||||
}, [voice.transcript, voice.isTranscribing])
|
||||
|
||||
useEffect(() => {
|
||||
if (voice.error) {
|
||||
track(NEWTAB_VOICE_ERROR_EVENT, { error: voice.error })
|
||||
}
|
||||
}, [voice.error])
|
||||
|
||||
const handleStartRecording = async () => {
|
||||
const started = await voice.startRecording()
|
||||
if (started) {
|
||||
track(NEWTAB_VOICE_RECORDING_STARTED_EVENT)
|
||||
}
|
||||
}
|
||||
|
||||
const handleStopRecording = async () => {
|
||||
await voice.stopRecording()
|
||||
track(NEWTAB_VOICE_RECORDING_STOPPED_EVENT)
|
||||
}
|
||||
|
||||
const connectedManagedServers = mcpServers.filter((s) => {
|
||||
if (s.type !== 'managed' || !s.managedServerName) return false
|
||||
return userMCPIntegrations?.integrations?.find(
|
||||
@@ -430,32 +468,89 @@ export const NewTab = () => {
|
||||
anchorRef={inputRef}
|
||||
side="bottom"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={searchPlaceholder}
|
||||
className="flex-1 border-none bg-transparent text-base text-foreground outline-none placeholder:text-muted-foreground"
|
||||
{...getInputProps({
|
||||
ref: inputRef,
|
||||
onChange: (e) => handleInputChange(e.currentTarget.value),
|
||||
onKeyDown: (e) => {
|
||||
if (!mentionStateRef.current.isOpen) return
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault()
|
||||
closeMention()
|
||||
}
|
||||
},
|
||||
})}
|
||||
/>
|
||||
{voice.isRecording ? (
|
||||
<div className="flex min-h-[40px] flex-1 items-center justify-center gap-1.5">
|
||||
{voice.audioLevels.map((level, i) => (
|
||||
<div
|
||||
key={i.toString()}
|
||||
className="w-1.5 rounded-full bg-red-500 transition-all duration-75"
|
||||
style={{
|
||||
height: `${Math.max(6, Math.min(28, level * 0.7))}px`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
placeholder={
|
||||
voice.isTranscribing ? 'Transcribing...' : searchPlaceholder
|
||||
}
|
||||
disabled={voice.isTranscribing}
|
||||
className="flex-1 border-none bg-transparent text-base text-foreground outline-none placeholder:text-muted-foreground disabled:opacity-60"
|
||||
{...getInputProps({
|
||||
ref: inputRef,
|
||||
onChange: (e) => handleInputChange(e.currentTarget.value),
|
||||
onKeyDown: (e) => {
|
||||
if (!mentionStateRef.current.isOpen) return
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault()
|
||||
closeMention()
|
||||
}
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
onClick={handleSend}
|
||||
size="icon"
|
||||
className="h-10 w-10 flex-shrink-0 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
<ArrowRight className="h-5 w-5" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{voice.isRecording ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="icon"
|
||||
onClick={handleStopRecording}
|
||||
className="h-10 w-10 flex-shrink-0 rounded-xl bg-red-600 text-white hover:bg-red-700"
|
||||
>
|
||||
<Square className="h-4 w-4" />
|
||||
</Button>
|
||||
) : voice.isTranscribing ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
disabled
|
||||
className="h-10 w-10 flex-shrink-0 rounded-xl"
|
||||
>
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleStartRecording}
|
||||
className="h-10 w-10 flex-shrink-0 rounded-xl text-muted-foreground transition-colors hover:text-foreground"
|
||||
title="Voice input"
|
||||
>
|
||||
<Mic className="h-5 w-5" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={handleSend}
|
||||
size="icon"
|
||||
disabled={voice.isRecording || voice.isTranscribing}
|
||||
className="h-10 w-10 flex-shrink-0 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
>
|
||||
<ArrowRight className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{voice.error && (
|
||||
<div className="px-5 pb-2 text-destructive text-xs">
|
||||
{voice.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AnimatePresence>
|
||||
{selectedTabs.length > 0 && (
|
||||
<motion.div
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
const GATEWAY_URL = 'https://llm.browseros.com'
|
||||
|
||||
interface TranscribeResponse {
|
||||
text: string
|
||||
}
|
||||
|
||||
export async function transcribeAudio(audioBlob: Blob): Promise<string> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', audioBlob, 'recording.webm')
|
||||
formData.append('response_format', 'json')
|
||||
|
||||
const response = await fetch(`${GATEWAY_URL}/api/transcribe`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody: { error?: string } = await response
|
||||
.json()
|
||||
.catch(() => ({ error: 'Transcription failed' }))
|
||||
throw new Error(
|
||||
errorBody.error || `Transcription failed: ${response.status}`,
|
||||
)
|
||||
}
|
||||
|
||||
const result: TranscribeResponse = await response.json()
|
||||
return result.text || ''
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { transcribeAudio } from './transcribe-audio'
|
||||
|
||||
const GATEWAY_URL = 'https://llm.browseros.com'
|
||||
const WAVEFORM_BAND_COUNT = 5
|
||||
|
||||
export interface VoiceInputState {
|
||||
@@ -26,34 +26,6 @@ export interface UseVoiceInputReturn {
|
||||
|
||||
const EMPTY_LEVELS = Array(WAVEFORM_BAND_COUNT).fill(0)
|
||||
|
||||
interface TranscribeResponse {
|
||||
text: string
|
||||
}
|
||||
|
||||
async function transcribeAudio(audioBlob: Blob): Promise<string> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', audioBlob, 'recording.webm')
|
||||
formData.append('response_format', 'json')
|
||||
|
||||
const response = await fetch(`${GATEWAY_URL}/api/transcribe`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody: { error?: string } = await response
|
||||
.json()
|
||||
.catch(() => ({ error: 'Transcription failed' }))
|
||||
throw new Error(
|
||||
errorBody.error || `Transcription failed: ${response.status}`,
|
||||
)
|
||||
}
|
||||
|
||||
const result: TranscribeResponse = await response.json()
|
||||
return result.text || ''
|
||||
}
|
||||
|
||||
export function useVoiceInput(): UseVoiceInputReturn {
|
||||
const [isRecording, setIsRecording] = useState(false)
|
||||
const [isTranscribing, setIsTranscribing] = useState(false)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -170,11 +172,44 @@ func defaultServerURL() string {
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err == nil {
|
||||
if url := normalizeServerURL(cfg.ServerURL); url != "" {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
if url := loadBrowserosServerURL(); url != "" {
|
||||
return url
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
type serverDiscoveryConfig struct {
|
||||
ServerPort int `json:"server_port"`
|
||||
URL string `json:"url"`
|
||||
ServerVersion string `json:"server_version"`
|
||||
BrowserOSVersion string `json:"browseros_version,omitempty"`
|
||||
ChromiumVersion string `json:"chromium_version,omitempty"`
|
||||
}
|
||||
|
||||
func loadBrowserosServerURL() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return normalizeServerURL(cfg.ServerURL)
|
||||
data, err := os.ReadFile(filepath.Join(home, ".browseros", "server.json"))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var sc serverDiscoveryConfig
|
||||
if err := json.Unmarshal(data, &sc); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return normalizeServerURL(sc.URL)
|
||||
}
|
||||
|
||||
func normalizeServerURL(raw string) string {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
data/raw/
|
||||
results/
|
||||
extensions/
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"agent": {
|
||||
"type": "single",
|
||||
"provider": "openai-compatible",
|
||||
"model": "accounts/fireworks/models/kimi-k2p5",
|
||||
"apiKey": "FIREWORKS_API_KEY",
|
||||
"baseUrl": "https://api.fireworks.ai/inference/v1",
|
||||
"supportsImages": true
|
||||
},
|
||||
"dataset": "../data/webbench-2of4-50.jsonl",
|
||||
"num_workers": 10,
|
||||
"restart_server_per_task": true,
|
||||
"browseros": {
|
||||
"server_url": "http://127.0.0.1:9110",
|
||||
"base_cdp_port": 9010,
|
||||
"base_server_port": 9110,
|
||||
"base_extension_port": 9310,
|
||||
"load_extensions": false,
|
||||
"headless": true
|
||||
},
|
||||
"graders": ["performance_grader"],
|
||||
"grader_api_key_env": "OPENROUTER_API_KEY",
|
||||
"grader_base_url": "https://openrouter.ai/api/v1",
|
||||
"grader_model": "openai/gpt-4.1",
|
||||
"timeout_ms": 1800000
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"agent": {
|
||||
"type": "orchestrator-executor",
|
||||
"orchestrator": {
|
||||
"provider": "openai-compatible",
|
||||
"model": "accounts/fireworks/models/kimi-k2p5",
|
||||
"apiKey": "FIREWORKS_API_KEY",
|
||||
"baseUrl": "https://api.fireworks.ai/inference/v1"
|
||||
},
|
||||
"executor": {
|
||||
"provider": "clado-action",
|
||||
"model": "qwen3-vl-30b-a3b-instruct",
|
||||
"apiKey": "",
|
||||
"baseUrl": "https://clado-ai--clado-browseros-action-actionmodel-generate.modal.run"
|
||||
}
|
||||
},
|
||||
"dataset": "../data/webbench-2of4-50.jsonl",
|
||||
"num_workers": 10,
|
||||
"restart_server_per_task": true,
|
||||
"browseros": {
|
||||
"server_url": "http://127.0.0.1:9110",
|
||||
"base_cdp_port": 9010,
|
||||
"base_server_port": 9110,
|
||||
"base_extension_port": 9310,
|
||||
"load_extensions": false,
|
||||
"headless": true
|
||||
},
|
||||
"graders": ["performance_grader"],
|
||||
"grader_api_key_env": "OPENROUTER_API_KEY",
|
||||
"grader_base_url": "https://openrouter.ai/api/v1",
|
||||
"grader_model": "openai/gpt-4.1",
|
||||
"timeout_ms": 1800000
|
||||
}
|
||||
@@ -9,12 +9,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.63",
|
||||
"@aws-sdk/client-s3": "^3.1014.0",
|
||||
"@browseros/server": "workspace:*",
|
||||
"@browseros/shared": "workspace:*",
|
||||
"@google/gemini-cli-core": "^0.16.0",
|
||||
"ai": "^6.0.94",
|
||||
"@google/genai": "1.30.0",
|
||||
"@modelcontextprotocol/sdk": "^1.25.2",
|
||||
"ai": "^6.0.94",
|
||||
"hono": "^4.6.0",
|
||||
"openai": "^4.0.0",
|
||||
"sharp": "^0.34.5",
|
||||
|
||||
349
packages/browseros-agent/apps/eval/scripts/upload-run.ts
Normal file
349
packages/browseros-agent/apps/eval/scripts/upload-run.ts
Normal file
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* Upload eval runs to R2.
|
||||
*
|
||||
* Two modes:
|
||||
* bun scripts/upload-run.ts results/browseros-agent-weekly/2026-03-21-1730
|
||||
* → uploads that specific run
|
||||
*
|
||||
* bun scripts/upload-run.ts results/browseros-agent-weekly
|
||||
* → finds all timestamped subfolders, uploads any not yet in R2
|
||||
*
|
||||
* Env vars: EVAL_R2_ACCOUNT_ID, EVAL_R2_ACCESS_KEY_ID, EVAL_R2_SECRET_ACCESS_KEY
|
||||
* EVAL_R2_BUCKET (default: browseros-eval)
|
||||
* EVAL_R2_CDN_BASE_URL (default: https://eval.browseros.com)
|
||||
*/
|
||||
|
||||
import { readdir, readFile, stat } from 'node:fs/promises'
|
||||
import { basename, dirname, extname, join } from 'node:path'
|
||||
import {
|
||||
GetObjectCommand,
|
||||
PutObjectCommand,
|
||||
S3Client,
|
||||
} from '@aws-sdk/client-s3'
|
||||
|
||||
const CONCURRENCY = 20
|
||||
|
||||
const CONTENT_TYPES: Record<string, string> = {
|
||||
'.json': 'application/json',
|
||||
'.jsonl': 'application/x-ndjson',
|
||||
'.png': 'image/png',
|
||||
}
|
||||
|
||||
interface R2Config {
|
||||
accountId: string
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
bucket: string
|
||||
cdnBaseUrl: string
|
||||
}
|
||||
|
||||
function loadConfig(): R2Config {
|
||||
const accountId = process.env.EVAL_R2_ACCOUNT_ID
|
||||
const accessKeyId = process.env.EVAL_R2_ACCESS_KEY_ID
|
||||
const secretAccessKey = process.env.EVAL_R2_SECRET_ACCESS_KEY
|
||||
|
||||
if (!accountId || !accessKeyId || !secretAccessKey) {
|
||||
console.error(
|
||||
'Missing required env vars: EVAL_R2_ACCOUNT_ID, EVAL_R2_ACCESS_KEY_ID, EVAL_R2_SECRET_ACCESS_KEY',
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
return {
|
||||
accountId,
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
bucket: process.env.EVAL_R2_BUCKET || 'browseros-eval',
|
||||
cdnBaseUrl: (
|
||||
process.env.EVAL_R2_CDN_BASE_URL || 'https://eval.browseros.com'
|
||||
).replace(/\/+$/, ''),
|
||||
}
|
||||
}
|
||||
|
||||
function createClient(config: R2Config): S3Client {
|
||||
return new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,
|
||||
credentials: {
|
||||
accessKeyId: config.accessKeyId,
|
||||
secretAccessKey: config.secretAccessKey,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function upload(
|
||||
client: S3Client,
|
||||
bucket: string,
|
||||
key: string,
|
||||
body: Buffer,
|
||||
contentType: string,
|
||||
) {
|
||||
await client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
Body: body,
|
||||
ContentType: contentType,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
async function collectFiles(dir: string): Promise<string[]> {
|
||||
const files: string[] = []
|
||||
const entries = await readdir(dir, { withFileTypes: true })
|
||||
for (const entry of entries) {
|
||||
const full = join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...(await collectFiles(full)))
|
||||
} else {
|
||||
files.push(full)
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
async function runPool<T>(
|
||||
items: T[],
|
||||
concurrency: number,
|
||||
fn: (item: T) => Promise<void>,
|
||||
) {
|
||||
let i = 0
|
||||
const workers = Array.from({ length: concurrency }, async () => {
|
||||
while (i < items.length) {
|
||||
const idx = i++
|
||||
await fn(items[idx])
|
||||
}
|
||||
})
|
||||
await Promise.all(workers)
|
||||
}
|
||||
|
||||
// Check if a run has already been uploaded to R2
|
||||
async function isUploaded(
|
||||
client: S3Client,
|
||||
bucket: string,
|
||||
runId: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await client.send(
|
||||
new GetObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: `runs/${runId}/manifest.json`,
|
||||
}),
|
||||
)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Detect if a directory is a run dir (has task subdirs with metadata.json)
|
||||
// vs a config dir (has timestamped subdirs like 2026-03-21-1730/)
|
||||
async function isRunDir(dir: string): Promise<boolean> {
|
||||
const entries = await readdir(dir, { withFileTypes: true })
|
||||
const subdirs = entries.filter((e) => e.isDirectory())
|
||||
for (const subdir of subdirs) {
|
||||
const metaPath = join(dir, subdir.name, 'metadata.json')
|
||||
const metaStat = await stat(metaPath).catch(() => null)
|
||||
if (metaStat?.isFile()) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
async function uploadSingleRun(
|
||||
runDir: string,
|
||||
runId: string,
|
||||
r2Config: R2Config,
|
||||
client: S3Client,
|
||||
): Promise<void> {
|
||||
const taskDirs = await readdir(runDir, { withFileTypes: true })
|
||||
const taskEntries = taskDirs.filter((d) => d.isDirectory())
|
||||
|
||||
if (taskEntries.length === 0) {
|
||||
console.warn(` No task subdirectories in ${runId}, skipping`)
|
||||
return
|
||||
}
|
||||
|
||||
const manifestTasks: Record<string, unknown>[] = []
|
||||
const jobs: { key: string; filePath: string; contentType: string }[] = []
|
||||
|
||||
// Extract agent config from first task
|
||||
let agentConfig: Record<string, unknown> | undefined
|
||||
let dataset: string | undefined
|
||||
|
||||
for (const taskDir of taskEntries) {
|
||||
const taskId = taskDir.name
|
||||
const taskPath = join(runDir, taskId)
|
||||
const metaPath = join(taskPath, 'metadata.json')
|
||||
|
||||
let meta: Record<string, unknown> = {}
|
||||
try {
|
||||
meta = JSON.parse(await readFile(metaPath, 'utf-8'))
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!agentConfig && meta.agent_config)
|
||||
agentConfig = meta.agent_config as Record<string, unknown>
|
||||
if (!dataset && meta.dataset) dataset = meta.dataset as string
|
||||
|
||||
const files = await collectFiles(taskPath)
|
||||
let screenshotCount = 0
|
||||
|
||||
for (const file of files) {
|
||||
const relative = file.slice(taskPath.length + 1)
|
||||
const ext = extname(file)
|
||||
if (relative.startsWith('screenshots/') && ext === '.png')
|
||||
screenshotCount++
|
||||
|
||||
jobs.push({
|
||||
key: `runs/${runId}/${taskId}/${relative}`,
|
||||
filePath: file,
|
||||
contentType: CONTENT_TYPES[ext] || 'application/octet-stream',
|
||||
})
|
||||
}
|
||||
|
||||
manifestTasks.push({
|
||||
queryId: meta.query_id || taskId,
|
||||
query: meta.query || '',
|
||||
startUrl: meta.start_url || '',
|
||||
status:
|
||||
meta.termination_reason === 'completed'
|
||||
? 'completed'
|
||||
: meta.termination_reason || 'unknown',
|
||||
durationMs: meta.total_duration_ms || 0,
|
||||
screenshotCount: (meta.screenshot_count as number) || screenshotCount,
|
||||
graderResults: meta.grader_results || {},
|
||||
})
|
||||
}
|
||||
|
||||
if (manifestTasks.length === 0) {
|
||||
console.warn(` No completed tasks in ${runId}, skipping`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(
|
||||
` Uploading ${jobs.length} files across ${manifestTasks.length} tasks...`,
|
||||
)
|
||||
|
||||
let uploaded = 0
|
||||
await runPool(jobs, CONCURRENCY, async (job) => {
|
||||
const body = await readFile(job.filePath)
|
||||
await upload(client, r2Config.bucket, job.key, body, job.contentType)
|
||||
uploaded++
|
||||
if (uploaded % 50 === 0 || uploaded === jobs.length) {
|
||||
console.log(` ${uploaded}/${jobs.length}`)
|
||||
}
|
||||
})
|
||||
|
||||
// Read summary.json if it exists
|
||||
let summaryData: Record<string, unknown> | undefined
|
||||
try {
|
||||
summaryData = JSON.parse(
|
||||
await readFile(join(runDir, 'summary.json'), 'utf-8'),
|
||||
)
|
||||
} catch {}
|
||||
|
||||
// Upload manifest
|
||||
const manifest = {
|
||||
runId,
|
||||
uploadedAt: new Date().toISOString(),
|
||||
agentConfig,
|
||||
dataset,
|
||||
summary: summaryData
|
||||
? {
|
||||
passRate: summaryData.passRate,
|
||||
avgDurationMs: summaryData.avgDurationMs,
|
||||
}
|
||||
: undefined,
|
||||
tasks: manifestTasks,
|
||||
}
|
||||
const manifestBody = Buffer.from(JSON.stringify(manifest, null, 2))
|
||||
await upload(
|
||||
client,
|
||||
r2Config.bucket,
|
||||
`runs/${runId}/manifest.json`,
|
||||
manifestBody,
|
||||
'application/json',
|
||||
)
|
||||
|
||||
// Upload viewer.html to bucket root
|
||||
const viewerPath = join(
|
||||
import.meta.dir,
|
||||
'..',
|
||||
'src',
|
||||
'dashboard',
|
||||
'viewer.html',
|
||||
)
|
||||
const viewerBody = await readFile(viewerPath)
|
||||
await upload(client, r2Config.bucket, 'viewer.html', viewerBody, 'text/html')
|
||||
|
||||
console.log(` Uploaded ${uploaded + 2} files`)
|
||||
console.log(` ${r2Config.cdnBaseUrl}/viewer.html?run=${runId}`)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const inputDir = process.argv[2]
|
||||
if (!inputDir) {
|
||||
console.error(
|
||||
'Usage:\n' +
|
||||
' bun scripts/upload-run.ts results/config-name/2026-03-21-1730 (specific run)\n' +
|
||||
' bun scripts/upload-run.ts results/config-name (all un-uploaded runs)',
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const dirStat = await stat(inputDir).catch(() => null)
|
||||
if (!dirStat?.isDirectory()) {
|
||||
console.error(`Not a directory: ${inputDir}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const r2Config = loadConfig()
|
||||
const client = createClient(r2Config)
|
||||
|
||||
if (await isRunDir(inputDir)) {
|
||||
// Single run: results/config-name/2026-03-21-1730
|
||||
const timestamp = basename(inputDir)
|
||||
const configName = basename(dirname(inputDir))
|
||||
const runId = `${configName}-${timestamp}`
|
||||
console.log(`Uploading run: ${runId}`)
|
||||
await uploadSingleRun(inputDir, runId, r2Config, client)
|
||||
} else {
|
||||
// Config dir: results/config-name/ — upload all un-uploaded runs
|
||||
const configName = basename(inputDir)
|
||||
const entries = await readdir(inputDir, { withFileTypes: true })
|
||||
const runDirs = entries
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => e.name)
|
||||
.sort()
|
||||
|
||||
if (runDirs.length === 0) {
|
||||
console.error('No run subdirectories found')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Found ${runDirs.length} runs for config "${configName}", checking R2...`,
|
||||
)
|
||||
|
||||
let uploadedCount = 0
|
||||
for (const dir of runDirs) {
|
||||
const runId = `${configName}-${dir}`
|
||||
const alreadyUploaded = await isUploaded(client, r2Config.bucket, runId)
|
||||
if (alreadyUploaded) {
|
||||
console.log(` ${runId}: already uploaded, skipping`)
|
||||
continue
|
||||
}
|
||||
|
||||
console.log(` ${runId}: uploading...`)
|
||||
await uploadSingleRun(join(inputDir, dir), runId, r2Config, client)
|
||||
uploadedCount++
|
||||
}
|
||||
|
||||
console.log(
|
||||
`\nDone. Uploaded ${uploadedCount} new run(s), ${runDirs.length - uploadedCount} already in R2.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
590
packages/browseros-agent/apps/eval/scripts/weekly-report.ts
Normal file
590
packages/browseros-agent/apps/eval/scripts/weekly-report.ts
Normal file
@@ -0,0 +1,590 @@
|
||||
/**
|
||||
* Weekly Report Generator
|
||||
*
|
||||
* Reads all uploaded eval runs from R2, builds cumulative score history,
|
||||
* and generates an HTML dashboard with:
|
||||
* - Config selector dropdown (groups runs by config/runId pattern)
|
||||
* - Config details card (architecture, model, dataset, grader)
|
||||
* - Interactive trend chart (filtered by selected config)
|
||||
* - Stat cards (latest, trend, best, duration)
|
||||
* - Searchable table of all runs
|
||||
*
|
||||
* Usage:
|
||||
* bun apps/eval/scripts/weekly-report.ts [local-output-path]
|
||||
*
|
||||
* Env vars required:
|
||||
* EVAL_R2_ACCOUNT_ID, EVAL_R2_ACCESS_KEY_ID, EVAL_R2_SECRET_ACCESS_KEY
|
||||
* EVAL_R2_BUCKET (default: browseros-eval)
|
||||
*/
|
||||
|
||||
import { writeFile } from 'node:fs/promises'
|
||||
import {
|
||||
GetObjectCommand,
|
||||
ListObjectsV2Command,
|
||||
PutObjectCommand,
|
||||
S3Client,
|
||||
} from '@aws-sdk/client-s3'
|
||||
|
||||
interface ManifestTask {
|
||||
queryId: string
|
||||
query: string
|
||||
status: string
|
||||
durationMs: number
|
||||
screenshotCount: number
|
||||
graderResults: Record<string, { pass: boolean; score: number }>
|
||||
}
|
||||
|
||||
interface Manifest {
|
||||
runId: string
|
||||
uploadedAt: string
|
||||
agentConfig?: { type?: string; model?: string }
|
||||
dataset?: string
|
||||
summary?: { passRate?: number; avgDurationMs?: number }
|
||||
tasks: ManifestTask[]
|
||||
}
|
||||
|
||||
interface RunSummary {
|
||||
runId: string
|
||||
configName: string
|
||||
date: string
|
||||
passRate: number
|
||||
total: number
|
||||
completed: number
|
||||
failed: number
|
||||
timeout: number
|
||||
avgDurationMs: number
|
||||
model: string
|
||||
dataset: string
|
||||
agentType: string
|
||||
}
|
||||
|
||||
const PASS_FAIL_GRADER_ORDER = [
|
||||
'performance_grader',
|
||||
'webvoyager_grader',
|
||||
'fara_combined',
|
||||
'fara_grader',
|
||||
]
|
||||
|
||||
function requireEnv(name: string): string {
|
||||
const value = process.env[name]
|
||||
if (!value) {
|
||||
console.error(`Missing required env var: ${name}`)
|
||||
process.exit(1)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const accountId = requireEnv('EVAL_R2_ACCOUNT_ID')
|
||||
const accessKeyId = requireEnv('EVAL_R2_ACCESS_KEY_ID')
|
||||
const secretAccessKey = requireEnv('EVAL_R2_SECRET_ACCESS_KEY')
|
||||
const bucket = process.env.EVAL_R2_BUCKET || 'browseros-eval'
|
||||
|
||||
const client = new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
||||
credentials: { accessKeyId, secretAccessKey },
|
||||
})
|
||||
|
||||
// Step 1: List all manifest.json files in runs/
|
||||
console.log('Scanning R2 for eval runs...')
|
||||
|
||||
const manifests: Manifest[] = []
|
||||
let continuationToken: string | undefined
|
||||
|
||||
do {
|
||||
const listRes = await client.send(
|
||||
new ListObjectsV2Command({
|
||||
Bucket: bucket,
|
||||
Prefix: 'runs/',
|
||||
ContinuationToken: continuationToken,
|
||||
}),
|
||||
)
|
||||
|
||||
const manifestKeys =
|
||||
listRes.Contents?.filter((obj) => obj.Key?.endsWith('/manifest.json')).map(
|
||||
(obj) => obj.Key as string,
|
||||
) ?? []
|
||||
|
||||
for (const key of manifestKeys) {
|
||||
try {
|
||||
const res = await client.send(
|
||||
new GetObjectCommand({ Bucket: bucket, Key: key }),
|
||||
)
|
||||
const body = await res.Body?.transformToString()
|
||||
if (body) manifests.push(JSON.parse(body))
|
||||
} catch {
|
||||
console.warn(` Failed to read ${key}, skipping`)
|
||||
}
|
||||
}
|
||||
|
||||
continuationToken = listRes.NextContinuationToken
|
||||
} while (continuationToken)
|
||||
|
||||
console.log(`Found ${manifests.length} runs`)
|
||||
|
||||
if (manifests.length === 0) {
|
||||
console.log('No runs found. Nothing to report.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// Step 2: Build run summaries
|
||||
const runs: RunSummary[] = manifests
|
||||
.map((m) => {
|
||||
const total = m.tasks.length
|
||||
const completed = m.tasks.filter((t) => t.status === 'completed').length
|
||||
const failed = m.tasks.filter((t) => t.status === 'failed').length
|
||||
const timeout = m.tasks.filter((t) => t.status === 'timeout').length
|
||||
|
||||
let graded = 0
|
||||
let passed = 0
|
||||
for (const task of m.tasks) {
|
||||
if (!task.graderResults) continue
|
||||
for (const name of PASS_FAIL_GRADER_ORDER) {
|
||||
if (task.graderResults[name]) {
|
||||
graded++
|
||||
if (task.graderResults[name].pass) passed++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const passRate = graded > 0 ? passed / graded : 0
|
||||
const durations = m.tasks
|
||||
.filter((t) => t.durationMs > 0)
|
||||
.map((t) => t.durationMs)
|
||||
const avgDurationMs =
|
||||
durations.length > 0
|
||||
? durations.reduce((a, b) => a + b, 0) / durations.length
|
||||
: 0
|
||||
|
||||
const date = m.uploadedAt
|
||||
? `${m.uploadedAt.split('T')[0]} ${m.uploadedAt.split('T')[1]?.slice(0, 5) || ''}`
|
||||
: m.runId.slice(0, 15)
|
||||
|
||||
const model = m.agentConfig?.model || 'unknown'
|
||||
const dataset = m.dataset || m.runId
|
||||
const agentType = m.agentConfig?.type || 'unknown'
|
||||
|
||||
const configName = extractConfigName(m.runId)
|
||||
return {
|
||||
runId: m.runId,
|
||||
configName,
|
||||
date,
|
||||
passRate,
|
||||
total,
|
||||
completed,
|
||||
failed,
|
||||
timeout,
|
||||
avgDurationMs,
|
||||
model,
|
||||
dataset,
|
||||
agentType,
|
||||
}
|
||||
})
|
||||
.sort((a, b) => a.date.localeCompare(b.date))
|
||||
|
||||
// Step 3: Identify unique config groups
|
||||
// runId can be "ci-weekly" (old) or "ci-weekly-2026-03-21-1730" (timestamped)
|
||||
// Extract config name by stripping the date-time suffix pattern
|
||||
function escHtml(s: string): string {
|
||||
return s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
function extractConfigName(runId: string): string {
|
||||
// "browseros-agent-weekly-2026-03-21-1730" → "browseros-agent-weekly"
|
||||
// "ci-weekly" → "ci-weekly" (no timestamp, old format)
|
||||
return runId.replace(/-\d{4}-\d{2}-\d{2}-\d{4}$/, '')
|
||||
}
|
||||
|
||||
const configGroups = [...new Set(runs.map((r) => r.configName))]
|
||||
const defaultConfig = configGroups.includes('ci-weekly')
|
||||
? 'ci-weekly'
|
||||
: configGroups[0]
|
||||
|
||||
// Step 4: Generate HTML report
|
||||
const html = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BrowserOS Eval Dashboard</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: #0d1117; color: #e6edf3; padding: 2rem; max-width: 1400px; margin: 0 auto; }
|
||||
|
||||
/* Header */
|
||||
.page-header { display: flex; align-items: center; gap: 16px; margin-bottom: 2rem; flex-wrap: wrap; }
|
||||
.page-header h1 { font-size: 1.5rem; }
|
||||
.page-header h1 span { color: #58a6ff; }
|
||||
.page-header .gen-date { color: #6e7681; font-size: 12px; margin-left: auto; }
|
||||
|
||||
/* Config selector */
|
||||
.config-bar { display: flex; align-items: center; gap: 16px; margin-bottom: 1.5rem; flex-wrap: wrap; }
|
||||
.config-bar label { font-size: 13px; color: #8b949e; font-weight: 600; }
|
||||
.config-bar select { background: #161b22; border: 1px solid #30363d; color: #e6edf3; padding: 8px 12px; border-radius: 6px; font-size: 13px; font-family: 'SF Mono', Consolas, monospace; cursor: pointer; min-width: 200px; }
|
||||
.config-bar select:focus { outline: none; border-color: #58a6ff; }
|
||||
|
||||
/* Config details card */
|
||||
.config-details { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px 20px; margin-bottom: 1.5rem; display: flex; gap: 32px; flex-wrap: wrap; }
|
||||
.config-detail { display: flex; flex-direction: column; gap: 2px; }
|
||||
.config-detail .cd-label { font-size: 10px; font-weight: 600; color: #6e7681; text-transform: uppercase; letter-spacing: 0.04em; }
|
||||
.config-detail .cd-value { font-size: 13px; color: #e6edf3; font-family: 'SF Mono', Consolas, monospace; }
|
||||
|
||||
/* Stat cards */
|
||||
.stats { display: flex; gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
|
||||
.stat-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 1.25rem; flex: 1; min-width: 140px; }
|
||||
.stat-label { color: #8b949e; font-size: 0.8rem; margin-bottom: 0.25rem; }
|
||||
.stat-value { font-size: 1.4rem; font-weight: 600; }
|
||||
.stat-value.big { font-size: 2.5rem; font-weight: 700; }
|
||||
.pass { color: #3fb950; }
|
||||
.fail { color: #f85149; }
|
||||
.neutral { color: #8b949e; }
|
||||
.trend-up { color: #3fb950; }
|
||||
.trend-down { color: #f85149; }
|
||||
.trend-flat { color: #8b949e; }
|
||||
|
||||
/* Chart */
|
||||
.chart-container { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; position: relative; }
|
||||
canvas { width: 100%; height: 300px; }
|
||||
#tooltip { display: none; position: absolute; background: #1c2128; border: 1px solid #30363d; border-radius: 6px; padding: 8px 12px; pointer-events: none; font-size: 12px; z-index: 10; box-shadow: 0 4px 12px rgba(0,0,0,0.4); }
|
||||
|
||||
/* Section headers */
|
||||
.section-header { display: flex; align-items: center; gap: 12px; margin-bottom: 1rem; }
|
||||
.section-header h2 { font-size: 1rem; font-weight: 600; }
|
||||
.section-header .search-input { margin-left: auto; background: #0d1117; border: 1px solid #30363d; color: #e6edf3; padding: 6px 12px; border-radius: 6px; font-size: 12px; font-family: inherit; width: 220px; }
|
||||
.section-header .search-input:focus { outline: none; border-color: #58a6ff; }
|
||||
.section-header .search-input::placeholder { color: #484f58; }
|
||||
|
||||
/* Table */
|
||||
table { width: 100%; border-collapse: collapse; background: #161b22; border: 1px solid #30363d; border-radius: 8px; overflow: hidden; }
|
||||
th, td { padding: 0.65rem 1rem; text-align: left; border-bottom: 1px solid #21262d; }
|
||||
th { background: #1c2128; color: #8b949e; font-weight: 600; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.03em; }
|
||||
td { font-size: 0.85rem; }
|
||||
td.mono { font-family: 'SF Mono', Consolas, monospace; font-size: 0.8rem; }
|
||||
a.view-link { color: #58a6ff; text-decoration: none; font-weight: 500; }
|
||||
a.view-link:hover { text-decoration: underline; }
|
||||
tr.hidden { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="page-header">
|
||||
<h1>BrowserOS <span>Eval Dashboard</span></h1>
|
||||
<span class="gen-date">Generated ${new Date().toISOString().split('T')[0]}</span>
|
||||
</div>
|
||||
|
||||
<!-- Config selector -->
|
||||
<div class="config-bar">
|
||||
<label>Config:</label>
|
||||
<select id="config-select">
|
||||
${configGroups.map((c) => `<option value="${escHtml(c)}"${c === defaultConfig ? ' selected' : ''}>${escHtml(c)}</option>`).join('\n ')}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Config details (populated by JS) -->
|
||||
<div class="config-details" id="config-details"></div>
|
||||
|
||||
<!-- Stat cards (populated by JS) -->
|
||||
<div class="stats" id="stat-cards"></div>
|
||||
|
||||
<!-- Chart -->
|
||||
<div class="chart-container">
|
||||
<canvas id="chart"></canvas>
|
||||
<div id="tooltip">
|
||||
<div id="tt-date" style="color:#8b949e;margin-bottom:2px;"></div>
|
||||
<div id="tt-score" style="font-size:16px;font-weight:700;"></div>
|
||||
<div id="tt-detail" style="color:#8b949e;margin-top:2px;font-size:11px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent runs table -->
|
||||
<div class="section-header">
|
||||
<h2>All Runs</h2>
|
||||
<input type="text" class="search-input" id="table-search" placeholder="Search runs..." autocomplete="off" spellcheck="false" />
|
||||
</div>
|
||||
<table id="runs-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Config</th>
|
||||
<th>Model</th>
|
||||
<th>Dataset</th>
|
||||
<th>Architecture</th>
|
||||
<th>Pass Rate</th>
|
||||
<th>Tasks</th>
|
||||
<th>Timeout</th>
|
||||
<th>Avg Duration</th>
|
||||
<th>View</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${runs
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((r) => {
|
||||
const viewerUrl = `viewer.html?run=${encodeURIComponent(r.runId)}`
|
||||
const passed = Math.round(r.passRate * r.total)
|
||||
const archLabel =
|
||||
r.agentType === 'orchestrator-executor'
|
||||
? 'Orch-Exec'
|
||||
: r.agentType === 'single'
|
||||
? 'Tool Loop'
|
||||
: r.agentType === 'gemini-computer-use'
|
||||
? 'Gemini CU'
|
||||
: r.agentType || '—'
|
||||
return `<tr data-config="${escHtml(r.runId)}" data-search="${escHtml(`${r.date} ${r.runId} ${r.model} ${r.dataset} ${archLabel}`)}">
|
||||
<td>${escHtml(r.date)}</td>
|
||||
<td class="mono">${escHtml(r.runId)}</td>
|
||||
<td class="mono" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escHtml(r.model)}">${escHtml(r.model)}</td>
|
||||
<td>${escHtml(r.dataset)}</td>
|
||||
<td>${escHtml(archLabel)}</td>
|
||||
<td class="${r.passRate >= 0.7 ? 'pass' : r.passRate >= 0.4 ? 'neutral' : 'fail'}">${(r.passRate * 100).toFixed(1)}% <span style="color:#6e7681;font-size:11px;">(${passed}/${r.total})</span></td>
|
||||
<td>${r.total}</td>
|
||||
<td class="${r.timeout > 0 ? 'neutral' : ''}">${r.timeout}</td>
|
||||
<td>${(r.avgDurationMs / 1000).toFixed(0)}s</td>
|
||||
<td><a href="${viewerUrl}" class="view-link">View →</a></td>
|
||||
</tr>`
|
||||
})
|
||||
.join('\n')}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var allRuns = ${JSON.stringify(runs)};
|
||||
var configSelect = document.getElementById('config-select');
|
||||
var canvas = document.getElementById('chart');
|
||||
var ctx = canvas.getContext('2d');
|
||||
var tooltip = document.getElementById('tooltip');
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
var dotPositions = [];
|
||||
|
||||
function getFilteredRuns() {
|
||||
var cfg = configSelect.value;
|
||||
return allRuns.filter(function(r) { return r.configName === cfg; });
|
||||
}
|
||||
|
||||
function updateDashboard() {
|
||||
var runs = getFilteredRuns();
|
||||
renderConfigDetails(runs);
|
||||
renderStatCards(runs);
|
||||
drawChart(runs);
|
||||
}
|
||||
|
||||
// Config details card
|
||||
function renderConfigDetails(runs) {
|
||||
var el = document.getElementById('config-details');
|
||||
if (runs.length === 0) { el.innerHTML = '<span style="color:#6e7681;">No runs found for this config.</span>'; return; }
|
||||
var latest = runs[runs.length - 1];
|
||||
var archLabel = latest.agentType === 'orchestrator-executor' ? 'Orchestrator-Executor'
|
||||
: latest.agentType === 'single' ? 'Single Agent (Tool Loop)'
|
||||
: latest.agentType === 'gemini-computer-use' ? 'Gemini Computer Use'
|
||||
: latest.agentType || 'Unknown';
|
||||
el.innerHTML =
|
||||
'<div class="config-detail"><span class="cd-label">Architecture</span><span class="cd-value">' + archLabel + '</span></div>' +
|
||||
'<div class="config-detail"><span class="cd-label">Model</span><span class="cd-value">' + (latest.model || 'unknown') + '</span></div>' +
|
||||
'<div class="config-detail"><span class="cd-label">Dataset</span><span class="cd-value">' + (latest.dataset || 'unknown') + '</span></div>' +
|
||||
'<div class="config-detail"><span class="cd-label">Tasks</span><span class="cd-value">' + latest.total + '</span></div>' +
|
||||
'<div class="config-detail"><span class="cd-label">Runs</span><span class="cd-value">' + runs.length + '</span></div>';
|
||||
}
|
||||
|
||||
// Stat cards
|
||||
function renderStatCards(runs) {
|
||||
var el = document.getElementById('stat-cards');
|
||||
if (runs.length === 0) { el.innerHTML = ''; return; }
|
||||
var latest = runs[runs.length - 1];
|
||||
var prev = runs.length >= 2 ? runs[runs.length - 2] : null;
|
||||
var best = Math.max.apply(null, runs.map(function(r) { return r.passRate; }));
|
||||
var delta = prev ? latest.passRate - prev.passRate : 0;
|
||||
var sign = delta > 0 ? '+' : '';
|
||||
var trendCls = delta > 0 ? 'trend-up' : delta < 0 ? 'trend-down' : 'trend-flat';
|
||||
|
||||
el.innerHTML =
|
||||
'<div class="stat-card"><div class="stat-label">Latest Pass Rate</div><div class="stat-value big ' + (latest.passRate >= 0.7 ? 'pass' : 'fail') + '">' + (latest.passRate * 100).toFixed(1) + '%</div></div>' +
|
||||
'<div class="stat-card"><div class="stat-label">Trend</div><div class="stat-value ' + trendCls + '">' + (prev ? sign + (delta * 100).toFixed(1) + ' pp' : 'N/A') + '</div></div>' +
|
||||
'<div class="stat-card"><div class="stat-label">Best Score</div><div class="stat-value pass">' + (best * 100).toFixed(1) + '%</div></div>' +
|
||||
'<div class="stat-card"><div class="stat-label">Avg Duration</div><div class="stat-value">' + (latest.avgDurationMs / 1000).toFixed(0) + 's</div></div>' +
|
||||
'<div class="stat-card"><div class="stat-label">Runs</div><div class="stat-value">' + runs.length + '</div></div>';
|
||||
}
|
||||
|
||||
// Chart
|
||||
function drawChart(runs) {
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
canvas.width = rect.width * dpr;
|
||||
canvas.height = rect.height * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
|
||||
var W = rect.width, H = rect.height;
|
||||
var pad = { top: 20, right: 20, bottom: 50, left: 50 };
|
||||
var plotW = W - pad.left - pad.right;
|
||||
var plotH = H - pad.top - pad.bottom;
|
||||
dotPositions = [];
|
||||
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
if (runs.length === 0) {
|
||||
ctx.fillStyle = '#8b949e';
|
||||
ctx.font = '14px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('No data for this config', W / 2, H / 2);
|
||||
return;
|
||||
}
|
||||
|
||||
var scores = runs.map(function(r) { return r.passRate * 100; });
|
||||
var minY = Math.max(0, Math.floor(Math.min.apply(null, scores) / 10) * 10 - 10);
|
||||
var maxY = Math.min(100, Math.ceil(Math.max.apply(null, scores) / 10) * 10 + 10);
|
||||
if (minY === maxY) { minY = Math.max(0, minY - 10); maxY = Math.min(100, maxY + 10); }
|
||||
|
||||
// Grid
|
||||
ctx.strokeStyle = '#21262d';
|
||||
ctx.lineWidth = 1;
|
||||
for (var y = minY; y <= maxY; y += 10) {
|
||||
var py = pad.top + plotH - ((y - minY) / (maxY - minY)) * plotH;
|
||||
ctx.beginPath(); ctx.moveTo(pad.left, py); ctx.lineTo(pad.left + plotW, py); ctx.stroke();
|
||||
ctx.fillStyle = '#8b949e'; ctx.font = '11px sans-serif'; ctx.textAlign = 'right';
|
||||
ctx.fillText(y + '%', pad.left - 8, py + 4);
|
||||
}
|
||||
|
||||
// X labels
|
||||
ctx.fillStyle = '#8b949e'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center';
|
||||
runs.forEach(function(r, i) {
|
||||
var px = pad.left + (runs.length === 1 ? plotW / 2 : (i / (runs.length - 1)) * plotW);
|
||||
ctx.save(); ctx.translate(px, pad.top + plotH + 15); ctx.rotate(-Math.PI / 6);
|
||||
ctx.fillText(r.date, 0, 0); ctx.restore();
|
||||
});
|
||||
|
||||
// Line
|
||||
ctx.strokeStyle = '#58a6ff'; ctx.lineWidth = 2; ctx.beginPath();
|
||||
runs.forEach(function(r, i) {
|
||||
var px = pad.left + (runs.length === 1 ? plotW / 2 : (i / (runs.length - 1)) * plotW);
|
||||
var py2 = pad.top + plotH - ((r.passRate * 100 - minY) / (maxY - minY)) * plotH;
|
||||
if (i === 0) ctx.moveTo(px, py2); else ctx.lineTo(px, py2);
|
||||
});
|
||||
ctx.stroke();
|
||||
|
||||
// Dots
|
||||
runs.forEach(function(r, i) {
|
||||
var px = pad.left + (runs.length === 1 ? plotW / 2 : (i / (runs.length - 1)) * plotW);
|
||||
var py2 = pad.top + plotH - ((r.passRate * 100 - minY) / (maxY - minY)) * plotH;
|
||||
dotPositions.push({ x: px, y: py2, run: r });
|
||||
ctx.beginPath(); ctx.arc(px, py2, 4, 0, Math.PI * 2);
|
||||
ctx.fillStyle = r.passRate >= 0.7 ? '#3fb950' : '#f85149';
|
||||
ctx.fill(); ctx.strokeStyle = '#0d1117'; ctx.lineWidth = 2; ctx.stroke();
|
||||
});
|
||||
}
|
||||
|
||||
// Tooltip
|
||||
canvas.addEventListener('mousemove', function(e) {
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
var mx = e.clientX - rect.left, my = e.clientY - rect.top;
|
||||
var closest = null, closestDist = Infinity;
|
||||
dotPositions.forEach(function(dot) {
|
||||
var d = Math.sqrt(Math.pow(mx - dot.x, 2) + Math.pow(my - dot.y, 2));
|
||||
if (d < closestDist) { closestDist = d; closest = dot; }
|
||||
});
|
||||
|
||||
if (closest && closestDist < 40) {
|
||||
var r = closest.run;
|
||||
var passed = Math.round(r.passRate * r.total);
|
||||
document.getElementById('tt-date').textContent = r.date;
|
||||
document.getElementById('tt-score').textContent = (r.passRate * 100).toFixed(1) + '%';
|
||||
document.getElementById('tt-score').style.color = r.passRate >= 0.7 ? '#3fb950' : '#f85149';
|
||||
document.getElementById('tt-detail').textContent = passed + '/' + r.total + ' pass \\u00B7 ' + (r.avgDurationMs / 1000).toFixed(0) + 's avg \\u00B7 ' + r.model;
|
||||
tooltip.style.display = 'block';
|
||||
|
||||
var tx = closest.x + 12, ty = closest.y - 50;
|
||||
if (tx + 200 > rect.width) tx = closest.x - 210;
|
||||
if (ty < 0) ty = closest.y + 12;
|
||||
tooltip.style.left = tx + 'px'; tooltip.style.top = ty + 'px';
|
||||
|
||||
// Highlight dot
|
||||
drawChart(getFilteredRuns());
|
||||
ctx.beginPath(); ctx.arc(closest.x, closest.y, 7, 0, Math.PI * 2);
|
||||
ctx.fillStyle = 'rgba(88, 166, 255, 0.3)'; ctx.fill();
|
||||
ctx.beginPath(); ctx.arc(closest.x, closest.y, 5, 0, Math.PI * 2);
|
||||
ctx.fillStyle = r.passRate >= 0.7 ? '#3fb950' : '#f85149'; ctx.fill();
|
||||
ctx.strokeStyle = '#e6edf3'; ctx.lineWidth = 2; ctx.stroke();
|
||||
canvas.style.cursor = 'pointer';
|
||||
} else {
|
||||
tooltip.style.display = 'none';
|
||||
canvas.style.cursor = 'default';
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseleave', function() {
|
||||
tooltip.style.display = 'none';
|
||||
drawChart(getFilteredRuns());
|
||||
});
|
||||
|
||||
canvas.addEventListener('click', function(e) {
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
var mx = e.clientX - rect.left, my = e.clientY - rect.top;
|
||||
dotPositions.forEach(function(dot) {
|
||||
if (Math.sqrt(Math.pow(mx - dot.x, 2) + Math.pow(my - dot.y, 2)) < 20) {
|
||||
window.open('viewer.html?run=' + encodeURIComponent(dot.run.runId), '_blank');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Config selector change
|
||||
configSelect.addEventListener('change', function() {
|
||||
tooltip.style.display = 'none';
|
||||
updateDashboard();
|
||||
});
|
||||
|
||||
// Table search
|
||||
document.getElementById('table-search').addEventListener('input', function(e) {
|
||||
var q = e.target.value.toLowerCase();
|
||||
var rows = document.querySelectorAll('#runs-table tbody tr');
|
||||
rows.forEach(function(row) {
|
||||
var searchText = row.getAttribute('data-search') || '';
|
||||
row.classList.toggle('hidden', q && searchText.toLowerCase().indexOf(q) === -1);
|
||||
});
|
||||
});
|
||||
|
||||
// Resize
|
||||
window.addEventListener('resize', function() { tooltip.style.display = 'none'; drawChart(getFilteredRuns()); });
|
||||
|
||||
// Init
|
||||
updateDashboard();
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
// Step 5: Save locally and upload to R2
|
||||
const localPath = process.argv[2] || '/tmp/eval-report.html'
|
||||
await writeFile(localPath, html)
|
||||
console.log(`Report saved locally: ${localPath}`)
|
||||
|
||||
await client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: 'report.html',
|
||||
Body: html,
|
||||
ContentType: 'text/html',
|
||||
CacheControl: 'public, max-age=300',
|
||||
}),
|
||||
)
|
||||
|
||||
const cdnBaseUrl = (
|
||||
process.env.EVAL_R2_CDN_BASE_URL || 'https://eval.browseros.com'
|
||||
).replace(/\/+$/, '')
|
||||
|
||||
console.log(`Report uploaded to R2: ${bucket}/report.html`)
|
||||
console.log(` View at: ${cdnBaseUrl}/report.html`)
|
||||
|
||||
// Print summary
|
||||
console.log('\nScore trend:')
|
||||
for (const run of runs.slice(-10)) {
|
||||
const bar = '\u2588'.repeat(Math.round(run.passRate * 20))
|
||||
const pct = (run.passRate * 100).toFixed(0).padStart(3)
|
||||
console.log(` ${run.date} ${pct}% ${bar}`)
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* Eval-specific constants shared across agents, runners, and capture modules.
|
||||
*/
|
||||
|
||||
export const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000 // 15 minutes
|
||||
export const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000 // 30 minutes
|
||||
export const SCREENSHOT_TIMEOUT_MS = 65_000 // 65s — ensures we get extension's error (60s)
|
||||
export const MAX_ACTIONS_PER_DELEGATION = 15
|
||||
export const CLADO_REQUEST_TIMEOUT_MS = 120_000
|
||||
|
||||
@@ -354,7 +354,7 @@
|
||||
</div>
|
||||
<div class="config-field">
|
||||
<label>Timeout (ms)</label>
|
||||
<input type="number" id="cfg-timeout" value="600000" min="30000" max="3600000">
|
||||
<input type="number" id="cfg-timeout" value="1800000" min="30000" max="3600000">
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-row" style="gap: 16px;">
|
||||
@@ -454,6 +454,17 @@
|
||||
<button class="btn-run" id="btn-run" onclick="submitConfig()">Run Eval</button>
|
||||
</div>
|
||||
|
||||
<!-- Load previous run -->
|
||||
<div class="config-actions">
|
||||
<div class="load-config">
|
||||
<label>Load run:</label>
|
||||
<select id="cfg-run-select">
|
||||
<option value="">-- select --</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn-secondary" onclick="loadPreviousRun()">Load Run</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -529,6 +540,7 @@ async function init() {
|
||||
|
||||
// Load saved configs into dropdown
|
||||
loadConfigList();
|
||||
loadRunList();
|
||||
|
||||
if (mode.configMode) {
|
||||
// Config mode — show panel expanded
|
||||
@@ -656,6 +668,53 @@ async function loadConfigList() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function loadRunList() {
|
||||
try {
|
||||
const res = await fetch('/api/runs');
|
||||
const runs = await res.json();
|
||||
const sel = document.getElementById('cfg-run-select');
|
||||
sel.innerHTML = '<option value="">-- select --</option>';
|
||||
runs.forEach(r => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = r;
|
||||
opt.textContent = r;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function loadPreviousRun() {
|
||||
const runName = document.getElementById('cfg-run-select').value;
|
||||
if (!runName) return;
|
||||
const errEl = document.getElementById('config-error');
|
||||
errEl.textContent = '';
|
||||
try {
|
||||
const res = await fetch('/api/load-run', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ runName }),
|
||||
});
|
||||
const result = await res.json();
|
||||
if (!res.ok) {
|
||||
errEl.textContent = result.error || 'Failed to load run';
|
||||
return;
|
||||
}
|
||||
const stateRes = await fetch('/api/state');
|
||||
const state = await stateRes.json();
|
||||
document.getElementById('config-name').textContent =
|
||||
state.configName ? `${state.configName} \u00B7 ${state.agentType}` : '';
|
||||
tasks = state.tasks;
|
||||
setConfigPanelOpen(false);
|
||||
updateConfigSummary(state.configName, state.agentType);
|
||||
selectedTaskId = null;
|
||||
renderTaskList();
|
||||
updateProgress();
|
||||
if (tasks.length > 0) selectTask(tasks[0].queryId);
|
||||
} catch (e) {
|
||||
errEl.textContent = `Network error: ${e.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSavedConfig(name) {
|
||||
if (!name) return;
|
||||
try {
|
||||
@@ -1055,7 +1114,8 @@ function renderTaskList() {
|
||||
if (t.graderResults) {
|
||||
const primary = getPrimaryGrader(t.graderResults);
|
||||
if (primary) {
|
||||
graderBadge = `<span class="grade-badge ${primary.pass ? 'pass' : 'fail'}">${primary.pass ? 'PASS' : 'FAIL'}</span>`;
|
||||
const pct = typeof primary.score === 'number' ? `${(primary.score * 100).toFixed(0)}%` : (primary.pass ? 'PASS' : 'FAIL');
|
||||
graderBadge = `<span class="grade-badge ${primary.pass ? 'pass' : 'fail'}">${pct}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1144,12 +1204,35 @@ function toggleAutoplay() {
|
||||
// ============================================================================
|
||||
// Agent Stream
|
||||
// ============================================================================
|
||||
function renderStreamForTask(taskId) {
|
||||
async function renderStreamForTask(taskId) {
|
||||
const body = document.getElementById('stream-body');
|
||||
body.innerHTML = '';
|
||||
const events = streamEvents[taskId] || [];
|
||||
events.forEach(e => appendStreamEntry(e, false));
|
||||
body.scrollTop = body.scrollHeight;
|
||||
if (events.length > 0) {
|
||||
events.forEach(e => appendStreamEntry(e, false));
|
||||
body.scrollTop = body.scrollHeight;
|
||||
return;
|
||||
}
|
||||
const task = tasks.find(t => t.queryId === taskId);
|
||||
if (!task || task.status === 'pending' || task.status === 'running') return;
|
||||
body.innerHTML = '<div class="empty-state">Loading events...</div>';
|
||||
try {
|
||||
const res = await fetch(`/api/messages/${taskId}`);
|
||||
if (!res.ok) {
|
||||
body.innerHTML = '<div class="empty-state">No event log available</div>';
|
||||
return;
|
||||
}
|
||||
const text = await res.text();
|
||||
const parsed = text.trim().split('\n').filter(Boolean).map(line => {
|
||||
try { return JSON.parse(line); } catch { return null; }
|
||||
}).filter(Boolean);
|
||||
streamEvents[taskId] = parsed;
|
||||
body.innerHTML = '';
|
||||
parsed.forEach(e => appendStreamEntry(e, false));
|
||||
body.scrollTop = body.scrollHeight;
|
||||
} catch {
|
||||
body.innerHTML = '<div class="empty-state">Failed to load events</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function appendStreamEntry(event, scroll = true) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mkdir, readdir, readFile } from 'node:fs/promises'
|
||||
import { mkdir, readdir, readFile, stat } from 'node:fs/promises'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { Hono } from 'hono'
|
||||
import { streamSSE } from 'hono/streaming'
|
||||
@@ -199,6 +199,133 @@ app.get('/api/screenshots/:taskId/:index', async (c) => {
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/api/messages/:taskId', async (c) => {
|
||||
const { taskId } = c.req.param()
|
||||
if (taskId.includes('..') || taskId.includes('/')) {
|
||||
return c.json({ error: 'Invalid parameters' }, 400)
|
||||
}
|
||||
const filepath = join(dashboardState.outputDir, taskId, 'messages.jsonl')
|
||||
const resolved = resolve(filepath)
|
||||
if (!resolved.startsWith(resolve(dashboardState.outputDir))) {
|
||||
return c.json({ error: 'Invalid path' }, 400)
|
||||
}
|
||||
try {
|
||||
const file = Bun.file(filepath)
|
||||
if (!(await file.exists())) return c.notFound()
|
||||
const data = await file.arrayBuffer()
|
||||
return c.body(data, 200, {
|
||||
'Content-Type': 'application/x-ndjson',
|
||||
'Cache-Control': 'no-cache',
|
||||
})
|
||||
} catch {
|
||||
return c.notFound()
|
||||
}
|
||||
})
|
||||
|
||||
const resultsDir = join(import.meta.dir, '..', '..', 'results')
|
||||
|
||||
app.get('/api/runs', async (c) => {
|
||||
try {
|
||||
const runs: string[] = []
|
||||
const entries = await readdir(resultsDir, { withFileTypes: true })
|
||||
for (const entry of entries.filter((e) => e.isDirectory())) {
|
||||
const subEntries = await readdir(join(resultsDir, entry.name), {
|
||||
withFileTypes: true,
|
||||
}).catch(() => [] as import('node:fs').Dirent[])
|
||||
const hasTimestampDirs = subEntries.some(
|
||||
(s) => s.isDirectory() && /^\d{4}-\d{2}-\d{2}-\d{4}$/.test(s.name),
|
||||
)
|
||||
if (hasTimestampDirs) {
|
||||
for (const sub of subEntries.filter((s) => s.isDirectory())) {
|
||||
runs.push(`${entry.name}/${sub.name}`)
|
||||
}
|
||||
} else {
|
||||
runs.push(entry.name)
|
||||
}
|
||||
}
|
||||
runs.sort().reverse()
|
||||
return c.json(runs)
|
||||
} catch {
|
||||
return c.json([])
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/api/load-run', async (c) => {
|
||||
if (evalRunning)
|
||||
return c.json({ error: 'Cannot load while eval is running' }, 409)
|
||||
let body: { runName: string }
|
||||
try {
|
||||
body = await c.req.json()
|
||||
} catch {
|
||||
return c.json({ error: 'Invalid JSON body' }, 400)
|
||||
}
|
||||
const runName = body.runName
|
||||
if (!runName || runName.includes('..')) {
|
||||
return c.json({ error: 'Invalid run name' }, 400)
|
||||
}
|
||||
if ((runName.match(/\//g) || []).length > 1) {
|
||||
return c.json({ error: 'Invalid run name' }, 400)
|
||||
}
|
||||
const outputDir = resolve(resultsDir, runName)
|
||||
if (!outputDir.startsWith(resolve(resultsDir))) {
|
||||
return c.json({ error: 'Invalid path' }, 400)
|
||||
}
|
||||
const dirStat = await stat(outputDir).catch(() => null)
|
||||
if (!dirStat?.isDirectory()) {
|
||||
return c.json({ error: 'Run directory not found' }, 404)
|
||||
}
|
||||
const entries = await readdir(outputDir, { withFileTypes: true })
|
||||
const taskDirs = entries.filter((e) => e.isDirectory())
|
||||
const loadedTasks: DashboardTask[] = []
|
||||
let agentType = ''
|
||||
for (const taskDir of taskDirs) {
|
||||
const metaPath = join(outputDir, taskDir.name, 'metadata.json')
|
||||
try {
|
||||
const raw = JSON.parse(await readFile(metaPath, 'utf-8'))
|
||||
if (!agentType && raw.agent_config?.type) {
|
||||
agentType = raw.agent_config.type
|
||||
}
|
||||
const screenshotDir = join(outputDir, taskDir.name, 'screenshots')
|
||||
let screenshotCount = raw.screenshot_count ?? 0
|
||||
if (!screenshotCount) {
|
||||
try {
|
||||
const files = await readdir(screenshotDir)
|
||||
screenshotCount = files.filter((f: string) =>
|
||||
f.endsWith('.png'),
|
||||
).length
|
||||
} catch {}
|
||||
}
|
||||
loadedTasks.push({
|
||||
queryId: raw.query_id || taskDir.name,
|
||||
query: raw.query || '',
|
||||
startUrl: raw.start_url,
|
||||
status:
|
||||
raw.termination_reason === 'completed'
|
||||
? 'completed'
|
||||
: raw.termination_reason === 'timeout'
|
||||
? 'timeout'
|
||||
: 'failed',
|
||||
durationMs: raw.total_duration_ms,
|
||||
graderResults: raw.grader_results,
|
||||
screenshotCount,
|
||||
})
|
||||
} catch {}
|
||||
}
|
||||
if (loadedTasks.length === 0) {
|
||||
return c.json({ error: 'No completed tasks found in this run' }, 404)
|
||||
}
|
||||
dashboardState.configName = runName
|
||||
dashboardState.agentType = agentType
|
||||
dashboardState.outputDir = outputDir
|
||||
dashboardState.tasks = loadedTasks
|
||||
return c.json({
|
||||
status: 'loaded',
|
||||
configName: runName,
|
||||
agentType,
|
||||
taskCount: loadedTasks.length,
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Config & Run API
|
||||
// ============================================================================
|
||||
|
||||
1270
packages/browseros-agent/apps/eval/src/dashboard/viewer.html
Normal file
1270
packages/browseros-agent/apps/eval/src/dashboard/viewer.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -53,8 +53,8 @@ The raw event stream — one JSON object per line with a "type" field.
|
||||
- "tool-output-error" / "tool-input-error" — Tool call failed. Fields: toolCallId, error.
|
||||
- "text-delta" — Agent's reasoning text. Field: delta (small text chunk).
|
||||
|
||||
**Event types to AVOID reading:**
|
||||
- "tool-output-available" — Tool output. The "output" field contains FULL PAGE DOM CONTENT — hundreds of interactive elements, entire page text, etc. These lines are 5-50KB each. NEVER read them. The tool-input-available lines already tell you what the agent did. Screenshots show you the visual result.
|
||||
**Event types to handle carefully:**
|
||||
- "tool-output-available" — Tool output. The "output" field contains FULL PAGE DOM CONTENT — hundreds of interactive elements, entire page text, etc. These lines are 5-50KB each. NEVER read them in bulk. However, you CAN and SHOULD use Grep to search within these lines for specific keywords when screenshots alone can't verify a claim. For example, if the task asks "find the price of X" and the screenshot is unclear, grep messages.jsonl for the product name or price value to confirm the agent actually saw it in the DOM.
|
||||
|
||||
### 2. screenshots/ directory
|
||||
Numbered PNG screenshots (1.png, 2.png, ...) captured after each tool execution.
|
||||
@@ -95,6 +95,13 @@ Grep for "tool-output-error" or "tool-input-error". If none found, zero errors.
|
||||
**Step 3: Sample reasoning (only if needed for reasoning_quality)**
|
||||
Grep for "text-delta" but LIMIT to the first 10 and last 10 results. Don't read all reasoning text.
|
||||
|
||||
**Step 4: Verify claims from DOM content (critical for task_completion)**
|
||||
When the agent's final answer contains specific data (prices, names, dates, counts, etc.) that you can't confirm from screenshots alone, use Grep to search messages.jsonl for those specific values or keywords. This searches the tool-output-available lines which contain DOM content the agent actually saw. For example:
|
||||
- Task asks "find cheapest flight price" → grep for the dollar amount from the final answer
|
||||
- Task asks "list the top 3 articles" → grep for the article titles mentioned in the answer
|
||||
- Task asks "extract the email address" → grep for the email pattern
|
||||
This is the most reliable way to verify whether the agent actually found the data it claims, since screenshots may be blurry, truncated, or missing the relevant section.
|
||||
|
||||
## How to View Screenshots
|
||||
|
||||
You have {screenshot_count} screenshots. View 3-5 strategically:
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
type PerformanceGraderOptions,
|
||||
} from './types'
|
||||
|
||||
export const DEFAULT_MAX_TURNS = 15
|
||||
export const DEFAULT_MAX_BUDGET_USD = 0.5
|
||||
export const DEFAULT_MAX_TURNS = 100
|
||||
export const DEFAULT_MAX_BUDGET_USD = 100
|
||||
export const DEFAULT_PASS_THRESHOLD = 75
|
||||
const DEFAULT_MODEL = 'claude-opus-4-5-20251101'
|
||||
const GRADER_TIMEOUT_MS = 300_000
|
||||
|
||||
@@ -30,9 +30,15 @@ const MONOREPO_ROOT = join(
|
||||
'../../../..',
|
||||
)
|
||||
|
||||
const BROWSEROS_BINARY = '/Applications/BrowserOS.app/Contents/MacOS/BrowserOS'
|
||||
const BROWSEROS_BINARY =
|
||||
process.env.BROWSEROS_BINARY ||
|
||||
'/Applications/BrowserOS.app/Contents/MacOS/BrowserOS'
|
||||
|
||||
const CONTROLLER_EXT_DIR = join(MONOREPO_ROOT, 'apps/controller-ext/dist')
|
||||
const CAPTCHA_EXT_DIR = join(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
'../../../extensions/nopecha',
|
||||
)
|
||||
|
||||
export class BrowserOSAppManager {
|
||||
private ports: EvalPorts
|
||||
@@ -152,8 +158,15 @@ export class BrowserOSAppManager {
|
||||
`--user-data-dir=${this.tempDir}`,
|
||||
]
|
||||
|
||||
const extensions: string[] = []
|
||||
if (this.loadExtensions && existsSync(CONTROLLER_EXT_DIR)) {
|
||||
chromeArgs.push(`--load-extension=${CONTROLLER_EXT_DIR}`)
|
||||
extensions.push(CONTROLLER_EXT_DIR)
|
||||
}
|
||||
if (existsSync(CAPTCHA_EXT_DIR)) {
|
||||
extensions.push(CAPTCHA_EXT_DIR)
|
||||
}
|
||||
if (extensions.length > 0) {
|
||||
chromeArgs.push(`--load-extension=${extensions.join(',')}`)
|
||||
}
|
||||
|
||||
chromeArgs.push('about:blank')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { mkdir, writeFile } from 'node:fs/promises'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { basename, dirname, join, resolve } from 'node:path'
|
||||
import {
|
||||
dashboardState,
|
||||
setActiveExecutor,
|
||||
@@ -120,18 +120,31 @@ function resolvePaths(
|
||||
? config.dataset
|
||||
: resolve(configDir, config.dataset)
|
||||
|
||||
// Resolve output directory: use options.outputDir if provided, otherwise resolve from config
|
||||
const outputDir = options.outputDir
|
||||
? options.outputDir
|
||||
: config.output_dir
|
||||
? config.output_dir.startsWith('/')
|
||||
? config.output_dir
|
||||
: resolve(configDir, config.output_dir)
|
||||
: resolve(configDir, 'results')
|
||||
// Resolve output directory: results/{config-name}/{timestamp}/
|
||||
// Config name derived from config filename (e.g., "browseros-agent-weekly.json" → "browseros-agent-weekly")
|
||||
const configName = options.configPath
|
||||
? basename(resolve(options.configPath), '.json')
|
||||
: 'eval'
|
||||
const timestamp = formatTimestamp(new Date())
|
||||
const resultsBase = config.output_dir
|
||||
? config.output_dir.startsWith('/')
|
||||
? config.output_dir
|
||||
: resolve(configDir, config.output_dir)
|
||||
: resolve(configDir, '..', 'results')
|
||||
const outputDir = join(resultsBase, configName, timestamp)
|
||||
|
||||
return { dataPath, outputDir }
|
||||
}
|
||||
|
||||
function formatTimestamp(date: Date): string {
|
||||
const y = date.getFullYear()
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const d = String(date.getDate()).padStart(2, '0')
|
||||
const h = String(date.getHours()).padStart(2, '0')
|
||||
const min = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${y}-${m}-${d}-${h}${min}`
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Task Loading
|
||||
// ============================================================================
|
||||
@@ -229,6 +242,12 @@ function printTaskProgress(
|
||||
if (result.status === 'failed') {
|
||||
console.log(` ERROR: ${result.error.message}`)
|
||||
} else if (isSuccessfulResult(result)) {
|
||||
// Log agent errors (e.g., LLM API failures) even if task "completed"
|
||||
if (result.agentResult.metadata.errors?.length) {
|
||||
for (const err of result.agentResult.metadata.errors) {
|
||||
console.log(` ERROR [${err.source}]: ${err.message}`)
|
||||
}
|
||||
}
|
||||
for (const [name, gr] of Object.entries(result.graderResults)) {
|
||||
const icon = gr.pass ? 'PASS' : 'FAIL'
|
||||
console.log(` ${name}: ${icon}`)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@browseros/server",
|
||||
"version": "0.0.78",
|
||||
"version": "0.0.79",
|
||||
"description": "BrowserOS server",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
@@ -89,11 +89,11 @@
|
||||
"fuse.js": "^7.1.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"hono": "^4.12.3",
|
||||
"jimp": "^1.6.0",
|
||||
"klavis": "^2.15.0",
|
||||
"pino": "^9.6.0",
|
||||
"posthog-node": "^4.17.0",
|
||||
"puppeteer-core": "24.23.0",
|
||||
"sharp": "^0.34.5",
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.24.2",
|
||||
"zod-from-json-schema": "^0.1.0"
|
||||
|
||||
@@ -50,11 +50,19 @@ export function createOAuthRoutes(deps: OAuthRouteDeps) {
|
||||
provider: providerId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Failed to start authentication. Please try again.'
|
||||
return c.json({ error: message }, 500)
|
||||
: 'Failed to start authentication.'
|
||||
|
||||
// Port conflict — clear actionable message
|
||||
const status =
|
||||
error instanceof Error && error.message.includes('callback port')
|
||||
? 503
|
||||
: 500
|
||||
|
||||
return c.json({ error: message }, status)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ export async function createHttpServer(config: HttpServerConfig) {
|
||||
|
||||
const { onShutdown } = config
|
||||
|
||||
// Initialize OAuth token manager + callback server (port released on process exit)
|
||||
// Initialize OAuth token manager (callback server binds lazily on first PKCE login)
|
||||
const tokenManager = browserosId
|
||||
? initializeOAuth(getDb(), browserosId)
|
||||
: null
|
||||
@@ -110,6 +110,7 @@ export async function createHttpServer(config: HttpServerConfig) {
|
||||
'/shutdown',
|
||||
createShutdownRoute({
|
||||
onShutdown: () => {
|
||||
tokenManager?.stopCallbackServer()
|
||||
klavisProxy?.close().catch((err) =>
|
||||
logger.warn('Failed to close Klavis proxy transport', {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { mkdir, readdir, rm, stat } from 'node:fs/promises'
|
||||
import { unlinkSync } from 'node:fs'
|
||||
import { mkdir, readdir, rm, stat, writeFile } from 'node:fs/promises'
|
||||
import { homedir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { PATHS } from '@browseros/shared/constants/paths'
|
||||
import type { ServerDiscoveryConfig } from '@browseros/shared/types/server-config'
|
||||
import { logger } from './logger'
|
||||
|
||||
export function getBrowserosDir(): string {
|
||||
@@ -32,6 +34,24 @@ export function getBuiltinSkillsDir(): string {
|
||||
return join(getSkillsDir(), PATHS.BUILTIN_DIR_NAME)
|
||||
}
|
||||
|
||||
export function getServerConfigPath(): string {
|
||||
return join(getBrowserosDir(), PATHS.SERVER_CONFIG_FILE_NAME)
|
||||
}
|
||||
|
||||
export async function writeServerConfig(
|
||||
config: ServerDiscoveryConfig,
|
||||
): Promise<void> {
|
||||
await writeFile(getServerConfigPath(), `${JSON.stringify(config, null, 2)}\n`)
|
||||
}
|
||||
|
||||
export function removeServerConfigSync(): void {
|
||||
try {
|
||||
unlinkSync(getServerConfigPath())
|
||||
} catch {
|
||||
// File may not exist or already be removed
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureBrowserosDir(): Promise<void> {
|
||||
await mkdir(getMemoryDir(), { recursive: true })
|
||||
await mkdir(getSkillsDir(), { recursive: true })
|
||||
|
||||
@@ -3,68 +3,164 @@
|
||||
* Copyright 2025 BrowserOS
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*
|
||||
* Temporary HTTP server on port 1455 for OAuth callbacks.
|
||||
* OpenAI's OAuth requires redirect_uri to use this specific port
|
||||
* (matching the Codex CLI client ID registration).
|
||||
* Lazy OAuth callback server on port 1455.
|
||||
*
|
||||
* Port 1455 is required by OpenAI's Codex CLI OAuth client registration
|
||||
* (redirect_uri must be http://localhost:1455/auth/callback).
|
||||
*
|
||||
* Unlike the old implementation that bound the port at startup, this class:
|
||||
* - Only binds when the user initiates a PKCE login flow
|
||||
* - Sends GET /cancel to any existing server on the port first (Codex CLI pattern)
|
||||
* - Exposes /cancel so other instances can cancel us
|
||||
* - Releases the port after the callback arrives and no flows are pending
|
||||
*/
|
||||
|
||||
import { OAUTH_CALLBACK_PORT } from '@browseros/shared/constants/ports'
|
||||
import { logger } from '../../logger'
|
||||
import type { OAuthTokenManager } from './token-manager'
|
||||
|
||||
export function startOAuthCallbackServer(tokenManager: OAuthTokenManager): {
|
||||
stop: () => void
|
||||
} {
|
||||
const server = Bun.serve({
|
||||
port: OAUTH_CALLBACK_PORT,
|
||||
hostname: '127.0.0.1',
|
||||
fetch: async (req) => {
|
||||
const url = new URL(req.url)
|
||||
if (url.pathname !== '/auth/callback') {
|
||||
return new Response('Not found', { status: 404 })
|
||||
}
|
||||
const MAX_BIND_ATTEMPTS = 5
|
||||
const RETRY_DELAY_MS = 300
|
||||
|
||||
const code = url.searchParams.get('code')
|
||||
const state = url.searchParams.get('state')
|
||||
const error = url.searchParams.get('error')
|
||||
export class OAuthCallbackServer {
|
||||
private server: ReturnType<typeof Bun.serve> | null = null
|
||||
private tokenManager: OAuthTokenManager | null = null
|
||||
|
||||
if (error) {
|
||||
const description = url.searchParams.get('error_description') || error
|
||||
logger.warn('OAuth callback received error', { error, description })
|
||||
return htmlResponse(errorPage(description))
|
||||
}
|
||||
|
||||
if (!code || !state) {
|
||||
return htmlResponse(errorPage('Missing authorization code or state'))
|
||||
}
|
||||
|
||||
try {
|
||||
await tokenManager.handleCallback(code, state)
|
||||
|
||||
// Always show success page — chrome-extension:// redirects are blocked by Chromium.
|
||||
// The extension polls /oauth/:provider/status and detects auth automatically.
|
||||
return htmlResponse(successPage())
|
||||
} catch (err) {
|
||||
logger.error('OAuth callback failed', {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
return htmlResponse(
|
||||
errorPage(
|
||||
err instanceof Error ? err.message : 'Authentication failed',
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
logger.info('OAuth callback server started', { port: OAUTH_CALLBACK_PORT })
|
||||
|
||||
return {
|
||||
stop: () => {
|
||||
server.stop()
|
||||
logger.info('OAuth callback server stopped')
|
||||
},
|
||||
setTokenManager(manager: OAuthTokenManager): void {
|
||||
this.tokenManager = manager
|
||||
}
|
||||
|
||||
isRunning(): boolean {
|
||||
return this.server !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the callback server is running on port 1455.
|
||||
* If the port is already held by another process, sends GET /cancel
|
||||
* to ask it to release, then retries.
|
||||
*/
|
||||
async ensureRunning(): Promise<void> {
|
||||
if (this.server) return
|
||||
|
||||
if (!this.tokenManager) {
|
||||
throw new Error('OAuth callback server not initialized')
|
||||
}
|
||||
|
||||
let cancelSent = false
|
||||
|
||||
for (let attempt = 1; attempt <= MAX_BIND_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
this.bind()
|
||||
return
|
||||
} catch {
|
||||
if (!cancelSent) {
|
||||
cancelSent = true
|
||||
await this.sendCancel()
|
||||
}
|
||||
|
||||
if (attempt < MAX_BIND_ATTEMPTS) {
|
||||
await sleep(RETRY_DELAY_MS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`OAuth callback port ${OAUTH_CALLBACK_PORT} is in use by another process. ` +
|
||||
'Close other BrowserOS instances or CLI tools and try again.',
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the server and release port 1455.
|
||||
*/
|
||||
stop(): void {
|
||||
if (this.server) {
|
||||
this.server.stop()
|
||||
this.server = null
|
||||
logger.info('OAuth callback server stopped', {
|
||||
port: OAUTH_CALLBACK_PORT,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private bind(): void {
|
||||
const tokenManager = this.tokenManager!
|
||||
|
||||
this.server = Bun.serve({
|
||||
port: OAUTH_CALLBACK_PORT,
|
||||
hostname: '127.0.0.1',
|
||||
fetch: async (req) => {
|
||||
const url = new URL(req.url)
|
||||
|
||||
// /cancel — let other instances ask us to release the port
|
||||
if (url.pathname === '/cancel') {
|
||||
logger.info('OAuth callback server received cancel request')
|
||||
// Schedule stop after responding
|
||||
queueMicrotask(() => this.stop())
|
||||
return new Response('Login cancelled', { status: 200 })
|
||||
}
|
||||
|
||||
if (url.pathname !== '/auth/callback') {
|
||||
return new Response('Not found', { status: 404 })
|
||||
}
|
||||
|
||||
const code = url.searchParams.get('code')
|
||||
const state = url.searchParams.get('state')
|
||||
const error = url.searchParams.get('error')
|
||||
|
||||
if (error) {
|
||||
const description = url.searchParams.get('error_description') || error
|
||||
logger.warn('OAuth callback received error', {
|
||||
error,
|
||||
description,
|
||||
})
|
||||
return htmlResponse(errorPage(description))
|
||||
}
|
||||
|
||||
if (!code || !state) {
|
||||
return htmlResponse(errorPage('Missing authorization code or state'))
|
||||
}
|
||||
|
||||
try {
|
||||
await tokenManager.handleCallback(code, state)
|
||||
return htmlResponse(successPage())
|
||||
} catch (err) {
|
||||
logger.error('OAuth callback failed', {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
return htmlResponse(
|
||||
errorPage(
|
||||
err instanceof Error ? err.message : 'Authentication failed',
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
logger.info('OAuth callback server started', {
|
||||
port: OAUTH_CALLBACK_PORT,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Send GET /cancel to any existing server on port 1455.
|
||||
* This politely asks the other process to release the port.
|
||||
* Follows the Codex CLI pattern (codex-rs/login/src/server.rs).
|
||||
*/
|
||||
private async sendCancel(): Promise<void> {
|
||||
try {
|
||||
await fetch(`http://127.0.0.1:${OAUTH_CALLBACK_PORT}/cancel`, {
|
||||
signal: AbortSignal.timeout(2000),
|
||||
})
|
||||
logger.info('Sent cancel to existing OAuth callback server')
|
||||
} catch {
|
||||
// Server might not support /cancel or might not be running
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
function htmlResponse(html: string): Response {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* VS Code's algorithm (max 2048px longest side, 768px shortest side).
|
||||
*/
|
||||
|
||||
import sharp from 'sharp'
|
||||
import { Jimp } from 'jimp'
|
||||
import { logger } from '../../logger'
|
||||
|
||||
const MAX_LONG_SIDE = 2048
|
||||
@@ -81,11 +81,13 @@ async function resizeDataUrl(dataUrl: string): Promise<string | null> {
|
||||
const base64Data = dataUrl.substring(commaIdx + 1)
|
||||
const buffer = Buffer.from(base64Data, 'base64')
|
||||
|
||||
const image = sharp(buffer)
|
||||
const metadata = await image.metadata()
|
||||
if (!metadata.width || !metadata.height) return null
|
||||
const image = await Jimp.fromBuffer(buffer)
|
||||
const origWidth = image.width
|
||||
const origHeight = image.height
|
||||
if (!origWidth || !origHeight) return null
|
||||
|
||||
let { width, height } = metadata
|
||||
let width = origWidth
|
||||
let height = origHeight
|
||||
|
||||
// Skip if already within both limits (no resize step will fire)
|
||||
if (
|
||||
@@ -110,24 +112,20 @@ async function resizeDataUrl(dataUrl: string): Promise<string | null> {
|
||||
height = Math.round(height * scale)
|
||||
}
|
||||
|
||||
// Preserve PNG for images with alpha, use JPEG otherwise
|
||||
const hasAlpha = metadata.channels === 4 || metadata.hasAlpha
|
||||
const resizedBuffer = hasAlpha
|
||||
? await sharp(buffer)
|
||||
.resize(width, height, { fit: 'inside' })
|
||||
.png()
|
||||
.toBuffer()
|
||||
: await sharp(buffer)
|
||||
.resize(width, height, { fit: 'inside' })
|
||||
.jpeg({ quality: 75 })
|
||||
.toBuffer()
|
||||
image.resize({ w: width, h: height })
|
||||
|
||||
// Jimp always outputs with alpha; use PNG for alpha sources, JPEG otherwise
|
||||
const hasAlpha = image.hasAlpha()
|
||||
const mime = hasAlpha ? 'image/png' : 'image/jpeg'
|
||||
const resizedBuffer = hasAlpha
|
||||
? await image.getBuffer('image/png')
|
||||
: await image.getBuffer('image/jpeg', { quality: 75 })
|
||||
|
||||
const originalKB = Math.round(base64Data.length / 1024)
|
||||
const resizedB64 = resizedBuffer.toString('base64')
|
||||
const resizedKB = Math.round(resizedB64.length / 1024)
|
||||
logger.debug('Resized image for Copilot', {
|
||||
original: `${metadata.width}x${metadata.height} (${originalKB}KB)`,
|
||||
original: `${origWidth}x${origHeight} (${originalKB}KB)`,
|
||||
resized: `${width}x${height} (${resizedKB}KB)`,
|
||||
})
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import type { Database } from 'bun:sqlite'
|
||||
import { startOAuthCallbackServer } from './callback-server'
|
||||
import { OAuthCallbackServer } from './callback-server'
|
||||
import { OAuthTokenManager } from './token-manager'
|
||||
import { OAuthTokenStore } from './token-store'
|
||||
|
||||
@@ -16,8 +16,9 @@ export function initializeOAuth(
|
||||
browserosId: string,
|
||||
): OAuthTokenManager {
|
||||
const store = new OAuthTokenStore(db)
|
||||
tokenManager = new OAuthTokenManager(store, browserosId)
|
||||
startOAuthCallbackServer(tokenManager)
|
||||
const callbackServer = new OAuthCallbackServer()
|
||||
tokenManager = new OAuthTokenManager(store, browserosId, callbackServer)
|
||||
callbackServer.setTokenManager(tokenManager)
|
||||
return tokenManager
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { OAUTH_CALLBACK_PORT } from '@browseros/shared/constants/ports'
|
||||
import { TIMEOUTS } from '@browseros/shared/constants/timeouts'
|
||||
import { logger } from '../../logger'
|
||||
import type { OAuthCallbackServer } from './callback-server'
|
||||
import { getOAuthProvider, type OAuthProviderConfig } from './providers'
|
||||
import type { OAuthTokenStore, StoredOAuthTokens } from './token-store'
|
||||
|
||||
@@ -58,6 +59,7 @@ export class OAuthTokenManager {
|
||||
constructor(
|
||||
private readonly store: OAuthTokenStore,
|
||||
private readonly browserosId: string,
|
||||
private readonly callbackServer: OAuthCallbackServer,
|
||||
) {}
|
||||
|
||||
// --- PKCE flow (ChatGPT Plus/Pro) ---
|
||||
@@ -66,6 +68,9 @@ export class OAuthTokenManager {
|
||||
providerId: string,
|
||||
redirectBackUrl?: string,
|
||||
): Promise<string> {
|
||||
// Lazily start callback server — only needed for PKCE flow
|
||||
await this.callbackServer.ensureRunning()
|
||||
|
||||
const provider = getOAuthProvider(providerId)
|
||||
if (!provider) throw new Error(`Unknown OAuth provider: ${providerId}`)
|
||||
|
||||
@@ -154,6 +159,7 @@ export class OAuthTokenManager {
|
||||
|
||||
this.store.upsertTokens(this.browserosId, flow.provider, tokens)
|
||||
this.pendingFlows.delete(state)
|
||||
this.stopCallbackIfIdle()
|
||||
|
||||
logger.info('OAuth authentication successful', {
|
||||
provider: flow.provider,
|
||||
@@ -444,6 +450,17 @@ export class OAuthTokenManager {
|
||||
this.store.deleteTokens(this.browserosId, provider)
|
||||
}
|
||||
|
||||
stopCallbackServer(): void {
|
||||
this.callbackServer.stop()
|
||||
}
|
||||
|
||||
private stopCallbackIfIdle(): void {
|
||||
const hasPkceFlows = [...this.pendingFlows.values()].some(() => true)
|
||||
if (!hasPkceFlows) {
|
||||
this.callbackServer.stop()
|
||||
}
|
||||
}
|
||||
|
||||
private cleanExpiredFlows(): void {
|
||||
const now = Date.now()
|
||||
for (const [state, flow] of this.pendingFlows) {
|
||||
|
||||
@@ -18,7 +18,12 @@ import { ControllerBackend } from './browser/backends/controller'
|
||||
import { Browser } from './browser/browser'
|
||||
import type { ServerConfig } from './config'
|
||||
import { INLINED_ENV } from './env'
|
||||
import { cleanOldSessions, ensureBrowserosDir } from './lib/browseros-dir'
|
||||
import {
|
||||
cleanOldSessions,
|
||||
ensureBrowserosDir,
|
||||
removeServerConfigSync,
|
||||
writeServerConfig,
|
||||
} from './lib/browseros-dir'
|
||||
import { initializeDb } from './lib/db'
|
||||
import { identity } from './lib/identity'
|
||||
import { logger } from './lib/logger'
|
||||
@@ -109,6 +114,20 @@ export class Application {
|
||||
this.handleStartupError('HTTP server', this.config.serverPort, error)
|
||||
}
|
||||
|
||||
try {
|
||||
await writeServerConfig({
|
||||
server_port: this.config.serverPort,
|
||||
url: `http://127.0.0.1:${this.config.serverPort}`,
|
||||
server_version: VERSION,
|
||||
browseros_version: this.config.instanceBrowserosVersion,
|
||||
chromium_version: this.config.instanceChromiumVersion,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.warn('Failed to write server config for auto-discovery', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`HTTP server listening on http://127.0.0.1:${this.config.serverPort}`,
|
||||
)
|
||||
@@ -125,6 +144,7 @@ export class Application {
|
||||
stop(reason?: string): void {
|
||||
logger.info('Shutting down server...', { reason })
|
||||
stopSkillSync()
|
||||
removeServerConfigSync()
|
||||
|
||||
// Immediate exit without graceful shutdown. Chromium may kill us on update/restart,
|
||||
// and we need to free the port instantly so the HTTP port doesn't keep switching.
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface BrowserConfig {
|
||||
binaryPath: string
|
||||
userDataDir: string
|
||||
headless: boolean
|
||||
extraArgs: string[]
|
||||
}
|
||||
|
||||
interface BrowserState {
|
||||
@@ -26,6 +27,12 @@ interface BrowserState {
|
||||
|
||||
let browserState: BrowserState | null = null
|
||||
|
||||
function shouldLogBrowserOutput(): boolean {
|
||||
return (
|
||||
process.env.CI === 'true' || process.env.BROWSEROS_TEST_DEBUG === 'true'
|
||||
)
|
||||
}
|
||||
|
||||
export async function isBrowserRunning(cdpPort: number): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${cdpPort}/json/version`, {
|
||||
@@ -74,6 +81,7 @@ export async function spawnBrowser(
|
||||
'--show-component-extension-options',
|
||||
'--enable-logging=stderr',
|
||||
...(config.headless ? ['--headless=new'] : []),
|
||||
...config.extraArgs,
|
||||
`--user-data-dir=${config.userDataDir}`,
|
||||
// TODO: replace with --browseros-cdp-port once we fix the browseros bug
|
||||
`--remote-debugging-port=${config.cdpPort}`,
|
||||
@@ -86,14 +94,18 @@ export async function spawnBrowser(
|
||||
},
|
||||
)
|
||||
|
||||
browserProcess.stdout?.on('data', (_data) => {
|
||||
// Uncomment for debugging
|
||||
// console.log(`[BROWSER] ${_data.toString().trim()}`)
|
||||
browserProcess.stdout?.on('data', (data) => {
|
||||
if (!shouldLogBrowserOutput()) {
|
||||
return
|
||||
}
|
||||
console.log(`[BROWSER] ${data.toString().trim()}`)
|
||||
})
|
||||
|
||||
browserProcess.stderr?.on('data', (_data) => {
|
||||
// Uncomment for debugging
|
||||
// console.log(`[BROWSER] ${_data.toString().trim()}`)
|
||||
browserProcess.stderr?.on('data', (data) => {
|
||||
if (!shouldLogBrowserOutput()) {
|
||||
return
|
||||
}
|
||||
console.error(`[BROWSER] ${data.toString().trim()}`)
|
||||
})
|
||||
|
||||
browserProcess.on('error', (error) => {
|
||||
|
||||
@@ -132,6 +132,7 @@ export async function ensureBrowserOS(
|
||||
binaryPath: runtimePlan.binaryPath,
|
||||
userDataDir: runtimePlan.userDataDir,
|
||||
headless: runtimePlan.headless,
|
||||
extraArgs: runtimePlan.extraArgs,
|
||||
}
|
||||
await spawnBrowser(browserConfig)
|
||||
|
||||
|
||||
@@ -20,9 +20,20 @@ export interface TestRuntimePlan {
|
||||
userDataDir: string
|
||||
binaryPath: string
|
||||
headless: boolean
|
||||
extraArgs: string[]
|
||||
usesFixedPorts: boolean
|
||||
}
|
||||
|
||||
function parseExtraArgs(value: string | undefined): string[] {
|
||||
if (!value) {
|
||||
return []
|
||||
}
|
||||
return value
|
||||
.split(/\s+/)
|
||||
.map((part) => part.trim())
|
||||
.filter((part) => part.length > 0)
|
||||
}
|
||||
|
||||
function parsePort(
|
||||
value: string | undefined,
|
||||
envName: string,
|
||||
@@ -137,12 +148,14 @@ export async function createTestRuntimePlan(): Promise<TestRuntimePlan> {
|
||||
const resolvedPorts = await resolveRuntimePorts()
|
||||
const userDataDir = mkdtempSync(join(tmpdir(), 'browseros-test-'))
|
||||
const headless = process.env.BROWSEROS_TEST_HEADLESS === 'true'
|
||||
const extraArgs = parseExtraArgs(process.env.BROWSEROS_TEST_EXTRA_ARGS)
|
||||
|
||||
return {
|
||||
ports: resolvedPorts.ports,
|
||||
userDataDir,
|
||||
binaryPath: DEFAULT_BINARY_PATH,
|
||||
headless,
|
||||
extraArgs,
|
||||
usesFixedPorts: resolvedPorts.usesFixedPorts,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ async function getOrCreateBrowser(): Promise<Browser> {
|
||||
binaryPath: runtimePlan.binaryPath,
|
||||
userDataDir: runtimePlan.userDataDir,
|
||||
headless: runtimePlan.headless,
|
||||
extraArgs: runtimePlan.extraArgs,
|
||||
}
|
||||
await spawnBrowser(config)
|
||||
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 BrowserOS
|
||||
*/
|
||||
|
||||
import { describe, it } from 'bun:test'
|
||||
import assert from 'node:assert'
|
||||
|
||||
import { Jimp } from 'jimp'
|
||||
|
||||
import { createCopilotFetch } from '../../src/lib/clients/oauth/copilot-fetch'
|
||||
|
||||
function makeImageBody(dataUrls: string[]) {
|
||||
return JSON.stringify({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: dataUrls.map((url) => ({
|
||||
type: 'image_url',
|
||||
image_url: { url },
|
||||
})),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
async function createTestImage(
|
||||
width: number,
|
||||
height: number,
|
||||
hasAlpha = false,
|
||||
): Promise<string> {
|
||||
const image = new Jimp({ width, height, color: 0xff0000ff })
|
||||
if (hasAlpha) {
|
||||
// Set some pixels to transparent so hasAlpha() returns true
|
||||
for (let x = 0; x < Math.min(width, 10); x++) {
|
||||
image.setPixelColor(0xff000080, x, 0)
|
||||
}
|
||||
}
|
||||
const mime = hasAlpha ? 'image/png' : 'image/jpeg'
|
||||
const buffer = await image.getBuffer(mime)
|
||||
return `data:${mime};base64,${buffer.toString('base64')}`
|
||||
}
|
||||
|
||||
function parseDataUrl(dataUrl: string) {
|
||||
const [header, b64] = dataUrl.split(',')
|
||||
const mime = header.match(/data:([^;]+)/)?.[1]
|
||||
return { mime, buffer: Buffer.from(b64, 'base64') }
|
||||
}
|
||||
|
||||
describe('createCopilotFetch', () => {
|
||||
it('sets Copilot headers on every request', async () => {
|
||||
const copilotFetch = createCopilotFetch()
|
||||
const calls: { input: RequestInfo | URL; init?: RequestInit }[] = []
|
||||
|
||||
const originalFetch = globalThis.fetch
|
||||
globalThis.fetch = (async (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit,
|
||||
) => {
|
||||
calls.push({ input, init })
|
||||
return new Response('ok')
|
||||
}) as typeof fetch
|
||||
|
||||
try {
|
||||
await copilotFetch('https://api.example.com', {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ messages: [{ role: 'user', content: 'hi' }] }),
|
||||
})
|
||||
|
||||
assert.strictEqual(calls.length, 1)
|
||||
const headers = new Headers(calls[0].init?.headers as HeadersInit)
|
||||
assert.strictEqual(headers.get('Openai-Intent'), 'conversation-edits')
|
||||
assert.strictEqual(headers.get('x-initiator'), 'user')
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch
|
||||
}
|
||||
})
|
||||
|
||||
it('sets Copilot-Vision-Request header when images present', async () => {
|
||||
const copilotFetch = createCopilotFetch()
|
||||
const calls: { input: RequestInfo | URL; init?: RequestInit }[] = []
|
||||
|
||||
const originalFetch = globalThis.fetch
|
||||
globalThis.fetch = (async (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit,
|
||||
) => {
|
||||
calls.push({ input, init })
|
||||
return new Response('ok')
|
||||
}) as typeof fetch
|
||||
|
||||
try {
|
||||
const dataUrl = await createTestImage(100, 100)
|
||||
await copilotFetch('https://api.example.com', {
|
||||
body: makeImageBody([dataUrl]),
|
||||
})
|
||||
|
||||
const headers = new Headers(calls[0].init?.headers as HeadersInit)
|
||||
assert.strictEqual(headers.get('Copilot-Vision-Request'), 'true')
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('image resizing', () => {
|
||||
async function resizeViaFetch(dataUrl: string): Promise<string> {
|
||||
let capturedBody = ''
|
||||
const originalFetch = globalThis.fetch
|
||||
globalThis.fetch = (async (_: RequestInfo | URL, init?: RequestInit) => {
|
||||
capturedBody = init?.body as string
|
||||
return new Response('ok')
|
||||
}) as typeof fetch
|
||||
|
||||
try {
|
||||
const copilotFetch = createCopilotFetch()
|
||||
await copilotFetch('https://api.example.com', {
|
||||
body: makeImageBody([dataUrl]),
|
||||
})
|
||||
const parsed = JSON.parse(capturedBody)
|
||||
return parsed.messages[0].content[0].image_url.url
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch
|
||||
}
|
||||
}
|
||||
|
||||
it('does not resize images already within limits', async () => {
|
||||
const dataUrl = await createTestImage(800, 600)
|
||||
const result = await resizeViaFetch(dataUrl)
|
||||
assert.strictEqual(result, dataUrl)
|
||||
})
|
||||
|
||||
it('scales down when longest side exceeds 2048', async () => {
|
||||
const dataUrl = await createTestImage(4096, 2048)
|
||||
const result = await resizeViaFetch(dataUrl)
|
||||
|
||||
assert.notStrictEqual(result, dataUrl)
|
||||
const { buffer } = parseDataUrl(result)
|
||||
const resized = await Jimp.fromBuffer(buffer)
|
||||
assert.ok(resized.width <= 2048)
|
||||
assert.ok(resized.height <= 1024)
|
||||
})
|
||||
|
||||
it('scales down when shortest side exceeds 768', async () => {
|
||||
const dataUrl = await createTestImage(2000, 1500)
|
||||
const result = await resizeViaFetch(dataUrl)
|
||||
|
||||
assert.notStrictEqual(result, dataUrl)
|
||||
const { buffer } = parseDataUrl(result)
|
||||
const resized = await Jimp.fromBuffer(buffer)
|
||||
assert.ok(Math.min(resized.width, resized.height) <= 768)
|
||||
})
|
||||
|
||||
it('outputs JPEG for opaque images', async () => {
|
||||
const dataUrl = await createTestImage(4096, 3072, false)
|
||||
const result = await resizeViaFetch(dataUrl)
|
||||
const { mime } = parseDataUrl(result)
|
||||
assert.strictEqual(mime, 'image/jpeg')
|
||||
})
|
||||
|
||||
it('outputs PNG for images with alpha', async () => {
|
||||
const dataUrl = await createTestImage(4096, 3072, true)
|
||||
const result = await resizeViaFetch(dataUrl)
|
||||
const { mime } = parseDataUrl(result)
|
||||
assert.strictEqual(mime, 'image/png')
|
||||
})
|
||||
|
||||
it('applies both scaling steps (long side then short side)', async () => {
|
||||
// 4000x3000: step 1 scales to 2048x1536, step 2 scales 1536→768 so 1024x768
|
||||
const dataUrl = await createTestImage(4000, 3000)
|
||||
const result = await resizeViaFetch(dataUrl)
|
||||
|
||||
const { buffer } = parseDataUrl(result)
|
||||
const resized = await Jimp.fromBuffer(buffer)
|
||||
assert.ok(resized.width <= 2048)
|
||||
assert.ok(Math.min(resized.width, resized.height) <= 768)
|
||||
})
|
||||
})
|
||||
100
packages/browseros-agent/apps/server/tests/build.test.ts
Normal file
100
packages/browseros-agent/apps/server/tests/build.test.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 BrowserOS
|
||||
*
|
||||
* Build smoke test — compiles the server binary and verifies --version output.
|
||||
* Catches compile failures, broken imports, and version injection bugs.
|
||||
*/
|
||||
|
||||
import { afterAll, describe, it } from 'bun:test'
|
||||
import assert from 'node:assert'
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
// Derive the build target from the current platform so the test is portable
|
||||
function getNativeTarget(): { id: string; ext: string } {
|
||||
const os =
|
||||
process.platform === 'darwin'
|
||||
? 'darwin'
|
||||
: process.platform === 'win32'
|
||||
? 'windows'
|
||||
: 'linux'
|
||||
const cpu = process.arch === 'arm64' ? 'arm64' : 'x64'
|
||||
return { id: `${os}-${cpu}`, ext: process.platform === 'win32' ? '.exe' : '' }
|
||||
}
|
||||
|
||||
// Stub values so the build config validation passes without real secrets
|
||||
const BUILD_ENV_STUBS: Record<string, string> = {
|
||||
BROWSEROS_CONFIG_URL: 'https://stub.test/config',
|
||||
CODEGEN_SERVICE_URL: 'https://stub.test/codegen',
|
||||
POSTHOG_API_KEY: 'phc_test_stub',
|
||||
SENTRY_DSN: 'https://stub@sentry.test/0',
|
||||
R2_ACCOUNT_ID: 'test',
|
||||
R2_ACCESS_KEY_ID: 'test',
|
||||
R2_SECRET_ACCESS_KEY: 'test',
|
||||
R2_BUCKET: 'test',
|
||||
}
|
||||
|
||||
describe('server build', () => {
|
||||
const rootDir = resolve(import.meta.dir, '../../..')
|
||||
const serverPkgPath = resolve(rootDir, 'apps/server/package.json')
|
||||
const buildScript = resolve(rootDir, 'scripts/build/server.ts')
|
||||
const target = getNativeTarget()
|
||||
const binaryPath = resolve(
|
||||
rootDir,
|
||||
`dist/prod/server/.tmp/binaries/browseros-server-${target.id}${target.ext}`,
|
||||
)
|
||||
|
||||
// Empty manifest so the build skips R2 resource downloads
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'browseros-build-test-'))
|
||||
const emptyManifestPath = join(tempDir, 'empty-manifest.json')
|
||||
writeFileSync(emptyManifestPath, JSON.stringify({ resources: [] }))
|
||||
|
||||
afterAll(() => {
|
||||
rmSync(tempDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it('compiles and --version outputs correct version', async () => {
|
||||
const pkg = await Bun.file(serverPkgPath).json()
|
||||
const expectedVersion: string = pkg.version
|
||||
|
||||
const build = Bun.spawn(
|
||||
[
|
||||
'bun',
|
||||
buildScript,
|
||||
`--target=${target.id}`,
|
||||
'--no-upload',
|
||||
`--manifest=${emptyManifestPath}`,
|
||||
],
|
||||
{
|
||||
cwd: rootDir,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
env: { ...process.env, ...BUILD_ENV_STUBS },
|
||||
},
|
||||
)
|
||||
const buildExit = await build.exited
|
||||
if (buildExit !== 0) {
|
||||
const stderr = await new Response(build.stderr).text()
|
||||
assert.fail(`Build failed (exit ${buildExit}):\n${stderr}`)
|
||||
}
|
||||
|
||||
const proc = Bun.spawn([binaryPath, '--version'], {
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
})
|
||||
const [versionOutput, versionStderr] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
])
|
||||
const versionExit = await proc.exited
|
||||
|
||||
assert.strictEqual(
|
||||
versionExit,
|
||||
0,
|
||||
`Binary --version exited non-zero:\n${versionStderr}`,
|
||||
)
|
||||
assert.strictEqual(versionOutput.trim(), expectedVersion)
|
||||
}, 300_000)
|
||||
})
|
||||
@@ -55,7 +55,7 @@ describe('filesystem_bash', () => {
|
||||
})
|
||||
|
||||
it('times out long-running commands', async () => {
|
||||
const result = await exec({ command: 'sleep 30', timeout: 1 })
|
||||
const result = await exec({ command: 'exec sleep 30', timeout: 1 })
|
||||
expect(result.isError).toBe(true)
|
||||
expect(result.text).toContain('timed out')
|
||||
}, 10_000)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { describe, it } from 'bun:test'
|
||||
import assert from 'node:assert'
|
||||
import type { Browser } from '../../src/browser/browser'
|
||||
import { executeTool, type ToolContext } from '../../src/tools/framework'
|
||||
import {
|
||||
check,
|
||||
click,
|
||||
@@ -320,37 +322,48 @@ describe('input tools', () => {
|
||||
}, 60_000)
|
||||
|
||||
it('scroll dispatches without error', async () => {
|
||||
await withBrowser(async ({ execute }) => {
|
||||
const newResult = await execute(new_page, {
|
||||
url: FORM_PAGE,
|
||||
})
|
||||
const pageId = pageIdOf(newResult)
|
||||
const calls: Array<{
|
||||
page: number
|
||||
direction: string
|
||||
amount: number
|
||||
element?: number
|
||||
}> = []
|
||||
const browser = {
|
||||
getTabIdForPage: () => undefined,
|
||||
scroll: async (
|
||||
page: number,
|
||||
direction: string,
|
||||
amount: number,
|
||||
element?: number,
|
||||
) => {
|
||||
calls.push({ page, direction, amount, element })
|
||||
},
|
||||
} as unknown as Browser
|
||||
const ctx: ToolContext = {
|
||||
browser,
|
||||
directories: { workingDir: process.cwd() },
|
||||
}
|
||||
|
||||
const before = await execute(evaluate_script, {
|
||||
page: pageId,
|
||||
expression: 'window.scrollY',
|
||||
})
|
||||
const result = await executeTool(
|
||||
scroll,
|
||||
{ page: 7, direction: 'down', amount: 5 },
|
||||
ctx,
|
||||
AbortSignal.timeout(1_000),
|
||||
)
|
||||
|
||||
const scrollResult = await execute(scroll, {
|
||||
page: pageId,
|
||||
direction: 'down',
|
||||
amount: 5,
|
||||
})
|
||||
assert.ok(!scrollResult.isError, textOf(scrollResult))
|
||||
assert.ok(textOf(scrollResult).includes('Scrolled down'))
|
||||
|
||||
const after = await execute(evaluate_script, {
|
||||
page: pageId,
|
||||
expression: 'window.scrollY',
|
||||
})
|
||||
assert.ok(
|
||||
Number(textOf(after)) > Number(textOf(before)),
|
||||
`Expected scrollY to increase, before=${textOf(before)} after=${textOf(after)}`,
|
||||
)
|
||||
|
||||
await execute(close_page, { page: pageId })
|
||||
assert.ok(!result.isError, textOf(result))
|
||||
assert.ok(textOf(result).includes('Scrolled down'))
|
||||
assert.deepStrictEqual(calls, [
|
||||
{ page: 7, direction: 'down', amount: 5, element: undefined },
|
||||
])
|
||||
assert.deepStrictEqual(structuredOf(result), {
|
||||
action: 'scroll',
|
||||
page: 7,
|
||||
direction: 'down',
|
||||
amount: 5,
|
||||
element: undefined,
|
||||
})
|
||||
}, 60_000)
|
||||
})
|
||||
|
||||
it('hover moves cursor over element', async () => {
|
||||
await withBrowser(async ({ execute }) => {
|
||||
|
||||
@@ -203,7 +203,8 @@ describe('observation tools', () => {
|
||||
savedPath = data.path
|
||||
|
||||
assert.strictEqual(data.writtenToFile, true)
|
||||
assert.ok(textOf(contentResult).includes('Saved page content'))
|
||||
assert.ok(textOf(contentResult).includes('Content truncated'))
|
||||
assert.ok(textOf(contentResult).includes(savedPath))
|
||||
assert.ok(existsSync(savedPath), 'Saved page content file should exist')
|
||||
assert.ok(
|
||||
dirname(savedPath).startsWith(
|
||||
|
||||
@@ -30,13 +30,13 @@ function structuredOf<T>(result: { structuredContent?: unknown }): T {
|
||||
|
||||
function createToolContext(
|
||||
browser: Browser,
|
||||
executionDir: string,
|
||||
workingDir: string,
|
||||
resourcesDir?: string,
|
||||
): ToolContext {
|
||||
return {
|
||||
browser,
|
||||
directories: {
|
||||
executionDir,
|
||||
workingDir,
|
||||
resourcesDir,
|
||||
},
|
||||
}
|
||||
@@ -50,10 +50,8 @@ function createBrowserStub(methods: Record<string, unknown>): Browser {
|
||||
}
|
||||
|
||||
describe('page action tools', () => {
|
||||
it('save_pdf resolves relative paths against the execution directory by default', async () => {
|
||||
const executionDir = await mkdtemp(
|
||||
join(tmpdir(), 'browseros-page-actions-'),
|
||||
)
|
||||
it('save_pdf resolves relative paths against the working directory by default', async () => {
|
||||
const workingDir = await mkdtemp(join(tmpdir(), 'browseros-page-actions-'))
|
||||
const browser = createBrowserStub({
|
||||
printToPDF: async () => ({
|
||||
data: Buffer.from('pdf-data').toString('base64'),
|
||||
@@ -64,26 +62,24 @@ describe('page action tools', () => {
|
||||
const result = await executeTool(
|
||||
save_pdf,
|
||||
{ page: 1, path: 'report.pdf' },
|
||||
createToolContext(browser, executionDir),
|
||||
createToolContext(browser, workingDir),
|
||||
AbortSignal.timeout(1_000),
|
||||
)
|
||||
|
||||
assert.ok(!result.isError, textOf(result))
|
||||
const outputPath = join(executionDir, 'report.pdf')
|
||||
const outputPath = join(workingDir, 'report.pdf')
|
||||
assert.strictEqual(
|
||||
structuredOf<{ path: string }>(result).path,
|
||||
outputPath,
|
||||
)
|
||||
assert.ok(existsSync(outputPath), 'PDF file should exist in executionDir')
|
||||
assert.ok(existsSync(outputPath), 'PDF file should exist in workingDir')
|
||||
} finally {
|
||||
await rm(executionDir, { recursive: true, force: true })
|
||||
await rm(workingDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('save_screenshot still honors an explicit cwd override', async () => {
|
||||
const executionDir = await mkdtemp(
|
||||
join(tmpdir(), 'browseros-page-actions-'),
|
||||
)
|
||||
const workingDir = await mkdtemp(join(tmpdir(), 'browseros-page-actions-'))
|
||||
const overrideDir = await mkdtemp(join(tmpdir(), 'browseros-page-actions-'))
|
||||
const browser = createBrowserStub({
|
||||
screenshot: async () => ({
|
||||
@@ -95,7 +91,7 @@ describe('page action tools', () => {
|
||||
const result = await executeTool(
|
||||
save_screenshot,
|
||||
{ page: 1, path: 'capture.png', cwd: overrideDir },
|
||||
createToolContext(browser, executionDir),
|
||||
createToolContext(browser, workingDir),
|
||||
AbortSignal.timeout(1_000),
|
||||
)
|
||||
|
||||
@@ -110,18 +106,18 @@ describe('page action tools', () => {
|
||||
'Screenshot should exist in overrideDir',
|
||||
)
|
||||
assert.ok(
|
||||
!existsSync(join(executionDir, 'capture.png')),
|
||||
'Execution directory should not be used when cwd is provided',
|
||||
!existsSync(join(workingDir, 'capture.png')),
|
||||
'Working directory should not be used when cwd is provided',
|
||||
)
|
||||
} finally {
|
||||
await rm(executionDir, { recursive: true, force: true })
|
||||
await rm(workingDir, { recursive: true, force: true })
|
||||
await rm(overrideDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('download_file resolves relative directories against the execution directory by default', async () => {
|
||||
it('download_file resolves relative directories against the working directory by default', async () => {
|
||||
const baseDir = await mkdtemp(join(tmpdir(), 'browseros-page-actions-'))
|
||||
const executionDir = join(baseDir, 'execution')
|
||||
const workingDir = join(baseDir, 'working')
|
||||
let stagingDir: string | undefined
|
||||
const browser = createBrowserStub({
|
||||
downloadViaClick: async (
|
||||
@@ -143,23 +139,23 @@ describe('page action tools', () => {
|
||||
const result = await executeTool(
|
||||
download_file,
|
||||
{ page: 1, element: 7, path: '.' },
|
||||
createToolContext(browser, executionDir),
|
||||
createToolContext(browser, workingDir),
|
||||
AbortSignal.timeout(1_000),
|
||||
)
|
||||
|
||||
assert.ok(!result.isError, textOf(result))
|
||||
const outputPath = join(executionDir, 'download.txt')
|
||||
const outputPath = join(workingDir, 'download.txt')
|
||||
const structured = structuredOf<{
|
||||
directory: string
|
||||
destinationPath: string
|
||||
}>(result)
|
||||
assert.strictEqual(structured.directory, executionDir)
|
||||
assert.strictEqual(structured.directory, workingDir)
|
||||
assert.strictEqual(structured.destinationPath, outputPath)
|
||||
assert.ok(existsSync(outputPath), 'Download should land in executionDir')
|
||||
assert.ok(existsSync(outputPath), 'Download should land in workingDir')
|
||||
assert.ok(stagingDir, 'Download should use a staging directory')
|
||||
assert.ok(
|
||||
stagingDir.startsWith(join(executionDir, 'browseros-dl-')),
|
||||
'Staging directory should be created inside executionDir',
|
||||
stagingDir.startsWith(join(workingDir, 'browseros-dl-')),
|
||||
'Staging directory should be created inside workingDir',
|
||||
)
|
||||
assert.ok(
|
||||
!existsSync(stagingDir),
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.63",
|
||||
"@aws-sdk/client-s3": "^3.1014.0",
|
||||
"@browseros/server": "workspace:*",
|
||||
"@browseros/shared": "workspace:*",
|
||||
"@google/gemini-cli-core": "^0.16.0",
|
||||
@@ -169,7 +170,7 @@
|
||||
},
|
||||
"apps/server": {
|
||||
"name": "@browseros/server",
|
||||
"version": "0.0.77",
|
||||
"version": "0.0.79",
|
||||
"bin": {
|
||||
"browseros-server": "./src/index.ts",
|
||||
},
|
||||
@@ -203,11 +204,11 @@
|
||||
"fuse.js": "^7.1.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"hono": "^4.12.3",
|
||||
"jimp": "^1.6.0",
|
||||
"klavis": "^2.15.0",
|
||||
"pino": "^9.6.0",
|
||||
"posthog-node": "^4.17.0",
|
||||
"puppeteer-core": "24.23.0",
|
||||
"sharp": "^0.34.5",
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.24.2",
|
||||
"zod-from-json-schema": "^0.1.0",
|
||||
@@ -895,6 +896,62 @@
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="],
|
||||
|
||||
"@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="],
|
||||
|
||||
"@jimp/file-ops": ["@jimp/file-ops@1.6.0", "", {}, "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ=="],
|
||||
|
||||
"@jimp/js-bmp": ["@jimp/js-bmp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "bmp-ts": "^1.0.9" } }, "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw=="],
|
||||
|
||||
"@jimp/js-gif": ["@jimp/js-gif@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g=="],
|
||||
|
||||
"@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "jpeg-js": "^0.4.4" } }, "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA=="],
|
||||
|
||||
"@jimp/js-png": ["@jimp/js-png@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "pngjs": "^7.0.0" } }, "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg=="],
|
||||
|
||||
"@jimp/js-tiff": ["@jimp/js-tiff@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "utif2": "^4.1.0" } }, "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw=="],
|
||||
|
||||
"@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA=="],
|
||||
|
||||
"@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw=="],
|
||||
|
||||
"@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw=="],
|
||||
|
||||
"@jimp/plugin-color": ["@jimp/plugin-color@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA=="],
|
||||
|
||||
"@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ=="],
|
||||
|
||||
"@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA=="],
|
||||
|
||||
"@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang=="],
|
||||
|
||||
"@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q=="],
|
||||
|
||||
"@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0" } }, "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ=="],
|
||||
|
||||
"@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA=="],
|
||||
|
||||
"@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg=="],
|
||||
|
||||
"@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "any-base": "^1.1.0" } }, "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q=="],
|
||||
|
||||
"@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA=="],
|
||||
|
||||
"@jimp/plugin-print": ["@jimp/plugin-print@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/types": "1.6.0", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A=="],
|
||||
|
||||
"@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.0", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg=="],
|
||||
|
||||
"@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA=="],
|
||||
|
||||
"@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw=="],
|
||||
|
||||
"@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w=="],
|
||||
|
||||
"@jimp/types": ["@jimp/types@1.6.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg=="],
|
||||
|
||||
"@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],
|
||||
|
||||
"@joshua.litt/get-ripgrep": ["@joshua.litt/get-ripgrep@0.0.3", "", { "dependencies": { "@lvce-editor/verror": "^1.6.0", "execa": "^9.5.2", "extract-zip": "^2.0.1", "fs-extra": "^11.3.0", "got": "^14.4.5", "path-exists": "^5.0.0", "xdg-basedir": "^5.1.0" } }, "sha512-rycdieAKKqXi2bsM7G2ayDiNk5CAX8ZOzsTQsirfOqUKPef04Xw40BWGGyimaOOuvPgLWYt3tPnLLG3TvPXi5Q=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
@@ -1735,6 +1792,8 @@
|
||||
|
||||
"@theguild/federation-composition": ["@theguild/federation-composition@0.21.3", "", { "dependencies": { "constant-case": "^3.0.4", "debug": "4.4.3", "json5": "^2.2.3", "lodash.sortby": "^4.7.0" }, "peerDependencies": { "graphql": "^16.0.0" } }, "sha512-+LlHTa4UbRpZBog3ggAxjYIFvdfH3UMvvBUptur19TMWkqU4+n3GmN+mDjejU+dyBXIG27c25RsiQP1HyvM99g=="],
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
|
||||
"@tokenlens/core": ["@tokenlens/core@1.3.0", "", {}, "sha512-d8YNHNC+q10bVpi95fELJwJyPVf1HfvBEI18eFQxRSZTdByXrP+f/ZtlhSzkx0Jl0aEmYVeBA5tPeeYRioLViQ=="],
|
||||
|
||||
"@tokenlens/fetch": ["@tokenlens/fetch@1.3.0", "", { "dependencies": { "@tokenlens/core": "1.3.0" } }, "sha512-RONDRmETYly9xO8XMKblmrZjKSwCva4s5ebJwQNfNlChZoA5kplPoCgnWceHnn1J1iRjLVlrCNB43ichfmGBKQ=="],
|
||||
@@ -2053,6 +2112,8 @@
|
||||
|
||||
"antd-style": ["antd-style@3.7.1", "", { "dependencies": { "@ant-design/cssinjs": "^1.21.1", "@babel/runtime": "^7.24.1", "@emotion/cache": "^11.11.0", "@emotion/css": "^11.11.2", "@emotion/react": "^11.11.4", "@emotion/serialize": "^1.1.3", "@emotion/utils": "^1.2.1", "use-merge-value": "^1.2.0" }, "peerDependencies": { "antd": ">=5.8.1", "react": ">=18" } }, "sha512-CQOfddVp4aOvBfCepa+Kj2e7ap+2XBINg1Kn2osdE3oQvrD7KJu/K0sfnLcFLkgCJygbxmuazYdWLKb+drPDYA=="],
|
||||
|
||||
"any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
@@ -2087,6 +2148,8 @@
|
||||
|
||||
"auto-bind": ["auto-bind@4.0.0", "", {}, "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ=="],
|
||||
|
||||
"await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
|
||||
|
||||
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
||||
|
||||
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
|
||||
@@ -2127,6 +2190,8 @@
|
||||
|
||||
"bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
|
||||
|
||||
"bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="],
|
||||
|
||||
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
@@ -2215,7 +2280,7 @@
|
||||
|
||||
"chrome-devtools-frontend": ["chrome-devtools-frontend@1.0.1577886", "", {}, "sha512-B9hY3o/0RuVCDWNYh9YnkEbRrPUMCY+NaOgBxvZRzGvqbGSMNckkVSdO67SwWR8bm4fo/qplXbUj0cSr229V6w=="],
|
||||
|
||||
"chrome-devtools-mcp": ["chrome-devtools-mcp@0.20.2", "", { "bin": { "chrome-devtools-mcp": "build/src/bin/chrome-devtools-mcp.js", "chrome-devtools": "build/src/bin/chrome-devtools.js" } }, "sha512-QYwRj8YJjvFHODiYVDJpHaUYLD8/wt0DP+HXQPn/IF+QbbAfr7Vn2JtACyrIPEzTX3XJXgDPZkr4gSYHRDgqvQ=="],
|
||||
"chrome-devtools-mcp": ["chrome-devtools-mcp@0.20.3", "", { "bin": { "chrome-devtools-mcp": "build/src/bin/chrome-devtools-mcp.js", "chrome-devtools": "build/src/bin/chrome-devtools.js" } }, "sha512-6MlNKlKa+J1FX9w4SUnFERF4MRGWLlrnZvIJGhhsuuMPM7qUG0F4SwheRyjwl0+tsTemxMCBHiib8mXkg5j6og=="],
|
||||
|
||||
"chrome-launcher": ["chrome-launcher@1.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^2.0.1" }, "bin": { "print-chrome-path": "bin/print-chrome-path.cjs" } }, "sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q=="],
|
||||
|
||||
@@ -2619,6 +2684,8 @@
|
||||
|
||||
"execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
|
||||
|
||||
"exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="],
|
||||
|
||||
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||
|
||||
"express-rate-limit": ["express-rate-limit@8.2.1", "", { "dependencies": { "ip-address": "10.0.1" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g=="],
|
||||
@@ -2677,6 +2744,8 @@
|
||||
|
||||
"file-selector": ["file-selector@0.5.0", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA=="],
|
||||
|
||||
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
|
||||
|
||||
"filesize": ["filesize@11.0.13", "", {}, "sha512-mYJ/qXKvREuO0uH8LTQJ6v7GsUvVOguqxg2VTwQUkyTPXXRRWPdjuUPVqdBrJQhvci48OHlNGRnux+Slr2Rnvw=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
@@ -2755,6 +2824,8 @@
|
||||
|
||||
"get-value": ["get-value@2.0.6", "", {}, "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA=="],
|
||||
|
||||
"gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
|
||||
|
||||
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
|
||||
|
||||
"giscus": ["giscus@1.6.0", "", { "dependencies": { "lit": "^3.2.1" } }, "sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ=="],
|
||||
@@ -2893,6 +2964,8 @@
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="],
|
||||
|
||||
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
|
||||
|
||||
"immer": ["immer@11.1.3", "", {}, "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q=="],
|
||||
@@ -3017,12 +3090,16 @@
|
||||
|
||||
"jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="],
|
||||
|
||||
"jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
|
||||
|
||||
"joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
|
||||
|
||||
"jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
|
||||
|
||||
"js-base64": ["js-base64@3.7.7", "", {}, "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="],
|
||||
|
||||
"js-cookie": ["js-cookie@3.0.5", "", {}, "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="],
|
||||
@@ -3467,6 +3544,8 @@
|
||||
|
||||
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
||||
|
||||
"omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
|
||||
|
||||
"on-change": ["on-change@4.0.2", "", {}, "sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA=="],
|
||||
|
||||
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
|
||||
@@ -3521,6 +3600,12 @@
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],
|
||||
|
||||
"parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="],
|
||||
|
||||
"parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="],
|
||||
|
||||
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
|
||||
|
||||
"parse-filepath": ["parse-filepath@1.0.2", "", { "dependencies": { "is-absolute": "^1.0.0", "map-cache": "^0.2.0", "path-root": "^0.1.1" } }, "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q=="],
|
||||
@@ -3543,6 +3628,8 @@
|
||||
|
||||
"path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
|
||||
|
||||
"path-expression-matcher": ["path-expression-matcher@1.2.0", "", {}, "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
@@ -3561,6 +3648,8 @@
|
||||
|
||||
"peberminta": ["peberminta@0.9.0", "", {}, "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ=="],
|
||||
|
||||
"peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
|
||||
|
||||
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
|
||||
|
||||
"perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="],
|
||||
@@ -3583,12 +3672,16 @@
|
||||
|
||||
"pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
|
||||
|
||||
"pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],
|
||||
|
||||
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
|
||||
|
||||
"pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="],
|
||||
|
||||
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
||||
|
||||
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
|
||||
|
||||
"points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="],
|
||||
|
||||
"points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="],
|
||||
@@ -3809,6 +3902,8 @@
|
||||
|
||||
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
|
||||
|
||||
"readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="],
|
||||
|
||||
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||
|
||||
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
|
||||
@@ -3997,6 +4092,8 @@
|
||||
|
||||
"simple-git": ["simple-git@3.30.0", "", { "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", "debug": "^4.4.0" } }, "sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg=="],
|
||||
|
||||
"simple-xml-to-json": ["simple-xml-to-json@1.2.4", "", {}, "sha512-3MY16e0ocMHL7N1ufpdObURGyX+lCo0T/A+y6VCwosLdH1HSda4QZl1Sdt/O+2qWp48WFi26XEp5rF0LoaL0Dg=="],
|
||||
|
||||
"sinon": ["sinon@21.0.1", "", { "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "^15.1.0", "@sinonjs/samsam": "^8.0.3", "diff": "^8.0.2", "supports-color": "^7.2.0" } }, "sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw=="],
|
||||
|
||||
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
||||
@@ -4093,6 +4190,8 @@
|
||||
|
||||
"strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="],
|
||||
|
||||
"strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
|
||||
|
||||
"stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],
|
||||
|
||||
"stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="],
|
||||
@@ -4149,6 +4248,8 @@
|
||||
|
||||
"timeout-signal": ["timeout-signal@2.0.0", "", {}, "sha512-YBGpG4bWsHoPvofT6y/5iqulfXIiIErl5B0LdtHT1mGXDFTAhhRrbUpTvBgYbovr+3cKblya2WAOcpoy90XguA=="],
|
||||
|
||||
"tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
@@ -4165,6 +4266,8 @@
|
||||
|
||||
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||
|
||||
"token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],
|
||||
|
||||
"tokenlens": ["tokenlens@1.3.1", "", { "dependencies": { "@tokenlens/core": "1.3.0", "@tokenlens/fetch": "1.3.0", "@tokenlens/helpers": "1.3.1", "@tokenlens/models": "1.3.0" } }, "sha512-7oxmsS5PNCX3z+b+z07hL5vCzlgHKkCGrEQjQmWl5l+v5cUrtL7S1cuST4XThaL1XyjbTX8J5hfP0cjDJRkaLA=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
@@ -4283,6 +4386,8 @@
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
||||
|
||||
"utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
@@ -4377,6 +4482,8 @@
|
||||
|
||||
"xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
|
||||
|
||||
"xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
|
||||
|
||||
"xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
|
||||
|
||||
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
|
||||
@@ -4449,7 +4556,9 @@
|
||||
|
||||
"@browseros/agent/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="],
|
||||
|
||||
"@browseros/eval/@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
|
||||
"@browseros/eval/@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1014.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/credential-provider-node": "^3.972.24", "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", "@aws-sdk/middleware-expect-continue": "^3.972.8", "@aws-sdk/middleware-flexible-checksums": "^3.974.3", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-location-constraint": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-sdk-s3": "^3.972.23", "@aws-sdk/middleware-ssec": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/signature-v4-multi-region": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-blob-browser": "^4.2.13", "@smithy/hash-node": "^4.2.12", "@smithy/hash-stream-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/md5-js": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-0XLrOT4Cm3NEhhiME7l/8LbTXS4KdsbR4dSrY207KNKTcHLLTZ9EXt4ZpgnTfLvWQF3pGP2us4Zi1fYLo0N+Ow=="],
|
||||
|
||||
"@browseros/eval/@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
||||
|
||||
"@browseros/server/@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
||||
|
||||
@@ -4567,6 +4676,8 @@
|
||||
|
||||
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"@jimp/core/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
||||
|
||||
"@lexical/react/react-error-boundary": ["react-error-boundary@3.1.4", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "react": ">=16.13.1" } }, "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA=="],
|
||||
|
||||
"@lobehub/fluent-emoji/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
@@ -4985,6 +5096,8 @@
|
||||
|
||||
"http-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],
|
||||
|
||||
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"is-inside-container/is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
|
||||
@@ -5049,12 +5162,16 @@
|
||||
|
||||
"pac-proxy-agent/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"parse-bmfont-xml/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
|
||||
|
||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||
|
||||
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"pino/pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
|
||||
|
||||
"pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
|
||||
|
||||
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
@@ -5191,7 +5308,101 @@
|
||||
|
||||
"@browseros/agent/@types/bun/bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
|
||||
|
||||
"@browseros/eval/@types/bun/bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/core": ["@aws-sdk/core@3.973.23", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.15", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-aoJncvD1XvloZ9JLnKqTRL9dBy+Szkryoag9VT+V1TqsuUgIxV9cnBVM/hrDi2vE8bDqLiDR8nirdRcCdtJu0w=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.24", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.21", "@aws-sdk/credential-provider-http": "^3.972.23", "@aws-sdk/credential-provider-ini": "^3.972.23", "@aws-sdk/credential-provider-process": "^3.972.21", "@aws-sdk/credential-provider-sso": "^3.972.23", "@aws-sdk/credential-provider-web-identity": "^3.972.23", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-9Jwi7aps3AfUicJyF5udYadPypPpCwUZ6BSKr/QjRbVCpRVS1wc+1Q6AEZ/qz8J4JraeRd247pSzyMQSIHVebw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.3", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/crc64-nvme": "^3.972.5", "@aws-sdk/types": "^3.973.6", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-fB7FNLH1+VPUs0QL3PLrHW+DD4gKu6daFgWtyq3R0Y0Lx8DLZPvyGAxCZNFBxH+M2xt9KvBJX6USwjuqvitmCQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-50QgHGPQAb2veqFOmTF1A3GsAklLHZXL47KbY35khIkfbXH5PLvqpEc/gOAEBPj/yFxrlgxz/8mqWcWTNxBkwQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.24", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-dLTWy6IfAMhNiSEvMr07g/qZ54be6pLqlxVblbF6AzafmmGAzMMj8qMoY9B4+YgT+gY9IcuxZslNh03L6PyMCQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.9", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-eQ+dFU05ZRC/lC2XpYlYSPlXtX3VT8sn5toxN2Fv7EXlMoA2p9V7vUBKqHunfD4TRLpxUq8Y8Ol/nCqiv327Ng=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.11", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.23", "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-SKgZY7x6AloLUXO20FJGnkKJ3a6CXzNDt6PYs2yqoPzgU0xKWcUoGGJGEBTsfM5eihKW42lbwp+sXzACLbSsaA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.10", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-E99zeTscCc+pTMfsvnfi6foPpKmdD1cZfOC7/P8UUrjsoQdg9VEWPRD+xdFduKnfPXwcvby58AlO9jwwF6U96g=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.13", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/md5-js": ["@smithy/md5-js@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-stream": ["@smithy/util-stream@4.5.20", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-waiter": ["@smithy/util-waiter@4.2.13", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ=="],
|
||||
|
||||
"@browseros/eval/@types/bun/bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
|
||||
|
||||
"@browseros/server/@types/bun/bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
||||
|
||||
@@ -5505,6 +5716,70 @@
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.15", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-PxMRlCFNiQnke9YR29vjFQwz4jq+6Q04rOVFeTDR2K7Qpv9h9FOWOxG+zJjageimYbWqE3bTuLjmryWHAWbvaA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/core/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-BkAfKq8Bd4shCtec1usNz//urPJF/SZy14qJyxkSaRJQ/Vv1gVh0VZSTmS7aE6aLMELkFV5wHHrS9ZcdG8Kxsg=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-4XZ3+Gu5DY8/n8zQFHBgcKTF7hWQl42G6CY9xfXVo2d25FM/lYkpmuzhYopYoPL1ITWkJ2OSBQfYEu5JRfHOhA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/credential-provider-env": "^3.972.21", "@aws-sdk/credential-provider-http": "^3.972.23", "@aws-sdk/credential-provider-login": "^3.972.23", "@aws-sdk/credential-provider-process": "^3.972.21", "@aws-sdk/credential-provider-sso": "^3.972.23", "@aws-sdk/credential-provider-web-identity": "^3.972.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-PZLSmU0JFpNCDFReidBezsgL5ji9jOBry8CnZdw4Jj6d0K2z3Ftnp44NXgADqYx5BLMu/ZHujfeJReaDoV+IwQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-nRxbeOJ1E1gVA0lNQezuMVndx+ZcuyaW/RB05pUsznN5BxykSlH6KkZ/7Ca/ubJf3i5N3p0gwNO5zgPSCzj+ww=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/token-providers": "3.1014.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-APUccADuYPLL0f2htpM8Z4czabSmHOdo4r41W6lKEZdy++cNJ42Radqy6x4TopENzr3hR6WYMyhiuiqtbf/nAA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-H5JNqtIwOu/feInmMMWcK0dL5r897ReEn7n2m16Dd0DPD9gA2Hg8Cq4UDzZ/9OzaLh/uqBM6seixz0U6Fi2Eag=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-bucket-endpoint/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-flexible-checksums/@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.5", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/middleware-sdk-s3/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/signature-v4-multi-region/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/middleware-endpoint/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/middleware-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/node-config-provider/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-defaults-mode-browser/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-defaults-mode-node/@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-retry/@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="],
|
||||
|
||||
"@google/gemini-cli-core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
|
||||
|
||||
"@google/gemini-cli-core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="],
|
||||
@@ -5549,6 +5824,22 @@
|
||||
|
||||
"wxt/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.23", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-OmE/pSkbMM3dCj1HdOnZ5kXnKK+R/Yz+kbBugraBecp0pGAs21eEURfQRz+1N2gzIHLVyGIP1MEjk/uSrFsngg=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1014.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.23", "@aws-sdk/nested-clients": "^3.996.13", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-gHTHNUoaOGNrSWkl32A7wFsU78jlNTlqMccLu0byUk5CysYYXaxNMIonIVr4YcykC7vgtDS5ABuz83giy6fzJA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.13", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.23", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.24", "@aws-sdk/region-config-resolver": "^3.972.9", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.10", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-retry": "^4.4.44", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.43", "@smithy/util-defaults-mode-node": "^4.2.47", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ptZ1HF4yYHNJX8cgFF+8NdYO69XJKZn7ft0/ynV3c0hCbN+89fAbrLS+fqniU2tW8o9Kfqhj8FUh+IPXb2Qsuw=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/eventstream-serde-browser/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@smithy/eventstream-serde-node/@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
|
||||
|
||||
"@google/genai/google-auth-library/gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"@google/genai/google-auth-library/gaxios/rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
@@ -5563,6 +5854,8 @@
|
||||
|
||||
"publish-browser-extension/listr2/cli-truncate/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"@browseros/eval/@aws-sdk/client-s3/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
|
||||
|
||||
"@google/genai/google-auth-library/gaxios/rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"@google/genai/google-auth-library/gaxios/rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
"types": "./src/types/logger.ts",
|
||||
"default": "./src/types/logger.ts"
|
||||
},
|
||||
"./types/server-config": {
|
||||
"types": "./src/types/server-config.ts",
|
||||
"default": "./src/types/server-config.ts"
|
||||
},
|
||||
"./schemas/llm": {
|
||||
"types": "./src/schemas/llm.ts",
|
||||
"default": "./src/schemas/llm.ts"
|
||||
|
||||
@@ -16,6 +16,7 @@ export const PATHS = {
|
||||
CORE_MEMORY_FILE_NAME: 'CORE.md',
|
||||
SKILLS_DIR_NAME: 'skills',
|
||||
BUILTIN_DIR_NAME: 'builtin',
|
||||
SERVER_CONFIG_FILE_NAME: 'server.json',
|
||||
SOUL_MAX_LINES: 150,
|
||||
MEMORY_RETENTION_DAYS: 30,
|
||||
SESSION_RETENTION_DAYS: 30,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 BrowserOS
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*
|
||||
* Shape of ~/.browseros/server.json written by the server on startup.
|
||||
* The CLI reads this file for auto-discovery of the server URL.
|
||||
*/
|
||||
|
||||
export interface ServerDiscoveryConfig {
|
||||
server_port: number
|
||||
url: string
|
||||
server_version: string
|
||||
browseros_version?: string
|
||||
chromium_version?: string
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
140
|
||||
141
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
BROWSEROS_MAJOR=0
|
||||
BROWSEROS_MINOR=44
|
||||
BROWSEROS_BUILD=0
|
||||
BROWSEROS_PATCH=0
|
||||
BROWSEROS_PATCH=1
|
||||
|
||||
Reference in New Issue
Block a user