mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-17 12:58:34 -04:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 667e6594ce | |||
| d4e363e175 | |||
| 14c2f7cab2 | |||
| 01fbe4027e | |||
| cc53db4406 | |||
| 5e651053cc | |||
| 9d4b6c6589 | |||
| fa055143ea | |||
| 216b479f7a | |||
| 6eece351c4 | |||
| 875b28658f | |||
| 0f90793df2 | |||
| 46e832b1fd | |||
| 0e022036fb | |||
| f0ae3b9569 | |||
| 3b5837d354 | |||
| e1ff217e44 | |||
| ecda3779fa | |||
| a3e69a967b | |||
| 6359623e24 | |||
| e26473f0bc | |||
| 9b4b36dfd0 | |||
| 100487719b | |||
| 6106cb64c7 | |||
| c786ab92d3 | |||
| c400746dd5 | |||
| e3ce37899d | |||
| c5e58b38a8 | |||
| 1f86a6d3f0 | |||
| be8f975a99 | |||
| c7abb340f1 | |||
| 45771a7e39 | |||
| 39810da936 | |||
| 01c8bf20f9 | |||
| 9241a79dc9 | |||
| e73fa8f4b7 | |||
| fd2699b4d7 | |||
| ec10d71f22 | |||
| ca589273c7 | |||
| 75a979ec5c | |||
| d4f10fa9be | |||
| 08dd3f51ed | |||
| 0e99cb987a | |||
| 7731d1235d | |||
| 174c0a742c | |||
| b080f216a5 | |||
| 0d7fe6e074 | |||
| 01b7b53eeb | |||
| 613c570a3b | |||
| a48d44955e | |||
| 8251934007 | |||
| 42e345e1bc | |||
| 6a7d6c5adc | |||
| 9e22c40fff | |||
| 467722c2f9 | |||
| d9dfceddf8 | |||
| 4fee4d7d86 | |||
| 61d7f942b5 | |||
| d8e1753330 | |||
| d94d520f45 | |||
| fcc1e9c42f | |||
| 64b9ba339e |
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
"@opencode-ai/core": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
Simplify interrupt continuation: the steer-scoped resume decision now lives in SessionExecution as a post-cleanup inbox check, and the run coordinator drops its continuation state machine. Wakes arriving during cancellation cleanup now restart a normal full drain, and interrupting an idle session with continue now resumes pending steering input.
|
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
name: "Setup Bun"
|
name: "Setup Bun"
|
||||||
description: "Setup Bun with caching and install dependencies"
|
description: "Setup Bun with caching and install dependencies"
|
||||||
inputs:
|
inputs:
|
||||||
|
bun-version:
|
||||||
|
description: "Bun version to install instead of the root packageManager version"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
install-flags:
|
install-flags:
|
||||||
description: "Additional flags to pass to 'bun install'"
|
description: "Additional flags to pass to 'bun install'"
|
||||||
required: false
|
required: false
|
||||||
@@ -20,19 +24,22 @@ runs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
if [ "$RUNNER_ARCH" = "X64" ]; then
|
if [ "$RUNNER_ARCH" = "X64" ]; then
|
||||||
V=$(node -p "require('./package.json').packageManager.split('@')[1]")
|
V="${{ inputs.bun-version }}"
|
||||||
|
if [ -z "$V" ]; then V=$(node -p "require('./package.json').packageManager.split('@')[1]"); fi
|
||||||
|
TAG=$([ "$V" = "canary" ] && echo "canary" || echo "bun-v${V}")
|
||||||
case "$RUNNER_OS" in
|
case "$RUNNER_OS" in
|
||||||
macOS) OS=darwin ;;
|
macOS) OS=darwin ;;
|
||||||
Linux) OS=linux ;;
|
Linux) OS=linux ;;
|
||||||
Windows) OS=windows ;;
|
Windows) OS=windows ;;
|
||||||
esac
|
esac
|
||||||
echo "url=https://github.com/oven-sh/bun/releases/download/bun-v${V}/bun-${OS}-x64-baseline.zip" >> "$GITHUB_OUTPUT"
|
echo "url=https://github.com/oven-sh/bun/releases/download/${TAG}/bun-${OS}-x64-baseline.zip" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||||
with:
|
with:
|
||||||
bun-version-file: ${{ !steps.bun-url.outputs.url && 'package.json' || '' }}
|
bun-version: ${{ !steps.bun-url.outputs.url && inputs.bun-version || '' }}
|
||||||
|
bun-version-file: ${{ !steps.bun-url.outputs.url && !inputs.bun-version && 'package.json' || '' }}
|
||||||
bun-download-url: ${{ steps.bun-url.outputs.url }}
|
bun-download-url: ${{ steps.bun-url.outputs.url }}
|
||||||
|
|
||||||
- name: Get cache directory
|
- name: Get cache directory
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
name: beta
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 * * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sync:
|
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Bun
|
|
||||||
uses: ./.github/actions/setup-bun
|
|
||||||
|
|
||||||
- name: Setup Git Committer
|
|
||||||
id: setup-git-committer
|
|
||||||
uses: ./.github/actions/setup-git-committer
|
|
||||||
with:
|
|
||||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
|
||||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
|
||||||
|
|
||||||
- name: Install OpenCode
|
|
||||||
run: bun i -g opencode-ai
|
|
||||||
|
|
||||||
- name: Sync beta branch
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ steps.setup-git-committer.outputs.token }}
|
|
||||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
|
||||||
run: bun script/beta.ts
|
|
||||||
@@ -7,6 +7,7 @@ on:
|
|||||||
- ci
|
- ci
|
||||||
- dev
|
- dev
|
||||||
- beta
|
- beta
|
||||||
|
- v2
|
||||||
- fix/npm-native-binary-install
|
- fix/npm-native-binary-install
|
||||||
- snapshot-*
|
- snapshot-*
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -32,7 +33,7 @@ permissions:
|
|||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'v2' && 'next') || '' }}
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'v2' && 'dev') || '' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
version:
|
version:
|
||||||
@@ -45,6 +46,13 @@ jobs:
|
|||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
- uses: ./.github/actions/setup-bun
|
||||||
|
|
||||||
|
- name: Deploy update service
|
||||||
|
if: github.ref_name == 'v2' || github.ref_name == 'beta'
|
||||||
|
working-directory: packages/updates
|
||||||
|
run: bun run deploy
|
||||||
|
env:
|
||||||
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
|
||||||
- name: Setup git committer
|
- name: Setup git committer
|
||||||
id: committer
|
id: committer
|
||||||
uses: ./.github/actions/setup-git-committer
|
uses: ./.github/actions/setup-git-committer
|
||||||
@@ -74,13 +82,15 @@ jobs:
|
|||||||
build-cli:
|
build-cli:
|
||||||
needs: version
|
needs: version
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'beta'
|
if: github.repository == 'anomalyco/opencode'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
with:
|
with:
|
||||||
fetch-tags: true
|
fetch-tags: true
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-bun
|
- uses: ./.github/actions/setup-bun
|
||||||
|
with:
|
||||||
|
bun-version: canary # Bun 1.4 until its stable release is published
|
||||||
|
|
||||||
- name: Setup git committer
|
- name: Setup git committer
|
||||||
id: committer
|
id: committer
|
||||||
@@ -102,6 +112,7 @@ jobs:
|
|||||||
id: build
|
id: build
|
||||||
run: ./packages/cli/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
|
run: ./packages/cli/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
|
||||||
env:
|
env:
|
||||||
|
BUN_COMPILE_RELEASE: canary
|
||||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||||
@@ -185,7 +196,7 @@ jobs:
|
|||||||
|
|
||||||
build-node-cli:
|
build-node-cli:
|
||||||
needs: version
|
needs: version
|
||||||
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'beta'
|
if: github.repository == 'anomalyco/opencode'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -335,6 +346,7 @@ jobs:
|
|||||||
build-electron:
|
build-electron:
|
||||||
needs:
|
needs:
|
||||||
- version
|
- version
|
||||||
|
- sign-cli-macos
|
||||||
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2'
|
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2'
|
||||||
continue-on-error: false
|
continue-on-error: false
|
||||||
env:
|
env:
|
||||||
@@ -373,6 +385,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
|
if: github.ref_name == 'beta'
|
||||||
|
with:
|
||||||
|
name: opencode-preview-cli
|
||||||
|
path: packages/cli/dist
|
||||||
|
|
||||||
- uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0
|
- uses: apple-actions/import-codesign-certs@8f3fb608891dd2244cdab3d69cd68c0d37a7fe93 # v2.0.0
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
with:
|
with:
|
||||||
@@ -431,6 +449,7 @@ jobs:
|
|||||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
|
||||||
OPENCODE_CLI_TARGET: ${{ matrix.settings.target }}
|
OPENCODE_CLI_TARGET: ${{ matrix.settings.target }}
|
||||||
|
OPENCODE_CLI_DIST: ${{ (github.ref_name == 'beta' && format('{0}/packages/cli/dist', github.workspace)) || '' }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: bun run build
|
run: bun run build
|
||||||
@@ -447,6 +466,7 @@ jobs:
|
|||||||
VITE_SENTRY_ENVIRONMENT: ${{ (github.ref_name == 'beta' && 'beta') || 'production' }}
|
VITE_SENTRY_ENVIRONMENT: ${{ (github.ref_name == 'beta' && 'beta') || 'production' }}
|
||||||
VITE_SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
VITE_SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||||
OPENCODE_CLI_TARGET: ${{ matrix.settings.target }}
|
OPENCODE_CLI_TARGET: ${{ matrix.settings.target }}
|
||||||
|
OPENCODE_CLI_DIST: ${{ github.workspace }}/packages/cli/dist
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
if: needs.version.outputs.release
|
if: needs.version.outputs.release
|
||||||
@@ -569,13 +589,11 @@ jobs:
|
|||||||
path: packages/opencode/dist
|
path: packages/opencode/dist
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
if: github.ref_name != 'beta'
|
|
||||||
with:
|
with:
|
||||||
name: opencode-preview-cli
|
name: opencode-preview-cli
|
||||||
path: packages/cli/dist
|
path: packages/cli/dist
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
if: github.ref_name != 'beta'
|
|
||||||
with:
|
with:
|
||||||
pattern: opencode-node-cli-*
|
pattern: opencode-node-cli-*
|
||||||
path: packages/cli/dist/node
|
path: packages/cli/dist/node
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
- Run `bun run dev:live` from a development worktree to test its TUI against the currently elected `opencode2` background server and live sessions.
|
- Run `bun run dev:live` from a development worktree to test its TUI against the currently elected `opencode2` background server and live sessions.
|
||||||
- Pass a directory after the script when needed, for example `bun run dev:live /path/to/project`.
|
- Pass a directory after the script when needed, for example `bun run dev:live /path/to/project`.
|
||||||
- The script discovers the server with `opencode2 service status`, injects its private local credential from `opencode2 service get password`, and uses the `next` TUI storage channel so tabs and other client-local state match the installed client.
|
- The script discovers the server with `opencode2 service status`, injects its private local credential from `opencode2 service get password`, and uses the `dev` TUI storage channel so tabs and other client-local state match the installed client.
|
||||||
- Prefer `dev:live` over plain `bun run dev` for this workflow. An implicit managed-service connection may replace the live server when the worktree client version differs; explicit `--server` warns and continues without replacing it.
|
- Prefer `dev:live` over plain `bun run dev` for this workflow. An implicit managed-service connection may replace the live server when the worktree client version differs; explicit `--server` warns and continues without replacing it.
|
||||||
|
|
||||||
## V2 TUI Stories
|
## V2 TUI Stories
|
||||||
|
|||||||
@@ -81,7 +81,6 @@
|
|||||||
"@solidjs/router": "catalog:",
|
"@solidjs/router": "catalog:",
|
||||||
"@tanstack/solid-query": "5.91.4",
|
"@tanstack/solid-query": "5.91.4",
|
||||||
"@tanstack/solid-virtual": "catalog:",
|
"@tanstack/solid-virtual": "catalog:",
|
||||||
"@thisbeyond/solid-dnd": "0.7.5",
|
|
||||||
"diff": "catalog:",
|
"diff": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
@@ -189,12 +188,15 @@
|
|||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
|
"solid-js": "catalog:",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"effect": "4.0.0-beta.101",
|
"effect": "4.0.0-beta.101",
|
||||||
|
"solid-js": ">=1.9.0",
|
||||||
},
|
},
|
||||||
"optionalPeers": [
|
"optionalPeers": [
|
||||||
"effect",
|
"effect",
|
||||||
|
"solid-js",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"packages/codemode": {
|
"packages/codemode": {
|
||||||
@@ -1028,7 +1030,7 @@
|
|||||||
"packages/www": {
|
"packages/www": {
|
||||||
"name": "@opencode-ai/www",
|
"name": "@opencode-ai/www",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"blume": "1.1.4",
|
"blume": "1.5.1",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/cloudflare": "14.1.4",
|
"@astrojs/cloudflare": "14.1.4",
|
||||||
@@ -1289,6 +1291,12 @@
|
|||||||
|
|
||||||
"@astrojs/yaml2ts": ["@astrojs/yaml2ts@0.2.4", "", { "dependencies": { "yaml": "^2.8.3" } }, "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A=="],
|
"@astrojs/yaml2ts": ["@astrojs/yaml2ts@0.2.4", "", { "dependencies": { "yaml": "^2.8.3" } }, "sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A=="],
|
||||||
|
|
||||||
|
"@asyncapi/converter": ["@asyncapi/converter@2.0.2", "", { "dependencies": { "@asyncapi/parser": "^3.6.2", "js-yaml": "^3.14.1", "path": "^0.12.7" } }, "sha512-tFvT2ijEriTe9CPCMP32fh2VPzdQiUupK63MyW7UBDCtENANPZJ0aboZuVPhgBhmqo2TxcHWbZANpmXc5sEFiw=="],
|
||||||
|
|
||||||
|
"@asyncapi/parser": ["@asyncapi/parser@3.6.3", "", { "dependencies": { "@asyncapi/specs": "^6.11.1", "@openapi-contrib/openapi-schema-to-json-schema": "~3.2.0", "@stoplight/json": "3.21.0", "@stoplight/json-ref-readers": "^1.2.2", "@stoplight/json-ref-resolver": "^3.1.5", "@stoplight/spectral-core": "^1.18.3", "@stoplight/spectral-functions": "^1.7.2", "@stoplight/spectral-parsers": "^1.0.2", "@stoplight/spectral-ref-resolver": "^1.0.3", "@stoplight/types": "^13.12.0", "@types/json-schema": "^7.0.11", "@types/urijs": "^1.19.19", "ajv": "^8.18.0", "ajv-errors": "^3.0.0", "ajv-formats": "^2.1.1", "avsc": "^5.7.5", "js-yaml": "^4.3.1", "jsonpath-plus": "^10.0.7", "node-fetch": "2.6.7" } }, "sha512-MUC8xIUMcS2qNvqrqyx/ie0txu3d/OdIsrXs7UCzawdyR6P07gh35DpOqPz/z57s1UA3vERVpcheZYl3h8cVtw=="],
|
||||||
|
|
||||||
|
"@asyncapi/specs": ["@asyncapi/specs@6.11.1", "", { "dependencies": { "@types/json-schema": "^7.0.11" } }, "sha512-A3WBLqAKGoJ2+6FWFtpjBlCQ1oFCcs4GxF7zsIGvNqp/klGUHjlA3aAcZ9XMMpLGE8zPeYDz2x9FmO6DSuKraQ=="],
|
||||||
|
|
||||||
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
||||||
|
|
||||||
"@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="],
|
"@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="],
|
||||||
@@ -1799,6 +1807,12 @@
|
|||||||
|
|
||||||
"@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="],
|
"@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="],
|
||||||
|
|
||||||
|
"@jsep-plugin/assignment": ["@jsep-plugin/assignment@1.3.0", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ=="],
|
||||||
|
|
||||||
|
"@jsep-plugin/regex": ["@jsep-plugin/regex@1.0.4", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg=="],
|
||||||
|
|
||||||
|
"@jsep-plugin/ternary": ["@jsep-plugin/ternary@1.1.4", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-ck5wiqIbqdMX6WRQztBL7ASDty9YLgJ3sSAK5ZpBzXeySvFGCzIvM6UiAI4hTZ22fEcYQVV/zhUbNscggW+Ukg=="],
|
||||||
|
|
||||||
"@jsx-email/all": ["@jsx-email/all@2.2.3", "", { "dependencies": { "@jsx-email/body": "1.0.2", "@jsx-email/button": "1.0.4", "@jsx-email/column": "1.0.3", "@jsx-email/container": "1.0.2", "@jsx-email/font": "1.0.3", "@jsx-email/head": "1.0.2", "@jsx-email/heading": "1.0.2", "@jsx-email/hr": "1.0.2", "@jsx-email/html": "1.0.2", "@jsx-email/img": "1.0.2", "@jsx-email/link": "1.0.2", "@jsx-email/markdown": "2.0.4", "@jsx-email/preview": "1.0.2", "@jsx-email/render": "1.1.1", "@jsx-email/row": "1.0.2", "@jsx-email/section": "1.0.2", "@jsx-email/tailwind": "2.4.4", "@jsx-email/text": "1.0.2" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-OBvLe/hVSQc0LlMSTJnkjFoqs3bmxcC4zpy/5pT5agPCSKMvAKQjzmsc2xJ2wO73jSpRV1K/g38GmvdCfrhSoQ=="],
|
"@jsx-email/all": ["@jsx-email/all@2.2.3", "", { "dependencies": { "@jsx-email/body": "1.0.2", "@jsx-email/button": "1.0.4", "@jsx-email/column": "1.0.3", "@jsx-email/container": "1.0.2", "@jsx-email/font": "1.0.3", "@jsx-email/head": "1.0.2", "@jsx-email/heading": "1.0.2", "@jsx-email/hr": "1.0.2", "@jsx-email/html": "1.0.2", "@jsx-email/img": "1.0.2", "@jsx-email/link": "1.0.2", "@jsx-email/markdown": "2.0.4", "@jsx-email/preview": "1.0.2", "@jsx-email/render": "1.1.1", "@jsx-email/row": "1.0.2", "@jsx-email/section": "1.0.2", "@jsx-email/tailwind": "2.4.4", "@jsx-email/text": "1.0.2" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-OBvLe/hVSQc0LlMSTJnkjFoqs3bmxcC4zpy/5pT5agPCSKMvAKQjzmsc2xJ2wO73jSpRV1K/g38GmvdCfrhSoQ=="],
|
||||||
|
|
||||||
"@jsx-email/body": ["@jsx-email/body@1.0.2", "", { "peerDependencies": { "react": "^18.2.0" } }, "sha512-NjR2tgLH4XGfGkm+O8kcVwi9MBqZsXZCLlmk3HlMux3/n/+a5zB+yhJqXWZBJl2i+6cSF+E2O6hK11ekyK9WWQ=="],
|
"@jsx-email/body": ["@jsx-email/body@1.0.2", "", { "peerDependencies": { "react": "^18.2.0" } }, "sha512-NjR2tgLH4XGfGkm+O8kcVwi9MBqZsXZCLlmk3HlMux3/n/+a5zB+yhJqXWZBJl2i+6cSF+E2O6hK11ekyK9WWQ=="],
|
||||||
@@ -1985,6 +1999,8 @@
|
|||||||
|
|
||||||
"@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="],
|
"@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="],
|
||||||
|
|
||||||
|
"@openapi-contrib/openapi-schema-to-json-schema": ["@openapi-contrib/openapi-schema-to-json-schema@3.2.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" } }, "sha512-Gj6C0JwCr8arj0sYuslWXUBSP/KnUlEGnPW4qxlXvAl543oaNQgMgIgkQUA6vs5BCCvwTEiL8m/wdWzfl4UvSw=="],
|
||||||
|
|
||||||
"@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="],
|
"@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="],
|
||||||
|
|
||||||
"@opencode-ai/ai": ["@opencode-ai/ai@workspace:packages/ai"],
|
"@opencode-ai/ai": ["@opencode-ai/ai@workspace:packages/ai"],
|
||||||
@@ -2585,6 +2601,8 @@
|
|||||||
|
|
||||||
"@scalar/validation": ["@scalar/validation@0.6.2", "", {}, "sha512-Sc1TkcwGV6aVCO51AyKeaGiP8gpwAHxEtO5d3tZzPV+KsnlC/YokQxFxwBrbIXw73k9hmcExnJyGu3k5i6n6VA=="],
|
"@scalar/validation": ["@scalar/validation@0.6.2", "", {}, "sha512-Sc1TkcwGV6aVCO51AyKeaGiP8gpwAHxEtO5d3tZzPV+KsnlC/YokQxFxwBrbIXw73k9hmcExnJyGu3k5i6n6VA=="],
|
||||||
|
|
||||||
|
"@scarf/scarf": ["@scarf/scarf@1.4.0", "", {}, "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ=="],
|
||||||
|
|
||||||
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
|
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
|
||||||
|
|
||||||
"@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.36.0", "", { "dependencies": { "@sentry/core": "10.36.0" } }, "sha512-WILVR8HQBWOxbqLRuTxjzRCMIACGsDTo6jXvzA8rz6ezElElLmIrn3CFAswrESLqEEUa4CQHl5bLgSVJCRNweA=="],
|
"@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.36.0", "", { "dependencies": { "@sentry/core": "10.36.0" } }, "sha512-WILVR8HQBWOxbqLRuTxjzRCMIACGsDTo6jXvzA8rz6ezElElLmIrn3CFAswrESLqEEUa4CQHl5bLgSVJCRNweA=="],
|
||||||
@@ -2807,6 +2825,36 @@
|
|||||||
|
|
||||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||||
|
|
||||||
|
"@stoplight/better-ajv-errors": ["@stoplight/better-ajv-errors@1.0.3", "", { "dependencies": { "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA=="],
|
||||||
|
|
||||||
|
"@stoplight/json": ["@stoplight/json@3.21.0", "", { "dependencies": { "@stoplight/ordered-object-literal": "^1.0.3", "@stoplight/path": "^1.3.2", "@stoplight/types": "^13.6.0", "jsonc-parser": "~2.2.1", "lodash": "^4.17.21", "safe-stable-stringify": "^1.1" } }, "sha512-5O0apqJ/t4sIevXCO3SBN9AHCEKKR/Zb4gaj7wYe5863jme9g02Q0n/GhM7ZCALkL+vGPTe4ZzTETP8TFtsw3g=="],
|
||||||
|
|
||||||
|
"@stoplight/json-ref-readers": ["@stoplight/json-ref-readers@1.2.2", "", { "dependencies": { "node-fetch": "^2.6.0", "tslib": "^1.14.1" } }, "sha512-nty0tHUq2f1IKuFYsLM4CXLZGHdMn+X/IwEUIpeSOXt0QjMUbL0Em57iJUDzz+2MkWG83smIigNZ3fauGjqgdQ=="],
|
||||||
|
|
||||||
|
"@stoplight/json-ref-resolver": ["@stoplight/json-ref-resolver@3.1.6", "", { "dependencies": { "@stoplight/json": "^3.21.0", "@stoplight/path": "^1.3.2", "@stoplight/types": "^12.3.0 || ^13.0.0", "@types/urijs": "^1.19.19", "dependency-graph": "~0.11.0", "fast-memoize": "^2.5.2", "immer": "^9.0.6", "lodash": "^4.17.21", "tslib": "^2.6.0", "urijs": "^1.19.11" } }, "sha512-YNcWv3R3n3U6iQYBsFOiWSuRGE5su1tJSiX6pAPRVk7dP0L7lqCteXGzuVRQ0gMZqUl8v1P0+fAKxF6PLo9B5A=="],
|
||||||
|
|
||||||
|
"@stoplight/ordered-object-literal": ["@stoplight/ordered-object-literal@1.0.5", "", {}, "sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg=="],
|
||||||
|
|
||||||
|
"@stoplight/path": ["@stoplight/path@1.3.2", "", {}, "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-core": ["@stoplight/spectral-core@1.23.1", "", { "dependencies": { "@scarf/scarf": "^1.4.0", "@stoplight/better-ajv-errors": "1.0.3", "@stoplight/json": "~3.21.0", "@stoplight/path": "1.3.2", "@stoplight/spectral-parsers": "^1.0.0", "@stoplight/spectral-ref-resolver": "^1.0.4", "@stoplight/spectral-runtime": "^1.1.2", "@stoplight/types": "~13.6.0", "@types/es-aggregate-error": "^1.0.2", "@types/json-schema": "^7.0.11", "ajv": "^8.18.0", "ajv-errors": "~3.0.0", "ajv-formats": "~2.1.1", "es-aggregate-error": "^1.0.7", "expr-eval-fork": "^3.0.1", "jsonpath-plus": "^10.3.0", "lodash": "^4.18.1", "lodash.topath": "^4.5.2", "minimatch": "^3.1.4", "nimma": "0.2.3", "pony-cause": "^1.1.1", "tslib": "^2.8.1" } }, "sha512-VLC8OhpO/pMJKb6IHhurxJjXO1qB56Ng1unIb8b+hNxdw0+SEcASvmR+RpjfHYX/jv/DfSaA1x8QhFBJBmqBOQ=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-formats": ["@stoplight/spectral-formats@1.8.5", "", { "dependencies": { "@scarf/scarf": "^1.4.0", "@stoplight/json": "^3.17.0", "@stoplight/spectral-core": "^1.23.0", "@types/json-schema": "^7.0.7", "tslib": "^2.8.1" } }, "sha512-xaC0rCH0p7/bzNJsz+JgLSj+Cp6uwYGWpePQxdLkF2G6a8Zyp3OyS7umkGYNiimEwKrOjvCNNTFJpeuiENZSBA=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-functions": ["@stoplight/spectral-functions@1.10.5", "", { "dependencies": { "@scarf/scarf": "^1.4.0", "@stoplight/better-ajv-errors": "1.0.3", "@stoplight/json": "^3.17.1", "@stoplight/spectral-core": "^1.23.0", "@stoplight/spectral-formats": "^1.8.1", "@stoplight/spectral-runtime": "^1.1.2", "ajv": "^8.18.0", "ajv-draft-04": "~1.0.0", "ajv-errors": "~3.0.0", "ajv-formats": "~2.1.1", "lodash": "^4.18.1", "tslib": "^2.8.1" } }, "sha512-vDCd0NJ93715bcUpZZ5vNHiyxd4cgHF6tuXsDiXOXKAByg+I1fR5/dMijEo6Ce1Lz95a+RZ22JKYhF1YuzVvuA=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-parsers": ["@stoplight/spectral-parsers@1.0.5", "", { "dependencies": { "@stoplight/json": "~3.21.0", "@stoplight/types": "^14.1.1", "@stoplight/yaml": "~4.3.0", "tslib": "^2.8.1" } }, "sha512-ANDTp2IHWGvsQDAY85/jQi9ZrF4mRrA5bciNHX+PUxPr4DwS6iv4h+FVWJMVwcEYdpyoIdyL+SRmHdJfQEPmwQ=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-ref-resolver": ["@stoplight/spectral-ref-resolver@1.0.5", "", { "dependencies": { "@stoplight/json-ref-readers": "1.2.2", "@stoplight/json-ref-resolver": "~3.1.6", "@stoplight/spectral-runtime": "^1.1.2", "dependency-graph": "0.11.0", "tslib": "^2.8.1" } }, "sha512-gj3TieX5a9zMW29z3mBlAtDOCgN3GEc1VgZnCVlr5irmR4Qi5LuECuFItAq4pTn5Zu+sW5bqutsCH7D4PkpyAA=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-runtime": ["@stoplight/spectral-runtime@1.1.6", "", { "dependencies": { "@stoplight/json": "^3.20.1", "@stoplight/path": "^1.3.2", "@stoplight/types": "^13.6.0", "lodash": "^4.18.1", "node-fetch": "^2.7.0", "tslib": "^2.8.1" } }, "sha512-Y8rEDyMN4bSMJCrDs2shdcVHYyCnH3FvXRP4dBhha4Z8iJv+JPp7KqOV/hwVB/hWFC209upiwj2oDmLfR0qCDg=="],
|
||||||
|
|
||||||
|
"@stoplight/types": ["@stoplight/types@13.20.0", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA=="],
|
||||||
|
|
||||||
|
"@stoplight/yaml": ["@stoplight/yaml@4.3.0", "", { "dependencies": { "@stoplight/ordered-object-literal": "^1.0.5", "@stoplight/types": "^14.1.1", "@stoplight/yaml-ast-parser": "0.0.50", "tslib": "^2.2.0" } }, "sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w=="],
|
||||||
|
|
||||||
|
"@stoplight/yaml-ast-parser": ["@stoplight/yaml-ast-parser@0.0.50", "", {}, "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ=="],
|
||||||
|
|
||||||
"@storybook/addon-a11y": ["@storybook/addon-a11y@10.5.7", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.5.7" } }, "sha512-I30rsNz6aA3xg3811MEry40uJDHP3l5SOkfqtmNkp7y4NdqTDdKhmbhhDAZQX1WWEk6GeMBGr3AJ3TCz7r7JmQ=="],
|
"@storybook/addon-a11y": ["@storybook/addon-a11y@10.5.7", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.5.7" } }, "sha512-I30rsNz6aA3xg3811MEry40uJDHP3l5SOkfqtmNkp7y4NdqTDdKhmbhhDAZQX1WWEk6GeMBGr3AJ3TCz7r7JmQ=="],
|
||||||
|
|
||||||
"@storybook/addon-docs": ["@storybook/addon-docs@10.5.7", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.5.7", "@storybook/icons": "^2.0.2", "@storybook/react-dom-shim": "10.5.7", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.5.7" }, "optionalPeers": ["@types/react"] }, "sha512-KNARJfjICaizinsR3INMEiipZm1ObYo+xw+E26gteu50Bcy2dIZUtk5uHY5XdtardU3AXX6yRXoBZ2HCY3lbHA=="],
|
"@storybook/addon-docs": ["@storybook/addon-docs@10.5.7", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.5.7", "@storybook/icons": "^2.0.2", "@storybook/react-dom-shim": "10.5.7", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.5.7" }, "optionalPeers": ["@types/react"] }, "sha512-KNARJfjICaizinsR3INMEiipZm1ObYo+xw+E26gteu50Bcy2dIZUtk5uHY5XdtardU3AXX6yRXoBZ2HCY3lbHA=="],
|
||||||
@@ -2907,8 +2955,6 @@
|
|||||||
|
|
||||||
"@testing-library/user-event": ["@testing-library/user-event@14.6.3", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-6dBq67jT8lE+JTE8Exm02Kt6ze43hz1jdiSpSJwtTZiT1xQQ6b7nZYTTQ9njdArdU8XklOwaDp/AbT/eYSKF4g=="],
|
"@testing-library/user-event": ["@testing-library/user-event@14.6.3", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-6dBq67jT8lE+JTE8Exm02Kt6ze43hz1jdiSpSJwtTZiT1xQQ6b7nZYTTQ9njdArdU8XklOwaDp/AbT/eYSKF4g=="],
|
||||||
|
|
||||||
"@thisbeyond/solid-dnd": ["@thisbeyond/solid-dnd@0.7.5", "", { "peerDependencies": { "solid-js": "^1.5" } }, "sha512-DfI5ff+yYGpK9M21LhYwIPlbP2msKxN2ARwuu6GF8tT1GgNVDTI8VCQvH4TJFoVApP9d44izmAcTh/iTCH2UUw=="],
|
|
||||||
|
|
||||||
"@tsconfig/bun": ["@tsconfig/bun@1.0.9", "", {}, "sha512-4M0/Ivfwcpz325z6CwSifOBZYji3DFOEpY6zEUt0+Xi2qRhzwvmqQN9XAHJh3OVvRJuAqVTLU2abdCplvp6mwQ=="],
|
"@tsconfig/bun": ["@tsconfig/bun@1.0.9", "", {}, "sha512-4M0/Ivfwcpz325z6CwSifOBZYji3DFOEpY6zEUt0+Xi2qRhzwvmqQN9XAHJh3OVvRJuAqVTLU2abdCplvp6mwQ=="],
|
||||||
|
|
||||||
"@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="],
|
"@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="],
|
||||||
@@ -3023,6 +3069,8 @@
|
|||||||
|
|
||||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||||
|
|
||||||
|
"@types/es-aggregate-error": ["@types/es-aggregate-error@1.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg=="],
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||||
|
|
||||||
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||||
@@ -3127,6 +3175,8 @@
|
|||||||
|
|
||||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||||
|
|
||||||
|
"@types/urijs": ["@types/urijs@1.19.26", "", {}, "sha512-wkXrVzX5yoqLnndOwFsieJA7oKM8cNkOKJtf/3vVGSUFkWDKZvFHpIl9Pvqb/T9UsawBBFMTTD8xu7sK5MWuvg=="],
|
||||||
|
|
||||||
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
|
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
|
||||||
|
|
||||||
"@types/which": ["@types/which@3.0.4", "", {}, "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w=="],
|
"@types/which": ["@types/which@3.0.4", "", {}, "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w=="],
|
||||||
@@ -3213,6 +3263,8 @@
|
|||||||
|
|
||||||
"@webgpu/types": ["@webgpu/types@0.1.54", "", {}, "sha512-81oaalC8LFrXjhsczomEQ0u3jG+TqE6V9QHLA8GNZq/Rnot0KDugu3LhSYSlie8tSdooAN1Hov05asrUUp9qgg=="],
|
"@webgpu/types": ["@webgpu/types@0.1.54", "", {}, "sha512-81oaalC8LFrXjhsczomEQ0u3jG+TqE6V9QHLA8GNZq/Rnot0KDugu3LhSYSlie8tSdooAN1Hov05asrUUp9qgg=="],
|
||||||
|
|
||||||
|
"@workflow/serde": ["@workflow/serde@4.1.0", "", {}, "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ=="],
|
||||||
|
|
||||||
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.13", "", {}, "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw=="],
|
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.13", "", {}, "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw=="],
|
||||||
|
|
||||||
"@yuuang/ffi-rs-android-arm64": ["@yuuang/ffi-rs-android-arm64@1.3.7", "", { "os": "android", "cpu": "arm64" }, "sha512-t6Wx3Xll6c07Nuk0k3xnZsxKFxlshm92i0U/BiTHc6kQbvu+fMJF+gKsj4yEj886jH51CM3EqZT9Xdhq9CdUVw=="],
|
"@yuuang/ffi-rs-android-arm64": ["@yuuang/ffi-rs-android-arm64@1.3.7", "", { "os": "android", "cpu": "arm64" }, "sha512-t6Wx3Xll6c07Nuk0k3xnZsxKFxlshm92i0U/BiTHc6kQbvu+fMJF+gKsj4yEj886jH51CM3EqZT9Xdhq9CdUVw=="],
|
||||||
@@ -3265,6 +3317,8 @@
|
|||||||
|
|
||||||
"ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
|
"ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
|
||||||
|
|
||||||
|
"ajv-errors": ["ajv-errors@3.0.0", "", { "peerDependencies": { "ajv": "^8.0.1" } }, "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ=="],
|
||||||
|
|
||||||
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||||
|
|
||||||
"ajv-i18n": ["ajv-i18n@4.2.0", "", { "peerDependencies": { "ajv": "^8.0.0-beta.0" } }, "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg=="],
|
"ajv-i18n": ["ajv-i18n@4.2.0", "", { "peerDependencies": { "ajv": "^8.0.0-beta.0" } }, "sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg=="],
|
||||||
@@ -3343,6 +3397,8 @@
|
|||||||
|
|
||||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||||
|
|
||||||
|
"avsc": ["avsc@5.7.9", "", {}, "sha512-yOA4wFeI7ET3v32Di/sUybQ+ttP20JHSW3mxLuNGeO0uD6PPcvLrIQXSvy/rhJOWU5JrYh7U4OHplWMmtAtjMg=="],
|
||||||
|
|
||||||
"aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
|
"aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
|
||||||
|
|
||||||
"aws4": ["aws4@1.13.2", "", {}, "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="],
|
"aws4": ["aws4@1.13.2", "", {}, "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="],
|
||||||
@@ -3409,7 +3465,7 @@
|
|||||||
|
|
||||||
"bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
|
"bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
|
||||||
|
|
||||||
"blume": ["blume@1.1.4", "", { "dependencies": { "@astrojs/check": "^0.9.0", "@astrojs/markdown-satteri": "^0.3.2", "@astrojs/mdx": "^7.0.0", "@astrojs/node": "^11.0.0", "@astrojs/react": "^6.0.0", "@astrojs/vercel": "^11.0.0", "@clack/prompts": "^1.7.0", "@iconify-json/lucide": "^1.2.115", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.3", "@modelcontextprotocol/sdk": "^1.29.0", "@orama/orama": "^3.1.18", "@pierre/diffs": "^1.2.11", "@scalar/astro": "^0.4.5", "@scalar/openapi-parser": "^0.28.8", "@scalar/openapi-types": "^0.9.1", "@shikijs/transformers": "^4.2.0", "@shikijs/twoslash": "^4.2.0", "@tailwindcss/typography": "^0.5.20", "@tailwindcss/vite": "^4", "@vercel/analytics": "^2.0.1", "ai": "^5.0.0", "astro": "^7.0.2", "babel-plugin-react-compiler": "^1.0.0", "citty": "^0.1.6", "consola": "^3.4.0", "dompurify": "^3.4.11", "epub-gen-memory": "^1.1.2", "github-slugger": "^2.0.0", "gray-matter": "^4.0.3", "jiti": "^2.4.0", "js-yaml": "^4.1.0", "katex": "^0.17.0", "marked": "^18.0.5", "mermaid": "^11.15.0", "node-html-parser": "^9.0.0", "pagefind": "^1.3.0", "pathe": "^2.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", "satteri": "^0.9.5", "shiki": "^4.2.0", "simple-icons": "^13.0.0", "tailwindcss": "^4", "takumi-js": "^2.2.1", "tinyglobby": "^0.2.10", "twoslash": "^0.3.9", "typescript": "^6.0.3", "undici": "^8.6.0", "zod": "^3.24.0" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^1.0.41", "@astrojs/cloudflare": "^14.0.0", "@astrojs/netlify": "^8.0.0", "@astrojs/svelte": "^9.0.0", "@astrojs/vue": "^7.0.0", "@mixedbread/sdk": "^0.76.0", "@notionhq/client": "^2.2.15", "@openrouter/ai-sdk-provider": "^1.5.4", "@oramacloud/client": "^2.1.0", "@sanity/client": "^6.21.0", "algoliasearch": "^5.55.0", "flexsearch": "^0.8.0", "typesense": "^3.0.0" }, "optionalPeers": ["@ai-sdk/openai-compatible", "@astrojs/cloudflare", "@astrojs/netlify", "@astrojs/svelte", "@astrojs/vue", "@mixedbread/sdk", "@notionhq/client", "@openrouter/ai-sdk-provider", "@oramacloud/client", "@sanity/client", "algoliasearch", "flexsearch", "typesense"], "bin": { "blume": "bin/blume.mjs" } }, "sha512-boCWAMfuyc2788hjGJPrmxu4mNUojG+XkkPypjq+z8KErq1yV4XrHgET8bzVJtmOdpWVGLIjfzmbLniTePBU8Q=="],
|
"blume": ["blume@1.5.1", "", { "dependencies": { "@astrojs/check": "^0.9.0", "@astrojs/markdown-satteri": "^0.3.2", "@astrojs/mdx": "^7.0.0", "@astrojs/node": "^11.0.0", "@astrojs/react": "^6.0.0", "@astrojs/vercel": "^11.0.3", "@asyncapi/converter": "^2.0.2", "@clack/prompts": "^1.7.0", "@iconify-json/lucide": "^1.2.115", "@iconify/types": "^2.0.0", "@iconify/utils": "^3.1.3", "@modelcontextprotocol/sdk": "^1.29.0", "@orama/orama": "^3.1.18", "@pierre/diffs": "^1.2.11", "@scalar/astro": "^0.4.5", "@scalar/openapi-parser": "^0.28.8", "@scalar/openapi-types": "^0.9.1", "@shikijs/transformers": "^4.2.0", "@shikijs/twoslash": "^4.2.0", "@tailwindcss/typography": "^0.5.20", "@tailwindcss/vite": "^4", "@types/mdast": "^4.0.4", "@vercel/analytics": "^2.0.1", "ai": "^7.0.42", "astro": "^7.1.0", "babel-plugin-react-compiler": "^1.0.0", "chokidar": "^5.0.0", "citty": "^0.1.6", "consola": "^3.4.0", "cross-spawn": "^7.0.6", "dompurify": "^3.4.13", "dotenv": "^17.4.2", "epub-gen-memory": "^1.1.2", "fast-xml-parser": "^5.10.1", "get-tsconfig": "^4.14.1", "github-slugger": "^2.0.0", "gray-matter": "^4.0.3", "html-escaper": "^3.0.3", "image-size": "^2.0.2", "jiti": "^2.4.0", "js-yaml": "^4.3.1", "katex": "^0.18.1", "markdown-table": "^3.0.4", "marked": "^18.0.5", "mdast-util-from-markdown": "^2.0.3", "mdast-util-gfm": "^3.1.0", "mdast-util-to-string": "^4.0.0", "medium-zoom": "^1.1.0", "mermaid": "^11.16.1", "micromark-extension-gfm": "^3.0.0", "nanotar": "^0.3.0", "node-html-parser": "^9.0.0", "openapi-sampler": "^1.7.4", "p-limit": "^7.3.1", "p-map": "^7.0.6", "p-retry": "^8.0.0", "package-manager-detector": "^1.8.0", "pagefind": "^1.3.0", "pathe": "^2.0.0", "perfect-debounce": "^2.1.0", "picomatch": "^4.0.5", "react": "^19.0.0", "react-dom": "^19.0.0", "robots-parser": "^3.0.1", "satteri": "^0.9.5", "semver": "^7.8.5", "sharp": "^0.35.3", "shiki": "^4.2.0", "simple-icons": "^13.0.0", "string-width": "^8.1.0", "tailwindcss": "^4.3.3", "takumi-js": "^2.2.1", "tinyglobby": "^0.2.10", "twoslash": "^0.3.9", "typescript": "^6.0.3", "ufo": "^1.6.4", "undici": "^8.9.0", "write-file-atomic": "^8.0.0", "zod": "^4.3.6" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^3.0.0", "@astrojs/cloudflare": "^14.0.0", "@astrojs/netlify": "^8.0.0", "@astrojs/svelte": "^9.0.0", "@astrojs/vue": "^7.0.0", "@mixedbread/sdk": "^0.76.0", "@notionhq/client": "^2.2.15", "@openrouter/ai-sdk-provider": "^3.0.0", "@oramacloud/client": "^2.1.0", "@sanity/client": "^6.21.0 || ^7.0.0", "algoliasearch": "^5.55.0", "flexsearch": "^0.8.0", "typesense": "^3.0.0" }, "optionalPeers": ["@ai-sdk/openai-compatible", "@astrojs/cloudflare", "@astrojs/netlify", "@astrojs/svelte", "@astrojs/vue", "@mixedbread/sdk", "@notionhq/client", "@openrouter/ai-sdk-provider", "@oramacloud/client", "@sanity/client", "algoliasearch", "flexsearch", "typesense"], "bin": { "blume": "bin/blume.mjs" } }, "sha512-RnzoY+OHvchUJkXjF7s45SUW5rhuL81URWATEZoEl3psWeajsUu6JRyqDuy8bpYZjoth7P6gpS4IijZspv8Yrw=="],
|
||||||
|
|
||||||
"body-parser": ["body-parser@2.3.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", "qs": "^6.15.2", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw=="],
|
"body-parser": ["body-parser@2.3.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", "qs": "^6.15.2", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw=="],
|
||||||
|
|
||||||
@@ -3737,6 +3793,8 @@
|
|||||||
|
|
||||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||||
|
|
||||||
|
"dependency-graph": ["dependency-graph@0.11.0", "", {}, "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg=="],
|
||||||
|
|
||||||
"deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="],
|
"deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="],
|
||||||
|
|
||||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||||
@@ -3791,7 +3849,7 @@
|
|||||||
|
|
||||||
"dot-prop": ["dot-prop@8.0.2", "", { "dependencies": { "type-fest": "^3.8.0" } }, "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ=="],
|
"dot-prop": ["dot-prop@8.0.2", "", { "dependencies": { "type-fest": "^3.8.0" } }, "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ=="],
|
||||||
|
|
||||||
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
|
||||||
|
|
||||||
"dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="],
|
"dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="],
|
||||||
|
|
||||||
@@ -3877,6 +3935,8 @@
|
|||||||
|
|
||||||
"es-abstract-get": ["es-abstract-get@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "es-object-atoms": "^1.1.2", "is-callable": "^1.2.7", "object-inspect": "^1.13.4" } }, "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg=="],
|
"es-abstract-get": ["es-abstract-get@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "es-object-atoms": "^1.1.2", "is-callable": "^1.2.7", "object-inspect": "^1.13.4" } }, "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg=="],
|
||||||
|
|
||||||
|
"es-aggregate-error": ["es-aggregate-error@1.0.14", "", { "dependencies": { "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "globalthis": "^1.0.4", "has-property-descriptors": "^1.0.2", "set-function-name": "^2.0.2" } }, "sha512-3YxX6rVb07B5TV11AV5wsL7nQCHXNwoHPsQC8S4AmBiqYhyNCJ5BRKXkXyDJvs8QzXN20NgRtxe3dEEQD9NLHA=="],
|
||||||
|
|
||||||
"es-array-method-boxes-properly": ["es-array-method-boxes-properly@1.0.0", "", {}, "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="],
|
"es-array-method-boxes-properly": ["es-array-method-boxes-properly@1.0.0", "", {}, "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="],
|
||||||
|
|
||||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||||
@@ -3951,6 +4011,8 @@
|
|||||||
|
|
||||||
"exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
|
"exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
|
||||||
|
|
||||||
|
"expr-eval-fork": ["expr-eval-fork@3.0.3", "", {}, "sha512-BhC+hbc5lIVjygr840n5DEkW3MQq7H9o+mc1/N7Z5uIiCFVyESLL5DIE7LNq4CYUNxy+XjA+3jRrL/h0Kt2xcg=="],
|
||||||
|
|
||||||
"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": ["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.6.2", "", { "dependencies": { "debug": "^4.4.3", "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A=="],
|
"express-rate-limit": ["express-rate-limit@8.6.2", "", { "dependencies": { "debug": "^4.4.3", "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A=="],
|
||||||
@@ -3977,6 +4039,8 @@
|
|||||||
|
|
||||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||||
|
|
||||||
|
"fast-memoize": ["fast-memoize@2.5.2", "", {}, "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw=="],
|
||||||
|
|
||||||
"fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
|
"fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
|
||||||
|
|
||||||
"fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
|
"fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
|
||||||
@@ -3987,7 +4051,7 @@
|
|||||||
|
|
||||||
"fast-xml-builder": ["fast-xml-builder@1.3.0", "", { "dependencies": { "path-expression-matcher": "^1.6.2", "xml-naming": "^0.3.0" } }, "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ=="],
|
"fast-xml-builder": ["fast-xml-builder@1.3.0", "", { "dependencies": { "path-expression-matcher": "^1.6.2", "xml-naming": "^0.3.0" } }, "sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ=="],
|
||||||
|
|
||||||
"fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="],
|
"fast-xml-parser": ["fast-xml-parser@5.10.1", "", { "dependencies": { "@nodable/entities": "^3.0.0", "fast-xml-builder": "^1.2.0", "is-unsafe": "^2.0.0", "path-expression-matcher": "^1.6.2", "strnum": "^2.4.1", "xml-naming": "^0.3.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw=="],
|
||||||
|
|
||||||
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
||||||
|
|
||||||
@@ -4027,6 +4091,8 @@
|
|||||||
|
|
||||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||||
|
|
||||||
|
"foreach": ["foreach@2.0.6", "", {}, "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg=="],
|
||||||
|
|
||||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||||
|
|
||||||
"form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="],
|
"form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="],
|
||||||
@@ -4243,6 +4309,8 @@
|
|||||||
|
|
||||||
"ignore-walk": ["ignore-walk@8.0.0", "", { "dependencies": { "minimatch": "^10.0.3" } }, "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A=="],
|
"ignore-walk": ["ignore-walk@8.0.0", "", { "dependencies": { "minimatch": "^10.0.3" } }, "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A=="],
|
||||||
|
|
||||||
|
"image-size": ["image-size@2.0.2", "", { "bin": { "image-size": "bin/image-size.js" } }, "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w=="],
|
||||||
|
|
||||||
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
|
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
|
||||||
|
|
||||||
"immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="],
|
"immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="],
|
||||||
@@ -4329,6 +4397,8 @@
|
|||||||
|
|
||||||
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
|
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
|
||||||
|
|
||||||
|
"is-network-error": ["is-network-error@1.3.2", "", {}, "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA=="],
|
||||||
|
|
||||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||||
|
|
||||||
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
|
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
|
||||||
@@ -4401,6 +4471,8 @@
|
|||||||
|
|
||||||
"jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="],
|
"jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="],
|
||||||
|
|
||||||
|
"jsep": ["jsep@1.4.0", "", {}, "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw=="],
|
||||||
|
|
||||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||||
|
|
||||||
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
||||||
@@ -4409,6 +4481,8 @@
|
|||||||
|
|
||||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="],
|
"json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="],
|
||||||
|
|
||||||
|
"json-pointer": ["json-pointer@0.6.2", "", { "dependencies": { "foreach": "^2.0.4" } }, "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw=="],
|
||||||
|
|
||||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
||||||
|
|
||||||
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
|
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
|
||||||
@@ -4431,6 +4505,8 @@
|
|||||||
|
|
||||||
"jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="],
|
"jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="],
|
||||||
|
|
||||||
|
"jsonpath-plus": ["jsonpath-plus@10.4.0", "", { "dependencies": { "@jsep-plugin/assignment": "^1.3.0", "@jsep-plugin/regex": "^1.0.4", "jsep": "^1.4.0" }, "bin": { "jsonpath": "bin/jsonpath-cli.js", "jsonpath-plus": "bin/jsonpath-cli.js" } }, "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA=="],
|
||||||
|
|
||||||
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
|
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
|
||||||
|
|
||||||
"jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
|
"jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
|
||||||
@@ -4529,6 +4605,8 @@
|
|||||||
|
|
||||||
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
|
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
|
||||||
|
|
||||||
|
"lodash.topath": ["lodash.topath@4.5.2", "", {}, "sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg=="],
|
||||||
|
|
||||||
"loglevelnext": ["loglevelnext@6.0.0", "", {}, "sha512-FDl1AI2sJGjHHG3XKJd6sG3/6ncgiGCQ0YkW46nxe7SfqQq6hujd9CvFXIXtkGBUN83KPZ2KSOJK8q5P0bSSRQ=="],
|
"loglevelnext": ["loglevelnext@6.0.0", "", {}, "sha512-FDl1AI2sJGjHHG3XKJd6sG3/6ncgiGCQ0YkW46nxe7SfqQq6hujd9CvFXIXtkGBUN83KPZ2KSOJK8q5P0bSSRQ=="],
|
||||||
|
|
||||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||||
@@ -4613,6 +4691,8 @@
|
|||||||
|
|
||||||
"media-typer": ["media-typer@1.1.1", "", {}, "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ=="],
|
"media-typer": ["media-typer@1.1.1", "", {}, "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ=="],
|
||||||
|
|
||||||
|
"medium-zoom": ["medium-zoom@1.1.0", "", {}, "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ=="],
|
||||||
|
|
||||||
"merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="],
|
"merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="],
|
||||||
|
|
||||||
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||||
@@ -4767,12 +4847,16 @@
|
|||||||
|
|
||||||
"nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
|
"nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
|
||||||
|
|
||||||
|
"nanotar": ["nanotar@0.3.0", "", {}, "sha512-Kv2JYYiCzt16Kt5QwAc9BFG89xfPNBx+oQL4GQXD9nLqPkZBiNaqaCWtwnbk/q7UVsTYevvM1b0UF8zmEI4pCg=="],
|
||||||
|
|
||||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||||
|
|
||||||
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
|
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
|
||||||
|
|
||||||
"nf3": ["nf3@0.1.12", "", {}, "sha512-qbMXT7RTGh74MYWPeqTIED8nDW70NXOULVHpdWcdZ7IVHVnAsMV9fNugSNnvooipDc1FMOzpis7T9nXJEbJhvQ=="],
|
"nf3": ["nf3@0.1.12", "", {}, "sha512-qbMXT7RTGh74MYWPeqTIED8nDW70NXOULVHpdWcdZ7IVHVnAsMV9fNugSNnvooipDc1FMOzpis7T9nXJEbJhvQ=="],
|
||||||
|
|
||||||
|
"nimma": ["nimma@0.2.3", "", { "dependencies": { "@jsep-plugin/regex": "^1.0.1", "@jsep-plugin/ternary": "^1.0.2", "astring": "^1.8.1", "jsep": "^1.2.0" }, "optionalDependencies": { "jsonpath-plus": "^6.0.1 || ^10.1.0", "lodash.topath": "^4.5.2" } }, "sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA=="],
|
||||||
|
|
||||||
"nitro": ["nitro@3.0.1-alpha.1", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.1", "db0": "^0.3.4", "h3": "2.0.1-rc.5", "jiti": "^2.6.1", "nf3": "^0.1.10", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "oxc-minify": "^0.96.0", "oxc-transform": "^0.96.0", "srvx": "^0.9.5", "undici": "^7.16.0", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.4" }, "peerDependencies": { "rolldown": "*", "rollup": "^4", "vite": "^7", "xml2js": "^0.6.2" }, "optionalPeers": ["rolldown", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-U4AxIsXxdkxzkFrK0XAw0e5Qbojk8jQ50MjjRBtBakC4HurTtQoiZvF+lSe382jhuQZCfAyywGWOFa9QzXLFaw=="],
|
"nitro": ["nitro@3.0.1-alpha.1", "", { "dependencies": { "consola": "^3.4.2", "crossws": "^0.4.1", "db0": "^0.3.4", "h3": "2.0.1-rc.5", "jiti": "^2.6.1", "nf3": "^0.1.10", "ofetch": "^2.0.0-alpha.3", "ohash": "^2.0.11", "oxc-minify": "^0.96.0", "oxc-transform": "^0.96.0", "srvx": "^0.9.5", "undici": "^7.16.0", "unenv": "^2.0.0-rc.24", "unstorage": "^2.0.0-alpha.4" }, "peerDependencies": { "rolldown": "*", "rollup": "^4", "vite": "^7", "xml2js": "^0.6.2" }, "optionalPeers": ["rolldown", "rollup", "vite", "xml2js"], "bin": { "nitro": "dist/cli/index.mjs" } }, "sha512-U4AxIsXxdkxzkFrK0XAw0e5Qbojk8jQ50MjjRBtBakC4HurTtQoiZvF+lSe382jhuQZCfAyywGWOFa9QzXLFaw=="],
|
||||||
|
|
||||||
"nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="],
|
"nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="],
|
||||||
@@ -4861,6 +4945,8 @@
|
|||||||
|
|
||||||
"openai": ["openai@6.49.0", "", { "peerDependencies": { "@aws-sdk/credential-provider-node": ">=3.972.0 <4", "@smithy/hash-node": ">=4.3.0 <5", "@smithy/signature-v4": ">=5.4.0 <6", "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@aws-sdk/credential-provider-node", "@smithy/hash-node", "@smithy/signature-v4", "ws", "zod"] }, "sha512-aYCc0C6L864eR6WSYIwQGyXriw/nIyZx0ObvhzOEVuk0zoBDpynjSbrionWI7q65B5H8jJX0DXR9snEzM6bfPg=="],
|
"openai": ["openai@6.49.0", "", { "peerDependencies": { "@aws-sdk/credential-provider-node": ">=3.972.0 <4", "@smithy/hash-node": ">=4.3.0 <5", "@smithy/signature-v4": ">=5.4.0 <6", "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@aws-sdk/credential-provider-node", "@smithy/hash-node", "@smithy/signature-v4", "ws", "zod"] }, "sha512-aYCc0C6L864eR6WSYIwQGyXriw/nIyZx0ObvhzOEVuk0zoBDpynjSbrionWI7q65B5H8jJX0DXR9snEzM6bfPg=="],
|
||||||
|
|
||||||
|
"openapi-sampler": ["openapi-sampler@1.7.4", "", { "dependencies": { "@types/json-schema": "^7.0.7", "fast-xml-parser": "^5.5.1", "json-pointer": "0.6.2" } }, "sha512-CKS/rd5ucPCuEDbJnjGDXZTsuGWcmv53aCmQx7soZlPEONUGN4af0/dY5+THRFZraSEjeA78nlfzdFswC/N5SA=="],
|
||||||
|
|
||||||
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
||||||
|
|
||||||
"openid-client": ["openid-client@5.6.4", "", { "dependencies": { "jose": "^4.15.4", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA=="],
|
"openid-client": ["openid-client@5.6.4", "", { "dependencies": { "jose": "^4.15.4", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA=="],
|
||||||
@@ -4899,7 +4985,7 @@
|
|||||||
|
|
||||||
"p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="],
|
"p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="],
|
||||||
|
|
||||||
"p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
|
"p-retry": ["p-retry@8.0.0", "", { "dependencies": { "is-network-error": "^1.3.0" } }, "sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A=="],
|
||||||
|
|
||||||
"p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="],
|
"p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="],
|
||||||
|
|
||||||
@@ -4933,6 +5019,8 @@
|
|||||||
|
|
||||||
"pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="],
|
"pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="],
|
||||||
|
|
||||||
|
"path": ["path@0.12.7", "", { "dependencies": { "process": "^0.11.1", "util": "^0.10.3" } }, "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q=="],
|
||||||
|
|
||||||
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||||
|
|
||||||
"path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
|
"path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
|
||||||
@@ -4965,6 +5053,8 @@
|
|||||||
|
|
||||||
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
|
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
|
||||||
|
|
||||||
|
"perfect-debounce": ["perfect-debounce@2.1.0", "", {}, "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g=="],
|
||||||
|
|
||||||
"piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="],
|
"piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="],
|
||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
@@ -4993,6 +5083,8 @@
|
|||||||
|
|
||||||
"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=="],
|
"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=="],
|
||||||
|
|
||||||
|
"pony-cause": ["pony-cause@1.1.1", "", {}, "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g=="],
|
||||||
|
|
||||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
|
"postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
|
||||||
@@ -5221,6 +5313,8 @@
|
|||||||
|
|
||||||
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
|
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
|
||||||
|
|
||||||
|
"robots-parser": ["robots-parser@3.0.1", "", {}, "sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ=="],
|
||||||
|
|
||||||
"robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
|
"robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="],
|
||||||
|
|
||||||
"rolldown": ["rolldown@1.2.3", "", { "dependencies": { "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.3", "@rolldown/binding-darwin-arm64": "1.2.3", "@rolldown/binding-darwin-x64": "1.2.3", "@rolldown/binding-freebsd-x64": "1.2.3", "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", "@rolldown/binding-linux-arm64-gnu": "1.2.3", "@rolldown/binding-linux-arm64-musl": "1.2.3", "@rolldown/binding-linux-ppc64-gnu": "1.2.3", "@rolldown/binding-linux-s390x-gnu": "1.2.3", "@rolldown/binding-linux-x64-gnu": "1.2.3", "@rolldown/binding-linux-x64-musl": "1.2.3", "@rolldown/binding-openharmony-arm64": "1.2.3", "@rolldown/binding-win32-arm64-msvc": "1.2.3", "@rolldown/binding-win32-x64-msvc": "1.2.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A=="],
|
"rolldown": ["rolldown@1.2.3", "", { "dependencies": { "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.3", "@rolldown/binding-darwin-arm64": "1.2.3", "@rolldown/binding-darwin-x64": "1.2.3", "@rolldown/binding-freebsd-x64": "1.2.3", "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", "@rolldown/binding-linux-arm64-gnu": "1.2.3", "@rolldown/binding-linux-arm64-musl": "1.2.3", "@rolldown/binding-linux-ppc64-gnu": "1.2.3", "@rolldown/binding-linux-s390x-gnu": "1.2.3", "@rolldown/binding-linux-x64-gnu": "1.2.3", "@rolldown/binding-linux-x64-musl": "1.2.3", "@rolldown/binding-openharmony-arm64": "1.2.3", "@rolldown/binding-win32-arm64-msvc": "1.2.3", "@rolldown/binding-win32-x64-msvc": "1.2.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A=="],
|
||||||
@@ -5249,6 +5343,8 @@
|
|||||||
|
|
||||||
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
|
||||||
|
|
||||||
|
"safe-stable-stringify": ["safe-stable-stringify@1.1.1", "", {}, "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw=="],
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
"sanitize-filename": ["sanitize-filename@1.6.4", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg=="],
|
"sanitize-filename": ["sanitize-filename@1.6.4", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg=="],
|
||||||
@@ -5459,7 +5555,7 @@
|
|||||||
|
|
||||||
"stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="],
|
"stripe": ["stripe@18.0.0", "", { "dependencies": { "@types/node": ">=8.1.0", "qs": "^6.11.0" } }, "sha512-3Fs33IzKUby//9kCkCa1uRpinAoTvj6rJgQ2jrBEysoxEvfsclvXdna1amyEYbA2EKkjynuB4+L/kleCCaWTpA=="],
|
||||||
|
|
||||||
"strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
|
"strnum": ["strnum@2.4.1", "", { "dependencies": { "anynum": "^1.0.1" } }, "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg=="],
|
||||||
|
|
||||||
"stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],
|
"stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],
|
||||||
|
|
||||||
@@ -5681,6 +5777,8 @@
|
|||||||
|
|
||||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||||
|
|
||||||
|
"urijs": ["urijs@1.19.11", "", {}, "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ=="],
|
||||||
|
|
||||||
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
|
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
|
||||||
|
|
||||||
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
|
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
|
||||||
@@ -5689,8 +5787,12 @@
|
|||||||
|
|
||||||
"utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="],
|
"utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="],
|
||||||
|
|
||||||
|
"util": ["util@0.10.4", "", { "dependencies": { "inherits": "2.0.3" } }, "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A=="],
|
||||||
|
|
||||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
|
||||||
|
"utility-types": ["utility-types@3.11.0", "", {}, "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw=="],
|
||||||
|
|
||||||
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
|
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
|
||||||
|
|
||||||
"uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="],
|
"uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="],
|
||||||
@@ -5807,7 +5909,7 @@
|
|||||||
|
|
||||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||||
|
|
||||||
"write-file-atomic": ["write-file-atomic@7.0.1", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg=="],
|
"write-file-atomic": ["write-file-atomic@8.0.0", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-dYwyZredl67GyLLIHJnRM3h2PcOmN5SkcgC7eM5DPDEOEl6dLFqVrMg3F1Ea32usj4VSVZtd2H4MtKTNOf6nPg=="],
|
||||||
|
|
||||||
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
||||||
|
|
||||||
@@ -5999,6 +6101,12 @@
|
|||||||
|
|
||||||
"@astrojs/vercel/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
"@astrojs/vercel/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
|
||||||
|
|
||||||
|
"@asyncapi/parser/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
|
||||||
|
|
||||||
|
"@asyncapi/parser/js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="],
|
||||||
|
|
||||||
|
"@asyncapi/parser/node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="],
|
||||||
|
|
||||||
"@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
|
"@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
|
||||||
|
|
||||||
"@aws-crypto/crc32c/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
|
"@aws-crypto/crc32c/@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
|
||||||
@@ -6117,8 +6225,6 @@
|
|||||||
|
|
||||||
"@azure/core-http/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
"@azure/core-http/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
||||||
|
|
||||||
"@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.10.1", "", { "dependencies": { "@nodable/entities": "^3.0.0", "fast-xml-builder": "^1.2.0", "is-unsafe": "^2.0.0", "path-expression-matcher": "^1.6.2", "strnum": "^2.4.1", "xml-naming": "^0.3.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw=="],
|
|
||||||
|
|
||||||
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
@@ -6325,6 +6431,8 @@
|
|||||||
|
|
||||||
"@scalar/types/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
"@scalar/types/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||||
|
|
||||||
|
"@sentry/bundler-plugin-core/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||||
|
|
||||||
"@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
"@sentry/bundler-plugin-core/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||||
|
|
||||||
"@sentry/bundler-plugin-core/magic-string": ["magic-string@0.30.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ=="],
|
"@sentry/bundler-plugin-core/magic-string": ["magic-string@0.30.8", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ=="],
|
||||||
@@ -6365,6 +6473,8 @@
|
|||||||
|
|
||||||
"@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
|
"@slack/web-api/p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
|
||||||
|
|
||||||
|
"@slack/web-api/p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
|
||||||
|
|
||||||
"@solidjs/start/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
"@solidjs/start/es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
|
||||||
|
|
||||||
"@solidjs/start/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
"@solidjs/start/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
||||||
@@ -6373,6 +6483,26 @@
|
|||||||
|
|
||||||
"@solidjs/start/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="],
|
"@solidjs/start/vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="],
|
||||||
|
|
||||||
|
"@stoplight/better-ajv-errors/leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
||||||
|
|
||||||
|
"@stoplight/json/jsonc-parser": ["jsonc-parser@2.2.1", "", {}, "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w=="],
|
||||||
|
|
||||||
|
"@stoplight/json-ref-readers/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
|
||||||
|
|
||||||
|
"@stoplight/json-ref-resolver/immer": ["immer@9.0.21", "", {}, "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-core/@stoplight/types": ["@stoplight/types@13.6.0", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-core/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-core/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-functions/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-parsers/@stoplight/types": ["@stoplight/types@14.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g=="],
|
||||||
|
|
||||||
|
"@stoplight/yaml/@stoplight/types": ["@stoplight/types@14.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g=="],
|
||||||
|
|
||||||
"@storybook/addon-docs/react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
|
"@storybook/addon-docs/react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
|
||||||
|
|
||||||
"@storybook/addon-docs/react-dom": ["react-dom@19.2.8", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.8" } }, "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ=="],
|
"@storybook/addon-docs/react-dom": ["react-dom@19.2.8", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.8" } }, "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ=="],
|
||||||
@@ -6435,6 +6565,8 @@
|
|||||||
|
|
||||||
"app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="],
|
"app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="],
|
||||||
|
|
||||||
|
"app-builder-lib/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||||
|
|
||||||
"app-builder-lib/hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="],
|
"app-builder-lib/hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="],
|
||||||
|
|
||||||
"app-builder-lib/js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="],
|
"app-builder-lib/js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="],
|
||||||
@@ -6473,6 +6605,8 @@
|
|||||||
|
|
||||||
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||||
|
|
||||||
|
"bin-links/write-file-atomic": ["write-file-atomic@7.0.1", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg=="],
|
||||||
|
|
||||||
"blume/@astrojs/mdx": ["@astrojs/mdx@7.0.5", "", { "dependencies": { "@astrojs/internal-helpers": "0.10.2", "@astrojs/markdown-remark": "7.2.2", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.16.0", "es-module-lexer": "^2.0.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "piccolore": "^0.1.3", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3" }, "peerDependencies": { "@astrojs/markdown-satteri": "^0.3.1", "astro": "^7.0.0" }, "optionalPeers": ["@astrojs/markdown-satteri"] }, "sha512-wEM/HH1RiEntyPVagdiF+yArzfcYLKBB0C1RZspVidKZ97rRMbaqP1Nbl/GR0sJs8zwaceqxRymw8aOKKJRdYw=="],
|
"blume/@astrojs/mdx": ["@astrojs/mdx@7.0.5", "", { "dependencies": { "@astrojs/internal-helpers": "0.10.2", "@astrojs/markdown-remark": "7.2.2", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.16.0", "es-module-lexer": "^2.0.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "piccolore": "^0.1.3", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3" }, "peerDependencies": { "@astrojs/markdown-satteri": "^0.3.1", "astro": "^7.0.0" }, "optionalPeers": ["@astrojs/markdown-satteri"] }, "sha512-wEM/HH1RiEntyPVagdiF+yArzfcYLKBB0C1RZspVidKZ97rRMbaqP1Nbl/GR0sJs8zwaceqxRymw8aOKKJRdYw=="],
|
||||||
|
|
||||||
"blume/@clack/prompts": ["@clack/prompts@1.7.0", "", { "dependencies": { "@clack/core": "1.4.3", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A=="],
|
"blume/@clack/prompts": ["@clack/prompts@1.7.0", "", { "dependencies": { "@clack/core": "1.4.3", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A=="],
|
||||||
@@ -6481,23 +6615,35 @@
|
|||||||
|
|
||||||
"blume/@shikijs/transformers": ["@shikijs/transformers@4.4.2", "", { "dependencies": { "@shikijs/core": "4.4.2", "@shikijs/types": "4.4.2" } }, "sha512-d81PJ9KkR1tVP95FH/9296HTtDo0mh76wv10u9T1YmsZq/UcXgt0OLdBszfUQ1i+umkRMCjDnFbFZU7/tCODTQ=="],
|
"blume/@shikijs/transformers": ["@shikijs/transformers@4.4.2", "", { "dependencies": { "@shikijs/core": "4.4.2", "@shikijs/types": "4.4.2" } }, "sha512-d81PJ9KkR1tVP95FH/9296HTtDo0mh76wv10u9T1YmsZq/UcXgt0OLdBszfUQ1i+umkRMCjDnFbFZU7/tCODTQ=="],
|
||||||
|
|
||||||
|
"blume/ai": ["ai@7.0.64", "", { "dependencies": { "@ai-sdk/gateway": "4.0.51", "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-29Ufm56e53pRzIPueWzZiwJsgxmZStaBFpwEkVM89sqyIIax/pzDm+ez/ksJ/CcY9UCuYzvCQpE2zkFHICvS3w=="],
|
||||||
|
|
||||||
"blume/astro": ["astro@7.1.3", "", { "dependencies": { "@astrojs/compiler-rs": "^0.3.1", "@astrojs/internal-helpers": "0.10.1", "@astrojs/markdown-satteri": "0.3.4", "@astrojs/telemetry": "3.3.3", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "am-i-vibing": "^0.4.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^2.0.1", "devalue": "^5.8.1", "diff": "^8.0.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.28.0", "flattie": "^1.1.1", "fontace": "~0.4.1", "get-tsconfig": "5.0.0-beta.4", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "jsonc-parser": "^3.3.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^1.0.1", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.4", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unstorage": "^1.17.5", "vite": "^8.0.13", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0 || ^0.35.0" }, "peerDependencies": { "@astrojs/markdown-remark": "7.2.1" }, "optionalPeers": ["@astrojs/markdown-remark"], "bin": { "astro": "./bin/astro.mjs" } }, "sha512-4dhPyAAXthf3xLEYnG8SeL7yr/nTPPABfY7e9YF0yuO+vK9Xp+8Q5j4xzsmL3GueukQv4oNwGNTBepLOiDGeJA=="],
|
"blume/astro": ["astro@7.1.3", "", { "dependencies": { "@astrojs/compiler-rs": "^0.3.1", "@astrojs/internal-helpers": "0.10.1", "@astrojs/markdown-satteri": "0.3.4", "@astrojs/telemetry": "3.3.3", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "am-i-vibing": "^0.4.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^2.0.1", "devalue": "^5.8.1", "diff": "^8.0.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.28.0", "flattie": "^1.1.1", "fontace": "~0.4.1", "get-tsconfig": "5.0.0-beta.4", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "jsonc-parser": "^3.3.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^1.0.1", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.4", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unstorage": "^1.17.5", "vite": "^8.0.13", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0 || ^0.35.0" }, "peerDependencies": { "@astrojs/markdown-remark": "7.2.1" }, "optionalPeers": ["@astrojs/markdown-remark"], "bin": { "astro": "./bin/astro.mjs" } }, "sha512-4dhPyAAXthf3xLEYnG8SeL7yr/nTPPABfY7e9YF0yuO+vK9Xp+8Q5j4xzsmL3GueukQv4oNwGNTBepLOiDGeJA=="],
|
||||||
|
|
||||||
|
"blume/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||||
|
|
||||||
"blume/dompurify": ["dompurify@3.4.13", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ=="],
|
"blume/dompurify": ["dompurify@3.4.13", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ=="],
|
||||||
|
|
||||||
"blume/js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="],
|
"blume/js-yaml": ["js-yaml@4.3.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ=="],
|
||||||
|
|
||||||
"blume/katex": ["katex@0.17.0", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Vdw0ATsQ9V+LuegM/BTwQqV/6cTl5lbGcIrU+BCgLxyf6bo38ybOr372tuSIxir3CN720flu1meYR6XzNMwQnw=="],
|
"blume/katex": ["katex@0.18.4", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-IMPntbRLOU+eu88XDiFKqQ8Akhr9Tv7jDMXqPhjG9SI1JMA4DIgXk4x9k4skJz2NZJXBRbC+2pYBLj9olqcZow=="],
|
||||||
|
|
||||||
"blume/node-html-parser": ["node-html-parser@9.0.1", "", { "dependencies": { "css-select": "^5.1.0", "entities": "^8.0.0" } }, "sha512-QrdiYYm1NnLRXsMXThUgVcF/syWfWIgHFmy8hylWMGFbHFtnRuXLBxxGQvIm3xaIJMUDJ0ayOmT/FGJYT3pZIw=="],
|
"blume/node-html-parser": ["node-html-parser@9.0.1", "", { "dependencies": { "css-select": "^5.1.0", "entities": "^8.0.0" } }, "sha512-QrdiYYm1NnLRXsMXThUgVcF/syWfWIgHFmy8hylWMGFbHFtnRuXLBxxGQvIm3xaIJMUDJ0ayOmT/FGJYT3pZIw=="],
|
||||||
|
|
||||||
|
"blume/p-limit": ["p-limit@7.3.1", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q=="],
|
||||||
|
|
||||||
"blume/react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
|
"blume/react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
|
||||||
|
|
||||||
"blume/react-dom": ["react-dom@19.2.8", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.8" } }, "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ=="],
|
"blume/react-dom": ["react-dom@19.2.8", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.8" } }, "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ=="],
|
||||||
|
|
||||||
|
"blume/sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="],
|
||||||
|
|
||||||
|
"blume/string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="],
|
||||||
|
|
||||||
|
"blume/tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="],
|
||||||
|
|
||||||
"blume/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
"blume/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||||
|
|
||||||
"blume/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"blume/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||||
|
|
||||||
"body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
"body-parser/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
||||||
|
|
||||||
@@ -6545,6 +6691,8 @@
|
|||||||
|
|
||||||
"dot-prop/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
|
"dot-prop/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
|
||||||
|
|
||||||
|
"dotenv-expand/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||||
|
|
||||||
"duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
"duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||||
|
|
||||||
"editorconfig/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
|
"editorconfig/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
|
||||||
@@ -6685,8 +6833,6 @@
|
|||||||
|
|
||||||
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||||
|
|
||||||
"p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
|
|
||||||
|
|
||||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||||
|
|
||||||
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||||
@@ -6785,6 +6931,8 @@
|
|||||||
|
|
||||||
"unzipper/fs-extra": ["fs-extra@11.3.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g=="],
|
"unzipper/fs-extra": ["fs-extra@11.3.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g=="],
|
||||||
|
|
||||||
|
"util/inherits": ["inherits@2.0.3", "", {}, "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="],
|
||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.42" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-oMh5/bhu2ibsXqzHeLbJ+9Y7UQdOSrfOLPI+UA1vEcqXGYVBQfWxLe5jBUpsaL8GBBj/nmSeYGX6QPan8RfHiQ=="],
|
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.42" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-oMh5/bhu2ibsXqzHeLbJ+9Y7UQdOSrfOLPI+UA1vEcqXGYVBQfWxLe5jBUpsaL8GBBj/nmSeYGX6QPan8RfHiQ=="],
|
||||||
|
|
||||||
"venice-ai-sdk-provider/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
|
"venice-ai-sdk-provider/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
|
||||||
@@ -7019,6 +7167,8 @@
|
|||||||
|
|
||||||
"@astrojs/vercel/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
"@astrojs/vercel/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
|
||||||
|
|
||||||
|
"@asyncapi/parser/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||||
|
|
||||||
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||||
|
|
||||||
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||||
@@ -7033,6 +7183,8 @@
|
|||||||
|
|
||||||
"@aws-sdk/client-lambda/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
|
"@aws-sdk/client-lambda/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-sts/@aws-sdk/core/fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.775.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/types": "3.775.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw=="],
|
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.775.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/types": "3.775.0", "@smithy/property-provider": "^4.0.2", "@smithy/types": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6ESVxwCbGm7WZ17kY1fjmxQud43vzJFoLd4bmlR+idQSWdqlzGDYdcfzpjDKTcivdtNrVYmFvcH1JBUwCRAZhw=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.775.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/types": "3.775.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/util-stream": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww=="],
|
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.775.0", "", { "dependencies": { "@aws-sdk/core": "3.775.0", "@aws-sdk/types": "3.775.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/property-provider": "^4.0.2", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/util-stream": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-PjDQeDH/J1S0yWV32wCj2k5liRo0ssXMseCBEkCsD3SqsU8o5cU82b0hMX4sAib/RkglCSZqGO0xMiN0/7ndww=="],
|
||||||
@@ -7091,10 +7243,6 @@
|
|||||||
|
|
||||||
"@aws-sdk/token-providers/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
|
"@aws-sdk/token-providers/@aws-sdk/core/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
|
||||||
|
|
||||||
"@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.4.1", "", { "dependencies": { "anynum": "^1.0.1" } }, "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg=="],
|
|
||||||
|
|
||||||
"@azure/core-xml/fast-xml-parser/strnum": ["strnum@2.4.1", "", { "dependencies": { "anynum": "^1.0.1" } }, "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg=="],
|
|
||||||
|
|
||||||
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||||
|
|
||||||
"@bruits/satteri-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
|
"@bruits/satteri-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
|
||||||
@@ -7413,6 +7561,8 @@
|
|||||||
|
|
||||||
"@slack/web-api/p-queue/p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
|
"@slack/web-api/p-queue/p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
|
||||||
|
|
||||||
|
"@slack/web-api/p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
|
||||||
|
|
||||||
"@solidjs/start/shiki/@shikijs/core": ["@shikijs/core@1.29.2", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="],
|
"@solidjs/start/shiki/@shikijs/core": ["@shikijs/core@1.29.2", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="],
|
||||||
|
|
||||||
"@solidjs/start/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="],
|
"@solidjs/start/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="],
|
||||||
@@ -7425,6 +7575,8 @@
|
|||||||
|
|
||||||
"@solidjs/start/shiki/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
"@solidjs/start/shiki/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-core/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="],
|
||||||
|
|
||||||
"@storybook/addon-docs/react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
"@storybook/addon-docs/react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||||
|
|
||||||
"@storybook/csf-plugin/unplugin/acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
|
"@storybook/csf-plugin/unplugin/acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
|
||||||
@@ -7515,6 +7667,12 @@
|
|||||||
|
|
||||||
"blume/@shikijs/transformers/@shikijs/types": ["@shikijs/types@4.4.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-PFYitV4vpDr/iPCIhnHp+Q4ftic5N5VeNJ3KQ1O8gn3h2ar8qgwMAXF7tq4m1CWaMS60fV4VqF6vfnWH4F7vqQ=="],
|
"blume/@shikijs/transformers/@shikijs/types": ["@shikijs/types@4.4.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-PFYitV4vpDr/iPCIhnHp+Q4ftic5N5VeNJ3KQ1O8gn3h2ar8qgwMAXF7tq4m1CWaMS60fV4VqF6vfnWH4F7vqQ=="],
|
||||||
|
|
||||||
|
"blume/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@4.0.51", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tQxWGUbg/O3A5EgekJo/C2byLVQ1r+vL6QdEVWmxUSnPhdCupcOeDbQO1tv9Um40VrdwYuSWlYzcbWvtlG5bOA=="],
|
||||||
|
|
||||||
|
"blume/ai/@ai-sdk/provider": ["@ai-sdk/provider@4.0.7", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q=="],
|
||||||
|
|
||||||
|
"blume/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@5.0.27", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", "undici": "^7.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EzAn4pdgG5g0xXtH6lE2zyNmfjDQIDjATkfqzuidEI35g++hh4+07vnjzkT/RmGmIClPZiRj/Q2GMPV2V7mkHw=="],
|
||||||
|
|
||||||
"blume/astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.10.1", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "js-yaml": "^4.1.1", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "unified": "^11.0.5" } }, "sha512-5phcroT/vmOOrYuuAxtkbPixy5hePtlz9i8K4OeDv3dNK6/UQRuXPOSRTxIOBbUY5Sonw2UaxjbuVc43Mcir6Q=="],
|
"blume/astro/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.10.1", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "js-yaml": "^4.1.1", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "unified": "^11.0.5" } }, "sha512-5phcroT/vmOOrYuuAxtkbPixy5hePtlz9i8K4OeDv3dNK6/UQRuXPOSRTxIOBbUY5Sonw2UaxjbuVc43Mcir6Q=="],
|
||||||
|
|
||||||
"blume/astro/@astrojs/telemetry": ["@astrojs/telemetry@3.3.3", "", { "dependencies": { "ci-info": "^4.4.0", "dset": "^3.1.4", "is-docker": "^4.0.0", "package-manager-detector": "^1.6.0" } }, "sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ=="],
|
"blume/astro/@astrojs/telemetry": ["@astrojs/telemetry@3.3.3", "", { "dependencies": { "ci-info": "^4.4.0", "dset": "^3.1.4", "is-docker": "^4.0.0", "package-manager-detector": "^1.6.0" } }, "sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ=="],
|
||||||
@@ -7535,12 +7693,8 @@
|
|||||||
|
|
||||||
"blume/astro/neotraverse": ["neotraverse@1.0.1", "", {}, "sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w=="],
|
"blume/astro/neotraverse": ["neotraverse@1.0.1", "", {}, "sha512-WmmLty1YWwJl9yZi77v2dVIV6X2kuYV8YYBI/G3LWGKdGHmHUvL1z7FW0iDvEvGAwNEoc5x1tOOOyDnf5jJw/w=="],
|
||||||
|
|
||||||
"blume/astro/p-limit": ["p-limit@7.3.1", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q=="],
|
|
||||||
|
|
||||||
"blume/astro/p-queue": ["p-queue@9.3.3", "", { "dependencies": { "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" } }, "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA=="],
|
"blume/astro/p-queue": ["p-queue@9.3.3", "", { "dependencies": { "eventemitter3": "^5.0.4", "p-timeout": "^7.0.0" } }, "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA=="],
|
||||||
|
|
||||||
"blume/astro/sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="],
|
|
||||||
|
|
||||||
"blume/astro/unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
|
"blume/astro/unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
|
||||||
|
|
||||||
"blume/astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
"blume/astro/unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
|
||||||
@@ -7549,7 +7703,7 @@
|
|||||||
|
|
||||||
"blume/astro/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
"blume/astro/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
||||||
|
|
||||||
"blume/astro/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
"blume/chokidar/readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="],
|
||||||
|
|
||||||
"blume/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
"blume/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||||
|
|
||||||
@@ -7559,6 +7713,42 @@
|
|||||||
|
|
||||||
"blume/react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
"blume/react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.2" }, "os": "darwin", "cpu": "arm64" }, "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.2" }, "os": "darwin", "cpu": "x64" }, "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.2", "", { "os": "linux", "cpu": "arm" }, "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.2" }, "os": "linux", "cpu": "arm" }, "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.2" }, "os": "linux", "cpu": "s390x" }, "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw=="],
|
||||||
|
|
||||||
|
"blume/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.3", "", { "os": "win32", "cpu": "x64" }, "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA=="],
|
||||||
|
|
||||||
"builder-util/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
"builder-util/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||||
|
|
||||||
"cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
@@ -8115,6 +8305,8 @@
|
|||||||
|
|
||||||
"@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=="],
|
"@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=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-sts/@aws-sdk/core/fast-xml-parser/strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
|
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-QOYC8q7luzHFXrP0xYAqBctoPkynjfV0r9dqntFu4/IWMTyC1vlo1UTxFAjIPyclYw92XJyEkVCVg9v/nQnsUA=="],
|
||||||
|
|
||||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-5GlJBejo8wqMpSSEKb45WE82YxI2k73YuebjLH/eWDNQeE6VI5Bh9lA1YQ7xNkLLH8hIsb0pSfKVuwh0VEzVrg=="],
|
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.782.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.775.0", "@aws-sdk/middleware-host-header": "3.775.0", "@aws-sdk/middleware-logger": "3.775.0", "@aws-sdk/middleware-recursion-detection": "3.775.0", "@aws-sdk/middleware-user-agent": "3.782.0", "@aws-sdk/region-config-resolver": "3.775.0", "@aws-sdk/types": "3.775.0", "@aws-sdk/util-endpoints": "3.782.0", "@aws-sdk/util-user-agent-browser": "3.775.0", "@aws-sdk/util-user-agent-node": "3.782.0", "@smithy/config-resolver": "^4.1.0", "@smithy/core": "^3.2.0", "@smithy/fetch-http-handler": "^5.0.2", "@smithy/hash-node": "^4.0.2", "@smithy/invalid-dependency": "^4.0.2", "@smithy/middleware-content-length": "^4.0.2", "@smithy/middleware-endpoint": "^4.1.0", "@smithy/middleware-retry": "^4.1.0", "@smithy/middleware-serde": "^4.0.3", "@smithy/middleware-stack": "^4.0.2", "@smithy/node-config-provider": "^4.0.2", "@smithy/node-http-handler": "^4.0.4", "@smithy/protocol-http": "^5.1.0", "@smithy/smithy-client": "^4.2.0", "@smithy/types": "^4.2.0", "@smithy/url-parser": "^4.0.2", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.8", "@smithy/util-defaults-mode-node": "^4.0.8", "@smithy/util-endpoints": "^3.0.2", "@smithy/util-middleware": "^4.0.2", "@smithy/util-retry": "^4.0.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-5GlJBejo8wqMpSSEKb45WE82YxI2k73YuebjLH/eWDNQeE6VI5Bh9lA1YQ7xNkLLH8hIsb0pSfKVuwh0VEzVrg=="],
|
||||||
@@ -8479,6 +8671,8 @@
|
|||||||
|
|
||||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
||||||
|
|
||||||
|
"@stoplight/spectral-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
"app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
|
"app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
|
||||||
@@ -8505,6 +8699,8 @@
|
|||||||
|
|
||||||
"blume/@shikijs/transformers/@shikijs/core/@shikijs/primitive": ["@shikijs/primitive@4.4.2", "", { "dependencies": { "@shikijs/types": "4.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-l6fQQKsOMlz72n38fztmSgZ76MO6KSWuw8o+GJ+FhmqrpC9pIOJNQNXGgbb5yX2AwpzlEHwsaLPnk/8o4Fm+rA=="],
|
"blume/@shikijs/transformers/@shikijs/core/@shikijs/primitive": ["@shikijs/primitive@4.4.2", "", { "dependencies": { "@shikijs/types": "4.4.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-l6fQQKsOMlz72n38fztmSgZ76MO6KSWuw8o+GJ+FhmqrpC9pIOJNQNXGgbb5yX2AwpzlEHwsaLPnk/8o4Fm+rA=="],
|
||||||
|
|
||||||
|
"blume/ai/@ai-sdk/provider-utils/undici": ["undici@7.29.0", "", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="],
|
||||||
|
|
||||||
"blume/astro/@astrojs/telemetry/is-docker": ["is-docker@4.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA=="],
|
"blume/astro/@astrojs/telemetry/is-docker": ["is-docker@4.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA=="],
|
||||||
|
|
||||||
"blume/astro/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
"blume/astro/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
|
||||||
@@ -8561,46 +8757,8 @@
|
|||||||
|
|
||||||
"blume/astro/p-queue/p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="],
|
"blume/astro/p-queue/p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="],
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.2" }, "os": "darwin", "cpu": "arm64" }, "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.2" }, "os": "darwin", "cpu": "x64" }, "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.2", "", { "os": "linux", "cpu": "arm" }, "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.2" }, "os": "linux", "cpu": "arm" }, "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.2" }, "os": "linux", "cpu": "s390x" }, "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw=="],
|
|
||||||
|
|
||||||
"blume/astro/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.3", "", { "os": "win32", "cpu": "x64" }, "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA=="],
|
|
||||||
|
|
||||||
"blume/astro/unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
"blume/astro/unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||||
|
|
||||||
"blume/astro/unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
|
||||||
|
|
||||||
"blume/astro/unstorage/h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="],
|
"blume/astro/unstorage/h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="],
|
||||||
|
|
||||||
"blume/astro/unstorage/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
"blume/astro/unstorage/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||||
@@ -9059,8 +9217,6 @@
|
|||||||
|
|
||||||
"babel-plugin-module-resolver/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
"babel-plugin-module-resolver/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
|
|
||||||
"blume/astro/unstorage/chokidar/readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="],
|
|
||||||
|
|
||||||
"blume/astro/unstorage/h3/cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="],
|
"blume/astro/unstorage/h3/cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="],
|
||||||
|
|
||||||
"blume/astro/unstorage/h3/crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
|
"blume/astro/unstorage/h3/crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
exact = true
|
exact = true
|
||||||
# Only install newly resolved package versions published at least 3 days ago.
|
# Only install newly resolved package versions published at least 3 days ago.
|
||||||
minimumReleaseAge = 259200
|
minimumReleaseAge = 259200
|
||||||
minimumReleaseAgeExcludes = ["@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@opencode-ai/sdk", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "opentui-spinner", "gitlab-ai-provider", "opencode-gitlab-auth", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64", "@pierre/diffs", "@pierre/theming", "app-builder-lib", "dmg-builder", "electron-builder", "electron-publish"]
|
minimumReleaseAgeExcludes = ["@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@opencode-ai/sdk", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "opentui-spinner", "gitlab-ai-provider", "opencode-gitlab-auth", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64", "@pierre/diffs", "@pierre/theming", "app-builder-lib", "dmg-builder", "electron-builder", "electron-publish", "blume"]
|
||||||
|
|
||||||
[test]
|
[test]
|
||||||
root = "./do-not-run-tests-from-root"
|
root = "./do-not-run-tests-from-root"
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ Usage: install.sh [options]
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h, --help Display this help message
|
-h, --help Display this help message
|
||||||
-v, --version <version> Install a specific version (e.g., 0.0.0-next-17236)
|
-v, --version <version> Install a specific version (e.g., 0.0.0-beta-17236)
|
||||||
-b, --binary <path> Install from a local binary instead of downloading
|
-b, --binary <path> Install from a local binary instead of downloading
|
||||||
--no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
|
--no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
curl -fsSL https://raw.githubusercontent.com/anomalyco/opencode/v2/install | bash
|
curl -fsSL https://opencode.ai/v2/install | bash
|
||||||
curl -fsSL https://raw.githubusercontent.com/anomalyco/opencode/v2/install | bash -s -- --version 0.0.0-next-17236
|
curl -fsSL https://opencode.ai/v2/install | bash -s -- --version 0.0.0-beta-17236
|
||||||
./install --binary /path/to/opencode2
|
./install --binary /path/to/opencode2
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$requested_version" ]; then
|
if [ -z "$requested_version" ]; then
|
||||||
metadata=$(curl -fsSL https://registry.npmjs.org/@opencode-ai%2fcli/next || true)
|
metadata=$(curl -fsSL https://registry.npmjs.org/@opencode-ai%2fcli/beta || true)
|
||||||
specific_version=$(echo "$metadata" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')
|
specific_version=$(echo "$metadata" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')
|
||||||
|
|
||||||
if [ -z "$specific_version" ]; then
|
if [ -z "$specific_version" ]; then
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
"packageManager": "bun@1.3.14",
|
"packageManager": "bun@1.3.14",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --cwd packages/cli --conditions=browser src/index.ts",
|
"dev": "bun run --cwd packages/cli --conditions=browser src/index.ts",
|
||||||
"dev:live": "OPENCODE_TUI_CHANNEL=next OPENCODE_PASSWORD=\"$(opencode2 service get password)\" bun run dev --server \"$(opencode2 service status)\"",
|
"dev:live": "OPENCODE_TUI_CHANNEL=dev OPENCODE_PASSWORD=\"$(opencode2 service get password)\" bun run dev --server \"$(opencode2 service status)\"",
|
||||||
"dev:desktop": "bun --cwd packages/desktop dev",
|
"dev:desktop": "bun --cwd packages/desktop dev",
|
||||||
"dev:web": "bun --cwd packages/app dev",
|
"dev:web": "bun --cwd packages/app dev",
|
||||||
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
|
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import { ToolSchemaProjection } from "./utils/tool-schema.js"
|
|||||||
import { ToolStream } from "./utils/tool-stream.js"
|
import { ToolStream } from "./utils/tool-stream.js"
|
||||||
|
|
||||||
const ADAPTER = "anthropic-messages"
|
const ADAPTER = "anthropic-messages"
|
||||||
const MEDIA_MIMES = new Set<string>([...ProviderShared.IMAGE_MIMES, ...ProviderShared.PDF_MIMES])
|
|
||||||
export const DEFAULT_BASE_URL = "https://api.anthropic.com/v1"
|
export const DEFAULT_BASE_URL = "https://api.anthropic.com/v1"
|
||||||
export const PATH = "/messages"
|
export const PATH = "/messages"
|
||||||
|
|
||||||
@@ -400,7 +399,7 @@ const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult
|
|||||||
})
|
})
|
||||||
|
|
||||||
const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part: MediaPart) {
|
const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part: MediaPart) {
|
||||||
const media = yield* ProviderShared.validateMedia("Anthropic Messages", part, MEDIA_MIMES)
|
const media = ProviderShared.normalizeMedia(part)
|
||||||
if (media.mime === "application/pdf")
|
if (media.mime === "application/pdf")
|
||||||
return {
|
return {
|
||||||
type: "document" as const,
|
type: "document" as const,
|
||||||
@@ -410,6 +409,8 @@ const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part: Me
|
|||||||
data: media.base64,
|
data: media.base64,
|
||||||
},
|
},
|
||||||
} satisfies AnthropicDocumentBlock
|
} satisfies AnthropicDocumentBlock
|
||||||
|
if (!media.mime.startsWith("image/"))
|
||||||
|
return yield* invalid(`Anthropic Messages does not support media type ${part.mediaType}`)
|
||||||
return {
|
return {
|
||||||
type: "image" as const,
|
type: "image" as const,
|
||||||
source: {
|
source: {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import { Lifecycle } from "./utils/lifecycle.js"
|
|||||||
import { ToolSchemaProjection } from "./utils/tool-schema.js"
|
import { ToolSchemaProjection } from "./utils/tool-schema.js"
|
||||||
|
|
||||||
const ADAPTER = "gemini"
|
const ADAPTER = "gemini"
|
||||||
const MEDIA_MIMES = new Set<string>(ProviderShared.MEDIA_MIMES)
|
|
||||||
// Google documents this sentinel for replaying Gemini 3 function calls after their original signature was lost.
|
// Google documents this sentinel for replaying Gemini 3 function calls after their original signature was lost.
|
||||||
const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator"
|
const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator"
|
||||||
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
|
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
|
||||||
@@ -248,7 +247,7 @@ const lowerToolConfig = (toolChoice: NonNullable<LLMRequest["toolChoice"]>) =>
|
|||||||
|
|
||||||
const lowerUserPart = Effect.fn("Gemini.lowerUserPart")(function* (part: TextPart | MediaPart) {
|
const lowerUserPart = Effect.fn("Gemini.lowerUserPart")(function* (part: TextPart | MediaPart) {
|
||||||
if (part.type === "text") return { text: part.text }
|
if (part.type === "text") return { text: part.text }
|
||||||
const media = yield* ProviderShared.validateMedia("Gemini", part, MEDIA_MIMES)
|
const media = ProviderShared.normalizeMedia(part)
|
||||||
return { inlineData: { mimeType: media.mime, data: media.base64 } }
|
return { inlineData: { mimeType: media.mime, data: media.base64 } }
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -353,7 +352,7 @@ const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request: LLMR
|
|||||||
const media: GeminiInlineDataPart[] = []
|
const media: GeminiInlineDataPart[] = []
|
||||||
for (const item of content) {
|
for (const item of content) {
|
||||||
if (item.type === "text") continue
|
if (item.type === "text") continue
|
||||||
const value = yield* ProviderShared.validateToolFile("Gemini", item, MEDIA_MIMES)
|
const value = ProviderShared.normalizeToolFile(item)
|
||||||
media.push({ inlineData: { mimeType: value.mime, data: value.base64 } })
|
media.push({ inlineData: { mimeType: value.mime, data: value.base64 } })
|
||||||
}
|
}
|
||||||
parts.push({
|
parts.push({
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import { ToolStream } from "./utils/tool-stream.js"
|
|||||||
|
|
||||||
const ADAPTER = "open-responses"
|
const ADAPTER = "open-responses"
|
||||||
const NAME = "Open Responses"
|
const NAME = "Open Responses"
|
||||||
const MEDIA_MIMES = new Set<string>([...ProviderShared.IMAGE_MIMES, ...ProviderShared.PDF_MIMES])
|
|
||||||
export const PATH = "/responses"
|
export const PATH = "/responses"
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -285,7 +284,7 @@ export interface Extension {
|
|||||||
readonly name: string
|
readonly name: string
|
||||||
readonly lowerMedia?: (input: {
|
readonly lowerMedia?: (input: {
|
||||||
readonly part: MediaPart
|
readonly part: MediaPart
|
||||||
readonly media: ProviderShared.ValidatedMedia
|
readonly media: ProviderShared.NormalizedMedia
|
||||||
readonly request: LLMRequest
|
readonly request: LLMRequest
|
||||||
}) => MediaInput | undefined
|
}) => MediaInput | undefined
|
||||||
readonly messagePhase?: (value: unknown) => MessagePhase | null | undefined
|
readonly messagePhase?: (value: unknown) => MessagePhase | null | undefined
|
||||||
@@ -380,13 +379,13 @@ const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (
|
|||||||
request: LLMRequest,
|
request: LLMRequest,
|
||||||
extension: Extension,
|
extension: Extension,
|
||||||
) {
|
) {
|
||||||
const media = yield* ProviderShared.validateMedia(extension.name, part, MEDIA_MIMES)
|
const media = ProviderShared.normalizeMedia(part)
|
||||||
const extended = extension.lowerMedia?.({ part, media, request })
|
const extended = extension.lowerMedia?.({ part, media, request })
|
||||||
if (extended) return extended
|
if (extended) return extended
|
||||||
if (media.mime === "application/pdf") {
|
if (!media.mime.startsWith("image/")) {
|
||||||
return {
|
return {
|
||||||
type: "input_file" as const,
|
type: "input_file" as const,
|
||||||
filename: part.filename ?? "document.pdf",
|
filename: part.filename ?? (media.mime === "application/pdf" ? "document.pdf" : "file"),
|
||||||
file_data: media.dataUrl,
|
file_data: media.dataUrl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import { ToolSchemaProjection } from "./utils/tool-schema.js"
|
|||||||
import { ToolStream } from "./utils/tool-stream.js"
|
import { ToolStream } from "./utils/tool-stream.js"
|
||||||
|
|
||||||
const ADAPTER = "openai-chat"
|
const ADAPTER = "openai-chat"
|
||||||
const IMAGE_MIMES = new Set<string>(ProviderShared.IMAGE_MIMES)
|
|
||||||
const RESERVED_REASONING_FIELDS = new Set(["role", "content", "tool_calls"])
|
const RESERVED_REASONING_FIELDS = new Set(["role", "content", "tool_calls"])
|
||||||
export const DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
export const DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
||||||
export const PATH = "/chat/completions"
|
export const PATH = "/chat/completions"
|
||||||
@@ -284,7 +283,9 @@ const lowerToolCall = (part: ToolCallPart): OpenAIChatAssistantToolCall => ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part: MediaPart) {
|
const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part: MediaPart) {
|
||||||
const media = yield* ProviderShared.validateMedia("OpenAI Chat", part, IMAGE_MIMES)
|
const media = ProviderShared.normalizeMedia(part)
|
||||||
|
if (!media.mime.startsWith("image/"))
|
||||||
|
return yield* ProviderShared.invalidRequest(`OpenAI Chat does not support media type ${part.mediaType}`)
|
||||||
return { type: "image_url" as const, image_url: { url: media.dataUrl } }
|
return { type: "image_url" as const, image_url: { url: media.dataUrl } }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -155,59 +155,24 @@ export const wrappedSystemUpdate = Effect.fn("ProviderShared.wrappedSystemUpdate
|
|||||||
export const parseToolInput = (route: string, name: string, raw: string) =>
|
export const parseToolInput = (route: string, name: string, raw: string) =>
|
||||||
parseJson(route, raw || "{}", `Invalid JSON input for ${route} tool call ${name}`)
|
parseJson(route, raw || "{}", `Invalid JSON input for ${route} tool call ${name}`)
|
||||||
|
|
||||||
export const IMAGE_MIMES = ["image/png", "image/jpeg", "image/gif", "image/webp"] as const
|
export interface NormalizedMedia {
|
||||||
export const VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"] as const
|
|
||||||
export const AUDIO_MIMES = ["audio/wav", "audio/mp3", "audio/aiff", "audio/aac", "audio/ogg", "audio/flac"] as const
|
|
||||||
export const PDF_MIMES = ["application/pdf"] as const
|
|
||||||
export const MEDIA_MIMES = [...IMAGE_MIMES, ...VIDEO_MIMES, ...AUDIO_MIMES, ...PDF_MIMES] as const
|
|
||||||
export const MAX_MEDIA_ENCODED_BYTES = 28 * 1024 * 1024
|
|
||||||
export const MAX_MEDIA_DECODED_BYTES = 20 * 1024 * 1024
|
|
||||||
|
|
||||||
const base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
|
|
||||||
|
|
||||||
export interface ValidatedMedia {
|
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly base64: string
|
readonly base64: string
|
||||||
readonly dataUrl: string
|
readonly dataUrl: string
|
||||||
readonly bytes: Uint8Array
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const validateMedia = Effect.fn("ProviderShared.validateMedia")(function* (
|
export const normalizeMedia = (part: MediaPart): NormalizedMedia => {
|
||||||
route: string,
|
|
||||||
part: MediaPart,
|
|
||||||
supportedMimes: ReadonlySet<string>,
|
|
||||||
) {
|
|
||||||
const mime = part.mediaType.toLowerCase()
|
const mime = part.mediaType.toLowerCase()
|
||||||
if (!supportedMimes.has(mime)) return yield* invalidRequest(`${route} does not support media type ${part.mediaType}`)
|
|
||||||
|
|
||||||
let base64: string
|
|
||||||
if (typeof part.data !== "string") {
|
if (typeof part.data !== "string") {
|
||||||
if (part.data.byteLength > MAX_MEDIA_DECODED_BYTES)
|
const base64 = Buffer.from(part.data).toString("base64")
|
||||||
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_DECODED_BYTES} byte decoded limit`)
|
return { mime, base64, dataUrl: `data:${mime};base64,${base64}` }
|
||||||
base64 = Buffer.from(part.data).toString("base64")
|
|
||||||
} else if (part.data.startsWith("data:")) {
|
|
||||||
const match = /^data:([^;,]+);base64,([A-Za-z0-9+/]*={0,2})$/s.exec(part.data)
|
|
||||||
if (!match) return yield* invalidRequest(`${route} media data URL must contain valid base64`)
|
|
||||||
if (match[1]!.toLowerCase() !== mime)
|
|
||||||
return yield* invalidRequest(`${route} media type ${part.mediaType} does not match data URL type ${match[1]}`)
|
|
||||||
base64 = match[2]!
|
|
||||||
} else {
|
|
||||||
base64 = part.data
|
|
||||||
}
|
}
|
||||||
|
if (!part.data.startsWith("data:")) return { mime, base64: part.data, dataUrl: `data:${mime};base64,${part.data}` }
|
||||||
|
return { mime, base64: part.data.slice(part.data.indexOf(",") + 1), dataUrl: part.data }
|
||||||
|
}
|
||||||
|
|
||||||
if (Buffer.byteLength(base64, "utf8") > MAX_MEDIA_ENCODED_BYTES)
|
export const normalizeToolFile = (part: Tool.FileContent) =>
|
||||||
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_ENCODED_BYTES} byte encoded limit`)
|
normalizeMedia({ type: "media", mediaType: part.mime, data: part.uri, filename: part.name })
|
||||||
if (!base64 || base64.length % 4 !== 0 || !base64Pattern.test(base64))
|
|
||||||
return yield* invalidRequest(`${route} media must contain valid base64`)
|
|
||||||
const bytes = Buffer.from(base64, "base64")
|
|
||||||
if (bytes.byteLength > MAX_MEDIA_DECODED_BYTES)
|
|
||||||
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_DECODED_BYTES} byte decoded limit`)
|
|
||||||
if (bytes.toString("base64") !== base64) return yield* invalidRequest(`${route} media must contain canonical base64`)
|
|
||||||
return { mime, base64, dataUrl: `data:${mime};base64,${base64}`, bytes } satisfies ValidatedMedia
|
|
||||||
})
|
|
||||||
|
|
||||||
export const validateToolFile = (route: string, part: Tool.FileContent, supportedMimes: ReadonlySet<string>) =>
|
|
||||||
validateMedia(route, { type: "media", mediaType: part.mime, data: part.uri, filename: part.name }, supportedMimes)
|
|
||||||
|
|
||||||
export const trimBaseUrl = (value: string) => value.replace(/\/+$/, "")
|
export const trimBaseUrl = (value: string) => value.replace(/\/+$/, "")
|
||||||
|
|
||||||
|
|||||||
@@ -66,11 +66,7 @@ export const lower = Effect.fn("BedrockMedia.lower")(function* (part: MediaPart)
|
|||||||
const mime = part.mediaType.toLowerCase()
|
const mime = part.mediaType.toLowerCase()
|
||||||
const imageFormat = IMAGE_FORMATS[mime as keyof typeof IMAGE_FORMATS]
|
const imageFormat = IMAGE_FORMATS[mime as keyof typeof IMAGE_FORMATS]
|
||||||
if (imageFormat) {
|
if (imageFormat) {
|
||||||
const media = yield* ProviderShared.validateMedia(
|
const media = ProviderShared.normalizeMedia(part)
|
||||||
"Bedrock Converse",
|
|
||||||
part,
|
|
||||||
new Set<string>(Object.keys(IMAGE_FORMATS)),
|
|
||||||
)
|
|
||||||
return { image: { format: imageFormat, source: { bytes: media.base64 } } } satisfies ImageBlock
|
return { image: { format: imageFormat, source: { bytes: media.base64 } } } satisfies ImageBlock
|
||||||
}
|
}
|
||||||
if (mime.startsWith("image/"))
|
if (mime.startsWith("image/"))
|
||||||
@@ -79,11 +75,7 @@ export const lower = Effect.fn("BedrockMedia.lower")(function* (part: MediaPart)
|
|||||||
if (documentFormat) {
|
if (documentFormat) {
|
||||||
if (!part.filename)
|
if (!part.filename)
|
||||||
return yield* ProviderShared.invalidRequest("Bedrock Converse document media requires a filename")
|
return yield* ProviderShared.invalidRequest("Bedrock Converse document media requires a filename")
|
||||||
const media = yield* ProviderShared.validateMedia(
|
const media = ProviderShared.normalizeMedia(part)
|
||||||
"Bedrock Converse",
|
|
||||||
part,
|
|
||||||
new Set<string>(Object.keys(DOCUMENT_FORMATS)),
|
|
||||||
)
|
|
||||||
return documentBlock(part.filename, documentFormat, media.base64)
|
return documentBlock(part.filename, documentFormat, media.base64)
|
||||||
}
|
}
|
||||||
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support media type ${part.mediaType}`)
|
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support media type ${part.mediaType}`)
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ describe("Anthropic Messages route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("rejects unsupported media in tool-result content with a clear error", () =>
|
it.effect("rejects tool-result media that cannot be lowered", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const error = yield* compileRequest(
|
const error = yield* compileRequest(
|
||||||
LLM.request({
|
LLM.request({
|
||||||
@@ -418,8 +418,7 @@ describe("Anthropic Messages route", () => {
|
|||||||
}),
|
}),
|
||||||
).pipe(Effect.flip)
|
).pipe(Effect.flip)
|
||||||
|
|
||||||
expect(error.message).toContain("Anthropic Messages")
|
expect(error.message).toContain("Anthropic Messages does not support media type audio/mpeg")
|
||||||
expect(error.message).toContain("audio/mpeg")
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { LLM, AIError, LLMRequest, Message, ToolCallPart, ToolDefinition, Usage
|
|||||||
import { Auth, LLMClient } from "../../src/route.js"
|
import { Auth, LLMClient } from "../../src/route.js"
|
||||||
import { compileRequest } from "../../src/route/client.js"
|
import { compileRequest } from "../../src/route/client.js"
|
||||||
import * as Gemini from "../../src/protocols/gemini.js"
|
import * as Gemini from "../../src/protocols/gemini.js"
|
||||||
import { ProviderShared } from "../../src/protocols/shared.js"
|
|
||||||
import { it } from "../lib/effect.js"
|
import { it } from "../lib/effect.js"
|
||||||
import { fixedResponse } from "../lib/http.js"
|
import { fixedResponse } from "../lib/http.js"
|
||||||
import { sseEvents, sseRaw } from "../lib/sse.js"
|
import { sseEvents, sseRaw } from "../lib/sse.js"
|
||||||
@@ -291,35 +290,30 @@ describe("Gemini route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const [name, media] of [
|
it.effect("passes encoded media through without local validation", () =>
|
||||||
["mismatched data URL MIME", { mediaType: "image/png", data: "data:image/jpeg;base64,/9j/" }],
|
|
||||||
["malformed base64", { mediaType: "image/png", data: "%%%=" }],
|
|
||||||
["unsupported SVG", { mediaType: "image/svg+xml", data: "PHN2Zz4=" }],
|
|
||||||
] as const)
|
|
||||||
it.effect(`rejects ${name}`, () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const error = yield* compileRequest(
|
|
||||||
LLM.request({ model, messages: [Message.user({ type: "media", ...media })] }),
|
|
||||||
).pipe(Effect.flip)
|
|
||||||
expect(error.message).toMatch(/does not support|does not match|valid base64/)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.effect("rejects oversized image input", () =>
|
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const error = yield* compileRequest(
|
const prepared = yield* compileRequest(
|
||||||
LLM.request({
|
LLM.request({
|
||||||
model,
|
model,
|
||||||
messages: [
|
messages: [
|
||||||
Message.user({
|
Message.user([
|
||||||
type: "media",
|
{ type: "media", mediaType: "image/png", data: "%%%=" },
|
||||||
mediaType: "image/png",
|
{ type: "media", mediaType: "image/png", data: "data:image/jpeg;base64,/9j/" },
|
||||||
data: "A".repeat(ProviderShared.MAX_MEDIA_ENCODED_BYTES + 4),
|
{ type: "media", mediaType: "image/svg+xml", data: "PHN2Zz4=" },
|
||||||
}),
|
]),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
).pipe(Effect.flip)
|
)
|
||||||
expect(error.message).toContain("encoded limit")
|
expect(prepared.body.contents).toEqual([
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
parts: [
|
||||||
|
{ inlineData: { mimeType: "image/png", data: "%%%=" } },
|
||||||
|
{ inlineData: { mimeType: "image/png", data: "/9j/" } },
|
||||||
|
{ inlineData: { mimeType: "image/svg+xml", data: "PHN2Zz4=" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -527,35 +527,42 @@ describe("OpenAI Chat route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const [name, media] of [
|
it.effect("passes encoded image media through without local validation", () =>
|
||||||
["mismatched data URL MIME", { mediaType: "image/png", data: "data:image/jpeg;base64,/9j/" }],
|
Effect.gen(function* () {
|
||||||
["malformed base64", { mediaType: "image/png", data: "not-base64" }],
|
const prepared = yield* compileRequest(
|
||||||
["unsupported SVG", { mediaType: "image/svg+xml", data: "PHN2Zz4=" }],
|
LLM.request({
|
||||||
] as const)
|
model,
|
||||||
it.effect(`rejects ${name}`, () =>
|
messages: [
|
||||||
Effect.gen(function* () {
|
Message.user([
|
||||||
const error = yield* compileRequest(
|
{ type: "media", mediaType: "image/png", data: "not-base64" },
|
||||||
LLM.request({ model, messages: [Message.user({ type: "media", ...media })] }),
|
{ type: "media", mediaType: "image/png", data: "data:image/jpeg;base64,/9j/" },
|
||||||
).pipe(Effect.flip)
|
{ type: "media", mediaType: "image/svg+xml", data: "PHN2Zz4=" },
|
||||||
expect(error.message).toMatch(/does not support|does not match|valid base64/)
|
]),
|
||||||
}),
|
],
|
||||||
)
|
}),
|
||||||
|
)
|
||||||
|
expect(prepared.body.messages).toEqual([
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: [
|
||||||
|
{ type: "image_url", image_url: { url: "data:image/png;base64,not-base64" } },
|
||||||
|
{ type: "image_url", image_url: { url: "data:image/jpeg;base64,/9j/" } },
|
||||||
|
{ type: "image_url", image_url: { url: "data:image/svg+xml;base64,PHN2Zz4=" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("rejects oversized image input", () =>
|
it.effect("rejects non-image media that cannot be lowered", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const error = yield* compileRequest(
|
const error = yield* compileRequest(
|
||||||
LLM.request({
|
LLM.request({
|
||||||
model,
|
model,
|
||||||
messages: [
|
messages: [Message.user({ type: "media", mediaType: "audio/mpeg", data: "AAECAw==" })],
|
||||||
Message.user({
|
|
||||||
type: "media",
|
|
||||||
mediaType: "image/png",
|
|
||||||
data: "A".repeat(ProviderShared.MAX_MEDIA_ENCODED_BYTES + 4),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
).pipe(Effect.flip)
|
).pipe(Effect.flip)
|
||||||
expect(error.message).toContain("encoded limit")
|
expect(error.message).toContain("OpenAI Chat does not support media type audio/mpeg")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1149,6 +1149,32 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("passes large PDF tool-result content through", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const base64 = "A".repeat(8_125_844)
|
||||||
|
const dataUrl = `data:application/pdf;base64,${base64}`
|
||||||
|
const prepared = yield* compileRequest(
|
||||||
|
LLM.request({
|
||||||
|
id: "req_tool_result_large_pdf",
|
||||||
|
model,
|
||||||
|
messages: [
|
||||||
|
Message.assistant([ToolCallPart.make({ id: "call_1", name: "read", input: {} })]),
|
||||||
|
Message.tool({
|
||||||
|
id: "call_1",
|
||||||
|
name: "read",
|
||||||
|
resultType: "content",
|
||||||
|
result: [{ type: "file", uri: dataUrl, mime: "application/pdf", name: "report.pdf" }],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(expectToolOutput(prepared.body).output).toEqual([
|
||||||
|
{ type: "input_file", filename: "report.pdf", file_data: dataUrl },
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("uses xAI inline file encoding for PDF tool results", () =>
|
it.effect("uses xAI inline file encoding for PDF tool results", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const prepared = yield* compileRequest(
|
const prepared = yield* compileRequest(
|
||||||
@@ -1184,9 +1210,9 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("rejects unsupported media in tool-result content with a clear error", () =>
|
it.effect("passes non-image tool-result content through as an input file", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const error = yield* compileRequest(
|
const prepared = yield* compileRequest(
|
||||||
LLM.request({
|
LLM.request({
|
||||||
id: "req_tool_result_unsupported_media",
|
id: "req_tool_result_unsupported_media",
|
||||||
model,
|
model,
|
||||||
@@ -1200,10 +1226,11 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
).pipe(Effect.flip)
|
)
|
||||||
|
|
||||||
expect(error.message).toContain("OpenAI Responses")
|
expect(expectToolOutput(prepared.body).output).toEqual([
|
||||||
expect(error.message).toContain("audio/mpeg")
|
{ type: "input_file", filename: "file", file_data: "data:audio/mpeg;base64,AAECAw==" },
|
||||||
|
])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2394,17 +2421,28 @@ describe("OpenAI Responses route", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("rejects unsupported user media content", () =>
|
it.effect("passes non-image user media through as an input file", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const error = yield* compileRequest(
|
const prepared = yield* compileRequest(
|
||||||
LLM.request({
|
LLM.request({
|
||||||
id: "req_media",
|
id: "req_media",
|
||||||
model,
|
model,
|
||||||
messages: [Message.user({ type: "media", mediaType: "application/x-tar", data: "AAECAw==" })],
|
messages: [Message.user({ type: "media", mediaType: "application/x-tar", data: "AAECAw==" })],
|
||||||
}),
|
}),
|
||||||
).pipe(Effect.flip)
|
)
|
||||||
|
|
||||||
expect(error.message).toContain("OpenAI Responses does not support media type application/x-tar")
|
expect(prepared.body.input).toEqual([
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "input_file",
|
||||||
|
filename: "file",
|
||||||
|
file_data: "data:application/x-tar;base64,AAECAw==",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,13 @@ export async function setupTimeline(
|
|||||||
const transport = await installSseTransport<EventPayload>(page, {
|
const transport = await installSseTransport<EventPayload>(page, {
|
||||||
server,
|
server,
|
||||||
retry: input.eventRetry ?? 20,
|
retry: input.eventRetry ?? 20,
|
||||||
|
existingTools: messages.flatMap((message) =>
|
||||||
|
"parts" in message
|
||||||
|
? message.parts.flatMap((part) => (part.type === "tool" ? [part.callID] : []))
|
||||||
|
: message.type === "assistant"
|
||||||
|
? message.content.flatMap((part) => (part.type === "tool" ? [part.id] : []))
|
||||||
|
: [],
|
||||||
|
),
|
||||||
})
|
})
|
||||||
await mockOpenCodeServer(page, {
|
await mockOpenCodeServer(page, {
|
||||||
protocol: "v2",
|
protocol: "v2",
|
||||||
@@ -151,9 +158,6 @@ export async function setupTimeline(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
if (settings.newLayoutDesigns === false) {
|
|
||||||
localStorage.setItem("app-version.v1", JSON.stringify({ version: "1.17.20" }))
|
|
||||||
}
|
|
||||||
}, input.settings ?? {})
|
}, input.settings ?? {})
|
||||||
if (input.locale) {
|
if (input.locale) {
|
||||||
await page.addInitScript((locale) => {
|
await page.addInitScript((locale) => {
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ benchmark.describe("performance: review pane scaling", () => {
|
|||||||
await setupTimelineBenchmark(page, {
|
await setupTimelineBenchmark(page, {
|
||||||
historyTurns: 0,
|
historyTurns: 0,
|
||||||
eventBatch: 1,
|
eventBatch: 1,
|
||||||
newLayoutDesigns: true,
|
|
||||||
})
|
})
|
||||||
await page.route("**/vcs/diff**", (route) =>
|
await page.route("**/vcs/diff**", (route) =>
|
||||||
route.fulfill({
|
route.fulfill({
|
||||||
|
|||||||
@@ -13,21 +13,8 @@ import { measureSessionSwitch, waitForStableTimeline } from "./session-tab-switc
|
|||||||
|
|
||||||
type Result = Awaited<ReturnType<typeof measureSessionSwitch>>
|
type Result = Awaited<ReturnType<typeof measureSessionSwitch>>
|
||||||
|
|
||||||
benchmark("benchmarks cold and hot session tab switching", async ({ browser, report }, testInfo) => {
|
|
||||||
benchmark.setTimeout(180_000)
|
|
||||||
const results = { cold: [] as Result[], hot: [] as Result[] }
|
|
||||||
for (const mode of ["cold", "hot"] as const) {
|
|
||||||
for (let run = 0; run < 5; run++) {
|
|
||||||
results[mode].push(
|
|
||||||
await withBenchmarkPage(browser, `session-tab-switch-${mode}-${run}`, (page) => trial(page, mode), testInfo),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
report({ results, summary: summarize(results) })
|
|
||||||
})
|
|
||||||
|
|
||||||
benchmark(
|
benchmark(
|
||||||
"benchmarks v2 session tab switching with and without the review pane",
|
"benchmarks session tab switching with and without the review pane",
|
||||||
async ({ browser, report }, testInfo) => {
|
async ({ browser, report }, testInfo) => {
|
||||||
benchmark.setTimeout(360_000)
|
benchmark.setTimeout(360_000)
|
||||||
const runs = Number(process.env.SESSION_TAB_SWITCH_RUNS ?? 5)
|
const runs = Number(process.env.SESSION_TAB_SWITCH_RUNS ?? 5)
|
||||||
@@ -41,8 +28,8 @@ benchmark(
|
|||||||
results[reviewPane][mode].push(
|
results[reviewPane][mode].push(
|
||||||
await withBenchmarkPage(
|
await withBenchmarkPage(
|
||||||
browser,
|
browser,
|
||||||
`session-tab-switch-v2-${reviewPane}-${mode}-${run}`,
|
`session-tab-switch-${reviewPane}-${mode}-${run}`,
|
||||||
(page) => trial(page, mode, { newLayoutDesigns: true, reviewPane }),
|
(page) => trial(page, mode, reviewPane),
|
||||||
testInfo,
|
testInfo,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -53,14 +40,10 @@ benchmark(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async function trial(
|
async function trial(page: Page, mode: "cold" | "hot", reviewPane: "closed" | "open") {
|
||||||
page: Page,
|
const reviewDiffs = createReviewDiffs()
|
||||||
mode: "cold" | "hot",
|
|
||||||
options?: { newLayoutDesigns?: boolean; reviewPane?: "closed" | "open" },
|
|
||||||
) {
|
|
||||||
const reviewDiffs = options?.newLayoutDesigns ? createReviewDiffs() : undefined
|
|
||||||
await mockStressTimeline(page, { vcsDiff: reviewDiffs })
|
await mockStressTimeline(page, { vcsDiff: reviewDiffs })
|
||||||
if (options?.newLayoutDesigns) await installTimelineSettings(page)
|
await installTimelineSettings(page)
|
||||||
await installStressSessionTabs(page)
|
await installStressSessionTabs(page)
|
||||||
if (mode === "hot") {
|
if (mode === "hot") {
|
||||||
await page.goto(stressSessionHref(fixture.targetID))
|
await page.goto(stressSessionHref(fixture.targetID))
|
||||||
@@ -72,7 +55,7 @@ async function trial(
|
|||||||
await expectSessionTitle(page, fixture.expected.sourceTitle)
|
await expectSessionTitle(page, fixture.expected.sourceTitle)
|
||||||
}
|
}
|
||||||
await waitForStableTimeline(page, fixture.expected.sourceMessageIDs.at(-1)!)
|
await waitForStableTimeline(page, fixture.expected.sourceMessageIDs.at(-1)!)
|
||||||
if (options?.reviewPane === "open") {
|
if (reviewPane === "open") {
|
||||||
await openReviewPane(page)
|
await openReviewPane(page)
|
||||||
await waitForStableTimeline(page, fixture.expected.sourceMessageIDs.at(-1)!)
|
await waitForStableTimeline(page, fixture.expected.sourceMessageIDs.at(-1)!)
|
||||||
}
|
}
|
||||||
@@ -134,8 +117,6 @@ async function openReviewPane(page: Page) {
|
|||||||
await page.getByRole("button", { name: "Toggle review" }).click()
|
await page.getByRole("button", { name: "Toggle review" }).click()
|
||||||
const panel = page.locator("#review-panel")
|
const panel = page.locator("#review-panel")
|
||||||
await expect(panel).toBeVisible()
|
await expect(panel).toBeVisible()
|
||||||
// Text-based readiness works across review implementations; the legacy list mounts
|
|
||||||
// diff viewers lazily while V2 mounts the active preview eagerly.
|
|
||||||
await page.waitForFunction(() => {
|
await page.waitForFunction(() => {
|
||||||
const panel = document.querySelector<HTMLElement>("#review-panel")
|
const panel = document.querySelector<HTMLElement>("#review-panel")
|
||||||
const text = panel?.textContent ?? ""
|
const text = panel?.textContent ?? ""
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ export async function setupTimelineBenchmark(
|
|||||||
options: {
|
options: {
|
||||||
historyTurns: number
|
historyTurns: number
|
||||||
eventBatch: number
|
eventBatch: number
|
||||||
newLayoutDesigns?: boolean
|
|
||||||
vcsDiff?: unknown[]
|
vcsDiff?: unknown[]
|
||||||
turnDiffs?: unknown[]
|
turnDiffs?: unknown[]
|
||||||
},
|
},
|
||||||
@@ -124,22 +123,18 @@ export async function setupTimelineBenchmark(
|
|||||||
events: () => events.splice(0, eventBatch),
|
events: () => events.splice(0, eventBatch),
|
||||||
eventRetry: 16,
|
eventRetry: 16,
|
||||||
})
|
})
|
||||||
await page.addInitScript(
|
await page.addInitScript(() => {
|
||||||
(input) => {
|
localStorage.setItem(
|
||||||
localStorage.setItem(
|
"settings.v3",
|
||||||
"settings.v3",
|
JSON.stringify({
|
||||||
JSON.stringify({
|
general: {
|
||||||
general: {
|
editToolPartsExpanded: true,
|
||||||
newLayoutDesigns: input.newLayoutDesigns,
|
shellToolPartsExpanded: true,
|
||||||
editToolPartsExpanded: true,
|
showReasoningSummaries: true,
|
||||||
shellToolPartsExpanded: true,
|
},
|
||||||
showReasoningSummaries: true,
|
}),
|
||||||
},
|
)
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
},
|
|
||||||
{ newLayoutDesigns: options.newLayoutDesigns ?? false },
|
|
||||||
)
|
|
||||||
await page.setViewportSize({ width: 1366, height: 768 })
|
await page.setViewportSize({ width: 1366, height: 768 })
|
||||||
const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") })
|
const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") })
|
||||||
const text = page.locator(`[data-timeline-part-id="${textPartID}"]`).first()
|
const text = page.locator(`[data-timeline-part-id="${textPartID}"]`).first()
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
} from "./session-timeline-stream-probe"
|
} from "./session-timeline-stream-probe"
|
||||||
|
|
||||||
type TimelineStreamOptions = {
|
type TimelineStreamOptions = {
|
||||||
newLayoutDesigns?: boolean
|
|
||||||
reviewDiffs?: boolean
|
reviewDiffs?: boolean
|
||||||
reviewPane?: boolean
|
reviewPane?: boolean
|
||||||
}
|
}
|
||||||
@@ -45,34 +44,27 @@ benchmark.describe("performance: session timeline streaming", () => {
|
|||||||
report(result.metrics, result.context)
|
report(result.metrics, result.context)
|
||||||
})
|
})
|
||||||
|
|
||||||
benchmark("streams assistant text in v2 with review pane closed", async ({ page, report }) => {
|
benchmark("streams assistant text with review diffs and pane closed", async ({ page, report }) => {
|
||||||
benchmark.setTimeout(Number(process.env.TIMELINE_COMPLETION_TIMEOUT_MS ?? 420_000) + 60_000)
|
benchmark.setTimeout(Number(process.env.TIMELINE_COMPLETION_TIMEOUT_MS ?? 420_000) + 60_000)
|
||||||
const result = await runTimelineStreamBenchmark(page, { newLayoutDesigns: true })
|
const result = await runTimelineStreamBenchmark(page, { reviewDiffs: true })
|
||||||
report(result.metrics, result.context)
|
report(result.metrics, result.context)
|
||||||
})
|
})
|
||||||
|
|
||||||
benchmark("streams assistant text in v2 with review diffs and pane closed", async ({ page, report }) => {
|
benchmark("streams assistant text with review pane open", async ({ page, report }) => {
|
||||||
benchmark.setTimeout(Number(process.env.TIMELINE_COMPLETION_TIMEOUT_MS ?? 420_000) + 60_000)
|
benchmark.setTimeout(Number(process.env.TIMELINE_COMPLETION_TIMEOUT_MS ?? 420_000) + 60_000)
|
||||||
const result = await runTimelineStreamBenchmark(page, { newLayoutDesigns: true, reviewDiffs: true })
|
const result = await runTimelineStreamBenchmark(page, { reviewPane: true })
|
||||||
report(result.metrics, result.context)
|
|
||||||
})
|
|
||||||
|
|
||||||
benchmark("streams assistant text in v2 with review pane open", async ({ page, report }) => {
|
|
||||||
benchmark.setTimeout(Number(process.env.TIMELINE_COMPLETION_TIMEOUT_MS ?? 420_000) + 60_000)
|
|
||||||
const result = await runTimelineStreamBenchmark(page, { newLayoutDesigns: true, reviewPane: true })
|
|
||||||
report(result.metrics, result.context)
|
report(result.metrics, result.context)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
benchmark.describe("performance: review pane", () => {
|
benchmark.describe("performance: review pane", () => {
|
||||||
benchmark("loads v2 review diffs and switches active files", async ({ page, report }) => {
|
benchmark("loads review diffs and switches active files", async ({ page, report }) => {
|
||||||
benchmark.setTimeout(240_000)
|
benchmark.setTimeout(240_000)
|
||||||
const historyTurns = Number(process.env.REVIEW_PANE_HISTORY_TURNS ?? 72)
|
const historyTurns = Number(process.env.REVIEW_PANE_HISTORY_TURNS ?? 72)
|
||||||
const diffs = createReviewDiffs()
|
const diffs = createReviewDiffs()
|
||||||
const fixture = await setupTimelineBenchmark(page, {
|
const fixture = await setupTimelineBenchmark(page, {
|
||||||
historyTurns,
|
historyTurns,
|
||||||
eventBatch: 1,
|
eventBatch: 1,
|
||||||
newLayoutDesigns: true,
|
|
||||||
vcsDiff: diffs,
|
vcsDiff: diffs,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -112,7 +104,6 @@ async function runTimelineStreamBenchmark(page: Page, options: TimelineStreamOpt
|
|||||||
const fixture = await setupTimelineBenchmark(page, {
|
const fixture = await setupTimelineBenchmark(page, {
|
||||||
historyTurns,
|
historyTurns,
|
||||||
eventBatch,
|
eventBatch,
|
||||||
newLayoutDesigns: options.newLayoutDesigns,
|
|
||||||
// Turn diffs exercise timeline data cost; the pane-open scenario serves the same
|
// Turn diffs exercise timeline data cost; the pane-open scenario serves the same
|
||||||
// diffs through the default git mode so it works across review implementations.
|
// diffs through the default git mode so it works across review implementations.
|
||||||
turnDiffs: options.reviewDiffs ? diffs : undefined,
|
turnDiffs: options.reviewDiffs ? diffs : undefined,
|
||||||
@@ -173,7 +164,6 @@ async function runTimelineStreamBenchmark(page: Page, options: TimelineStreamOpt
|
|||||||
queuedDeltas: deltas.length,
|
queuedDeltas: deltas.length,
|
||||||
historyTurns,
|
historyTurns,
|
||||||
eventBatch,
|
eventBatch,
|
||||||
newLayoutDesigns: options.newLayoutDesigns === true,
|
|
||||||
reviewPane: options.reviewPane === true ? "open" : "closed",
|
reviewPane: options.reviewPane === true ? "open" : "closed",
|
||||||
reviewDiffs: diffs?.length ?? 0,
|
reviewDiffs: diffs?.length ?? 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export async function installTimelineSettings(page: Page) {
|
|||||||
"settings.v3",
|
"settings.v3",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
general: {
|
general: {
|
||||||
newLayoutDesigns: true,
|
|
||||||
editToolPartsExpanded: true,
|
editToolPartsExpanded: true,
|
||||||
shellToolPartsExpanded: true,
|
shellToolPartsExpanded: true,
|
||||||
showReasoningSummaries: true,
|
showReasoningSummaries: true,
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ test("closing the active server's last tab opens the remaining server tab", asyn
|
|||||||
await mockServers(page, requests)
|
await mockServers(page, requests)
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ serverB, sessionA, sessionB }) => {
|
({ serverB, sessionA, sessionB }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] }))
|
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] }))
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.window.browser.dat:tabs",
|
"opencode.window.browser.dat:tabs",
|
||||||
@@ -39,19 +38,12 @@ test("closing the active server's last tab opens the remaining server tab", asyn
|
|||||||
await expect(page.getByText(sessionB.title).first()).toBeVisible()
|
await expect(page.getByText(sessionB.title).first()).toBeVisible()
|
||||||
const sessionBRequests = requests.filter((url) => url.includes(`/session/${sessionB.id}`))
|
const sessionBRequests = requests.filter((url) => url.includes(`/session/${sessionB.id}`))
|
||||||
expect(sessionBRequests.every((url) => url.startsWith(serverB))).toBe(true)
|
expect(sessionBRequests.every((url) => url.startsWith(serverB))).toBe(true)
|
||||||
expect(
|
|
||||||
requests.some((request) => {
|
|
||||||
const url = new URL(request)
|
|
||||||
return url.origin === serverB && url.searchParams.get("directory") === sessionB.directory
|
|
||||||
}),
|
|
||||||
).toBe(true)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("legacy session routes preserve an existing tab's server", async ({ page }) => {
|
test("legacy session routes preserve an existing tab's server", async ({ page }) => {
|
||||||
await mockServers(page, [])
|
await mockServers(page, [])
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ serverB, sessionB }) => {
|
({ serverB, sessionB }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] }))
|
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] }))
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.window.browser.dat:tabs",
|
"opencode.window.browser.dat:tabs",
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ async function setup(page: Page) {
|
|||||||
|
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ directory, server, sessionID }) => {
|
({ directory, server, sessionID }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.global.dat:server",
|
"opencode.global.dat:server",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ test("matches the rounded panel corners to the dark new-session background", asy
|
|||||||
})
|
})
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ directory, draftID, server }) => {
|
({ directory, draftID, server }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem("opencode-theme-id", "oc-2")
|
localStorage.setItem("opencode-theme-id", "oc-2")
|
||||||
localStorage.setItem("opencode-color-scheme", "dark")
|
localStorage.setItem("opencode-color-scheme", "dark")
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
|
|||||||
@@ -81,10 +81,6 @@ test("expands a folder whose path has a trailing Windows separator", async ({ pa
|
|||||||
|
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ directory, server, sessionID }) => {
|
({ directory, server, sessionID }) => {
|
||||||
localStorage.setItem(
|
|
||||||
"settings.v3",
|
|
||||||
JSON.stringify({ general: { newLayoutDesigns: true, shouldDisplayTabsToast: false } }),
|
|
||||||
)
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.global.dat:server",
|
"opencode.global.dat:server",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import { expect, test } from "@playwright/test"
|
|
||||||
import type { Page } from "@playwright/test"
|
|
||||||
import { fixture, pageMessages } from "../smoke/session-timeline.fixture"
|
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
|
||||||
import { expectAppVisible } from "../utils/waits"
|
|
||||||
|
|
||||||
const NAMES = ["alpha-service", "bravo-web", "charlie-api", "delta-tools", "echo-infra", "foxtrot-docs"]
|
|
||||||
const worktrees = NAMES.map((name) => `/opencode-demo/${name}`)
|
|
||||||
|
|
||||||
// The sixth project sits outside the five-item recent cap, so it is only reachable if the
|
|
||||||
// dialog hands every recent project to the list filter instead of a pre-truncated slice.
|
|
||||||
const OUTSIDE_CAP = "foxtrot-docs"
|
|
||||||
|
|
||||||
// Dialog rows carry data-directory-path; the sidebar project list does not, so this
|
|
||||||
// scopes assertions to the picker instead of matching the sidebar entry of the same name.
|
|
||||||
const rows = (page: Page) => page.locator("[data-directory-path]")
|
|
||||||
const row = (page: Page, name: string) => page.locator(`[data-directory-path*="${name}"]`)
|
|
||||||
|
|
||||||
async function openProjectDialog(page: Page) {
|
|
||||||
await mockOpenCodeServer(page, {
|
|
||||||
sessions: fixture.sessions,
|
|
||||||
provider: fixture.provider,
|
|
||||||
directory: fixture.directory,
|
|
||||||
project: fixture.project,
|
|
||||||
pageMessages,
|
|
||||||
fileList: () => [],
|
|
||||||
findFiles: () => [],
|
|
||||||
})
|
|
||||||
await page.addInitScript((dirs) => {
|
|
||||||
localStorage.setItem(
|
|
||||||
"opencode.global.dat:server",
|
|
||||||
JSON.stringify({
|
|
||||||
projects: { local: dirs.map((worktree: string) => ({ worktree, expanded: false })) },
|
|
||||||
lastProject: {},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}, worktrees)
|
|
||||||
await page.goto("/")
|
|
||||||
const add = page.getByRole("button", { name: "Add project" }).first()
|
|
||||||
await expectAppVisible(add)
|
|
||||||
await add.click()
|
|
||||||
await expect(rows(page)).toHaveCount(5)
|
|
||||||
return page.getByRole("textbox").last()
|
|
||||||
}
|
|
||||||
|
|
||||||
test("searches every recent project, not just the five most recent", async ({ page }) => {
|
|
||||||
const search = await openProjectDialog(page)
|
|
||||||
await expect(row(page, OUTSIDE_CAP)).toHaveCount(0)
|
|
||||||
|
|
||||||
await search.fill("foxtrot")
|
|
||||||
|
|
||||||
await expect(row(page, OUTSIDE_CAP)).toHaveCount(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("still caps the idle recent list at five projects", async ({ page }) => {
|
|
||||||
await openProjectDialog(page)
|
|
||||||
|
|
||||||
await expect(row(page, NAMES[4])).toHaveCount(1)
|
|
||||||
await expect(row(page, OUTSIDE_CAP)).toHaveCount(0)
|
|
||||||
})
|
|
||||||
@@ -32,10 +32,6 @@ test("preserves the draft when a populated command menu triggers a built-in", as
|
|||||||
],
|
],
|
||||||
pageMessages: () => ({ items: [] }),
|
pageMessages: () => ({ items: [] }),
|
||||||
})
|
})
|
||||||
await page.addInitScript(() => {
|
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
|
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
|
||||||
const composer = page.locator('[data-component="prompt-input-v2"]')
|
const composer = page.locator('[data-component="prompt-input-v2"]')
|
||||||
const input = composer.locator('[data-component="prompt-input"]')
|
const input = composer.locator('[data-component="prompt-input"]')
|
||||||
|
|||||||
@@ -49,10 +49,6 @@ test("shows the V2 thinking level control while relevant", async ({ page }) => {
|
|||||||
],
|
],
|
||||||
pageMessages: () => ({ items: [] }),
|
pageMessages: () => ({ items: [] }),
|
||||||
})
|
})
|
||||||
await page.addInitScript(() => {
|
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
|
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
|
||||||
const composer = page.locator('[data-component="prompt-input-v2"]')
|
const composer = page.locator('[data-component="prompt-input-v2"]')
|
||||||
const input = composer.locator('[data-component="prompt-input"]')
|
const input = composer.locator('[data-component="prompt-input"]')
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ type PermissionResponse = {
|
|||||||
async function configureServers(page: Page, tabs: { type: "session"; server: string; sessionId: string }[] = []) {
|
async function configureServers(page: Page, tabs: { type: "session"; server: string; sessionId: string }[] = []) {
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ serverB, tabs }) => {
|
({ serverB, tabs }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] }))
|
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] }))
|
||||||
localStorage.setItem("opencode.window.browser.dat:tabs", JSON.stringify(tabs))
|
localStorage.setItem("opencode.window.browser.dat:tabs", JSON.stringify(tabs))
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ test("tab busy indicator reflects the tab server's own session status", async ({
|
|||||||
await mockServers(page)
|
await mockServers(page)
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ serverA, serverB, sessionA, sessionB }) => {
|
({ serverA, serverB, sessionA, sessionB }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] }))
|
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ list: [serverB] }))
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.window.browser.dat:tabs",
|
"opencode.window.browser.dat:tabs",
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import { expect, test } from "@playwright/test"
|
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
|
||||||
|
|
||||||
const draftID = "draft_removed_layout_preference"
|
|
||||||
const directory = "C:/OpenCode/RemovedLayoutPreference"
|
|
||||||
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
|
|
||||||
|
|
||||||
test("ignores persisted old layout preferences when opening drafts", async ({ page }) => {
|
|
||||||
await mockOpenCodeServer(page, {
|
|
||||||
directory,
|
|
||||||
project: {
|
|
||||||
id: "proj_removed_layout_preference",
|
|
||||||
worktree: directory,
|
|
||||||
vcs: "git",
|
|
||||||
name: "removed-layout-preference",
|
|
||||||
time: { created: 1700000000000, updated: 1700000000000 },
|
|
||||||
sandboxes: [],
|
|
||||||
},
|
|
||||||
provider: { all: [], connected: [], default: {} },
|
|
||||||
sessions: [],
|
|
||||||
pageMessages: () => ({ items: [] }),
|
|
||||||
})
|
|
||||||
await page.addInitScript(
|
|
||||||
({ directory, draftID, server }) => {
|
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: false } }))
|
|
||||||
localStorage.setItem(
|
|
||||||
"opencode.window.browser.dat:tabs",
|
|
||||||
JSON.stringify([{ type: "draft", draftID, server, directory }]),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{ directory, draftID, server },
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.goto(`/new-session?draftId=${draftID}`)
|
|
||||||
|
|
||||||
await expect(page).toHaveURL(`/new-session?draftId=${draftID}`)
|
|
||||||
await expect(page.locator("body")).toHaveAttribute("data-new-layout", "")
|
|
||||||
await expect(page.getByRole("textbox", { name: "Prompt" })).toBeVisible()
|
|
||||||
})
|
|
||||||
@@ -26,9 +26,6 @@ test("clicking an image file in the v2 review pane does not blank the panel", as
|
|||||||
|
|
||||||
async function openReview(page: Page) {
|
async function openReview(page: Page) {
|
||||||
await page.setViewportSize({ width: 960, height: 900 })
|
await page.setViewportSize({ width: 960, height: 900 })
|
||||||
await page.addInitScript(() => {
|
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
})
|
|
||||||
await mockOpenCodeServer(page, {
|
await mockOpenCodeServer(page, {
|
||||||
directory,
|
directory,
|
||||||
project: {
|
project: {
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ test("opens and searches project files inline", async ({ page }) => {
|
|||||||
})
|
})
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ directory, server, sessionID }) => {
|
({ directory, server, sessionID }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.global.dat:server",
|
"opencode.global.dat:server",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ async function setup(page: Page) {
|
|||||||
)
|
)
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ directory, server, sessions }) => {
|
({ directory, server, sessions }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.global.dat:server",
|
"opencode.global.dat:server",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ async function setup(page: Page) {
|
|||||||
|
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ directory, server, sessions }) => {
|
({ directory, server, sessions }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.global.dat:server",
|
"opencode.global.dat:server",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ test("keeps the review tree and terminal sized when both panels are open", async
|
|||||||
)
|
)
|
||||||
await page.routeWebSocket("**/api/pty/pty_review_terminal/connect", () => undefined)
|
await page.routeWebSocket("**/api/pty/pty_review_terminal/connect", () => undefined)
|
||||||
await page.addInitScript(() => {
|
await page.addInitScript(() => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.global.dat:layout",
|
"opencode.global.dat:layout",
|
||||||
JSON.stringify({ review: { diffStyle: "split", panelOpened: true } }),
|
JSON.stringify({ review: { diffStyle: "split", panelOpened: true } }),
|
||||||
|
|||||||
@@ -233,7 +233,4 @@ async function mockServer(
|
|||||||
forms: requests.forms,
|
forms: requests.forms,
|
||||||
sessionStatus: requests.sessionStatus,
|
sessionStatus: requests.sessionStatus,
|
||||||
})
|
})
|
||||||
await page.addInitScript(() => {
|
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,15 +119,11 @@ test.describe("regression: session timeline local row state", () => {
|
|||||||
await wrapper.locator('[data-slot="collapsible-trigger"]').first().click()
|
await wrapper.locator('[data-slot="collapsible-trigger"]').first().click()
|
||||||
await expectExpanded(wrapper, false)
|
await expectExpanded(wrapper, false)
|
||||||
|
|
||||||
events.push({
|
events.push(...streamedTextEvents())
|
||||||
directory,
|
|
||||||
payload: {
|
|
||||||
type: "message.part.updated",
|
|
||||||
properties: { part: streamedTextPart },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(page.locator(`[data-timeline-part-id="${textPartID}"]`).first()).toBeVisible({ timeout: 10_000 })
|
await expect(page.locator(`[data-timeline-part-id="${assistantMessageID}:text:0"]`).first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
expect(await readToolState(page)).toEqual({
|
expect(await readToolState(page)).toEqual({
|
||||||
expanded: false,
|
expanded: false,
|
||||||
@@ -151,15 +147,11 @@ test.describe("regression: session timeline local row state", () => {
|
|||||||
await expectAppVisible(file)
|
await expectAppVisible(file)
|
||||||
await markDiffProbe(page)
|
await markDiffProbe(page)
|
||||||
|
|
||||||
events.push({
|
events.push(...streamedTextEvents())
|
||||||
directory,
|
|
||||||
payload: {
|
|
||||||
type: "message.part.updated",
|
|
||||||
properties: { part: streamedTextPart },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(page.locator(`[data-timeline-part-id="${textPartID}"]`).first()).toBeVisible({ timeout: 10_000 })
|
await expect(page.locator(`[data-timeline-part-id="${assistantMessageID}:text:0"]`).first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
const siblingProbe = await readDiffProbe(page)
|
const siblingProbe = await readDiffProbe(page)
|
||||||
expect(siblingProbe).toEqual({
|
expect(siblingProbe).toEqual({
|
||||||
fileMarker: "before",
|
fileMarker: "before",
|
||||||
@@ -171,13 +163,7 @@ test.describe("regression: session timeline local row state", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await markDiffProbe(page)
|
await markDiffProbe(page)
|
||||||
events.push({
|
events.push(...toolEvents(editPartWithAdditions(2)))
|
||||||
directory,
|
|
||||||
payload: {
|
|
||||||
type: "message.part.updated",
|
|
||||||
properties: { part: editPartWithAdditions(2) },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(wrapper.locator('[data-slot="diff-changes-additions"]').filter({ hasText: "+2" }).first()).toBeVisible(
|
await expect(wrapper.locator('[data-slot="diff-changes-additions"]').filter({ hasText: "+2" }).first()).toBeVisible(
|
||||||
{ timeout: 10_000 },
|
{ timeout: 10_000 },
|
||||||
@@ -293,7 +279,7 @@ async function readToolState(page: Page) {
|
|||||||
row: element.closest("[data-timeline-row]")?.getAttribute("data-timeline-row"),
|
row: element.closest("[data-timeline-row]")?.getAttribute("data-timeline-row"),
|
||||||
streamedTextVisible: !!document.querySelector(`[data-timeline-part-id="${textPartID}"]`),
|
streamedTextVisible: !!document.querySelector(`[data-timeline-part-id="${textPartID}"]`),
|
||||||
}),
|
}),
|
||||||
textPartID,
|
`${assistantMessageID}:text:0`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,6 +383,39 @@ async function mockServer(page: Page, events: EventPayload[], messages = [userMe
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function streamedTextEvents(): EventPayload[] {
|
||||||
|
const data = { sessionID, assistantMessageID, ordinal: 0 }
|
||||||
|
return [
|
||||||
|
{ directory, payload: { type: "session.text.started", properties: data } },
|
||||||
|
{ directory, payload: { type: "session.text.ended", properties: { ...data, text: streamedTextPart.text } } },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function toolEvents(part: typeof editPart): EventPayload[] {
|
||||||
|
const data = { sessionID, assistantMessageID, id: part.callID }
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
directory,
|
||||||
|
payload: {
|
||||||
|
type: "session.tool.called",
|
||||||
|
properties: { ...data, input: part.state.input, executed: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
directory,
|
||||||
|
payload: {
|
||||||
|
type: "session.tool.success",
|
||||||
|
properties: {
|
||||||
|
...data,
|
||||||
|
content: [{ type: "text", text: part.state.output }],
|
||||||
|
metadata: part.state.metadata,
|
||||||
|
executed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
function project() {
|
function project() {
|
||||||
return {
|
return {
|
||||||
id: projectID,
|
id: projectID,
|
||||||
|
|||||||
@@ -75,12 +75,7 @@ test.describe("regression: session timeline context group resize", () => {
|
|||||||
})
|
})
|
||||||
await startVisualProbe(page, regions)
|
await startVisualProbe(page, regions)
|
||||||
for (const [index, delay] of [120, 350, 80, 500].entries()) {
|
for (const [index, delay] of [120, 350, 80, 500].entries()) {
|
||||||
events.push({
|
const part = contextTool(
|
||||||
directory,
|
|
||||||
payload: {
|
|
||||||
type: "message.part.updated",
|
|
||||||
properties: {
|
|
||||||
part: contextTool(
|
|
||||||
contextIDs[index]!,
|
contextIDs[index]!,
|
||||||
id("msg_assistant", 10),
|
id("msg_assistant", 10),
|
||||||
["read", "glob", "grep", "list"][index]!,
|
["read", "glob", "grep", "list"][index]!,
|
||||||
@@ -90,10 +85,36 @@ test.describe("regression: session timeline context group resize", () => {
|
|||||||
{ path: directory, pattern: "Explored" },
|
{ path: directory, pattern: "Explored" },
|
||||||
{ path: "src" },
|
{ path: "src" },
|
||||||
][index]!,
|
][index]!,
|
||||||
),
|
)
|
||||||
|
events.push(
|
||||||
|
{
|
||||||
|
directory,
|
||||||
|
payload: {
|
||||||
|
type: "session.tool.called",
|
||||||
|
properties: {
|
||||||
|
sessionID,
|
||||||
|
assistantMessageID: part.messageID,
|
||||||
|
id: part.callID,
|
||||||
|
input: part.state.input,
|
||||||
|
executed: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
|
directory,
|
||||||
|
payload: {
|
||||||
|
type: "session.tool.success",
|
||||||
|
properties: {
|
||||||
|
sessionID,
|
||||||
|
assistantMessageID: part.messageID,
|
||||||
|
id: part.callID,
|
||||||
|
content: [{ type: "text", text: part.state.output }],
|
||||||
|
metadata: part.state.metadata,
|
||||||
|
executed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
await page.waitForTimeout(delay)
|
await page.waitForTimeout(delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,16 +95,16 @@ test("moves busy through retry and recovery to final idle content", async ({ pag
|
|||||||
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
|
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
|
||||||
await expect(page.locator('[data-timeline-row="DiffSummary"]')).toHaveCount(0)
|
await expect(page.locator('[data-timeline-row="DiffSummary"]')).toHaveCount(0)
|
||||||
await timeline.send(status("retry"), 180)
|
await timeline.send(status("retry"), 180)
|
||||||
await expect(page.locator('[data-timeline-row="Retry"]')).toBeVisible()
|
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
|
||||||
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
|
|
||||||
await timeline.send(status("busy", 2), 180)
|
await timeline.send(status("busy", 2), 180)
|
||||||
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
|
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
|
||||||
await timeline.send(partUpdated(textPart("prt_recovered", "Recovered response")), 140)
|
await timeline.send(partUpdated(textPart("prt_recovered", "Recovered response")), 140)
|
||||||
await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 100)
|
await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 100)
|
||||||
await timeline.send(status("idle"), 350)
|
await timeline.send(status("idle"), 350)
|
||||||
await expect(page.locator('[data-timeline-row="Retry"]')).toHaveCount(0)
|
|
||||||
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
|
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
|
||||||
await expect(page.locator('[data-timeline-part-id="prt_recovered"]')).toContainText("Recovered response")
|
await expect(page.locator('[data-timeline-part-id="msg_1001_timeline_assistant:text:0"]')).toContainText(
|
||||||
|
"Recovered response",
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
function lines(count: number) {
|
function lines(count: number) {
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import { expect, test } from "@playwright/test"
|
import { expect, test } from "@playwright/test"
|
||||||
import type { SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client/promise"
|
import type { SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||||
import { session, sessionID, setupTimeline } from "../performance/timeline-stability/fixture"
|
import { event, session, sessionID, setupTimeline } from "../performance/timeline-stability/fixture"
|
||||||
|
|
||||||
const user = { id: "msg_user", type: "user", text: "Run it", time: { created: 1 } } satisfies SessionMessageInfo
|
const user = { id: "msg_user", type: "user", text: "Run it", time: { created: 1 } } satisfies SessionMessageInfo
|
||||||
|
|
||||||
const assistant = (completed: boolean, tool = false, childID?: string): SessionMessageAssistant => ({
|
const assistant = (
|
||||||
|
completed: boolean,
|
||||||
|
tool = false,
|
||||||
|
childID?: string,
|
||||||
|
background = false,
|
||||||
|
): SessionMessageAssistant => ({
|
||||||
id: "msg_assistant",
|
id: "msg_assistant",
|
||||||
type: "assistant",
|
type: "assistant",
|
||||||
agent: "build",
|
agent: "build",
|
||||||
@@ -15,7 +20,11 @@ const assistant = (completed: boolean, tool = false, childID?: string): SessionM
|
|||||||
type: "tool",
|
type: "tool",
|
||||||
id: "call_subagent",
|
id: "call_subagent",
|
||||||
name: "subagent",
|
name: "subagent",
|
||||||
state: { status: "running", input: {}, metadata: childID ? { sessionID: childID } : {} },
|
state: {
|
||||||
|
status: "running",
|
||||||
|
input: { description: "Inspect code", ...(background ? { background: true } : {}) },
|
||||||
|
metadata: { status: "running", ...(childID ? { sessionID: childID } : {}) },
|
||||||
|
},
|
||||||
time: { created: 2 },
|
time: { created: 2 },
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -59,12 +68,17 @@ test("renders current protocol notices in CLI order", async ({ page }) => {
|
|||||||
await expect(notices.nth(1)).toContainText("explore finished · Search code")
|
await expect(notices.nth(1)).toContainText("explore finished · Search code")
|
||||||
await expect(notices.nth(2)).toContainText("Continuing after restart")
|
await expect(notices.nth(2)).toContainText("Continuing after restart")
|
||||||
await expect(notices.nth(3)).toContainText("Skill · Review")
|
await expect(notices.nth(3)).toContainText("Skill · Review")
|
||||||
|
await expect(notices).toHaveClass([/text-text-weak/, /text-text-weak/, /text-text-weak/, /text-text-weak/])
|
||||||
|
await expect(notices.locator(".text-text-strong")).toHaveCount(0)
|
||||||
expect(ownerWarnings).toEqual([])
|
expect(ownerWarnings).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("moves blocking work to the background with Ctrl+B", async ({ page }) => {
|
test("moves blocking work to the background with Ctrl+B", async ({ page }) => {
|
||||||
await setupTimeline(page, { currentMessages: [user, assistant(false, true)] })
|
await setupTimeline(page, { currentMessages: [user, assistant(false, true)] })
|
||||||
await expect(page.locator('[data-component="task-tool-card"]')).toBeVisible()
|
const card = page.locator('[data-component="task-tool-card"]')
|
||||||
|
await expect(card).toBeVisible()
|
||||||
|
await expect(card).toContainText("Inspect code")
|
||||||
|
await expect(card).not.toContainText("(background)")
|
||||||
await expect(page.getByText("Called `subagent`", { exact: false })).toHaveCount(0)
|
await expect(page.getByText("Called `subagent`", { exact: false })).toHaveCount(0)
|
||||||
await expect(page.locator('[data-component="background-tool-control"]')).toHaveCount(0)
|
await expect(page.locator('[data-component="background-tool-control"]')).toHaveCount(0)
|
||||||
await expect(page.locator('[data-action="session-background-toggle"]')).toContainText("Move 1 subagent to background")
|
await expect(page.locator('[data-action="session-background-toggle"]')).toContainText("Move 1 subagent to background")
|
||||||
@@ -77,6 +91,11 @@ test("moves blocking work to the background with Ctrl+B", async ({ page }) => {
|
|||||||
await request
|
await request
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("waits for completion before labeling requested background work", async ({ page }) => {
|
||||||
|
await setupTimeline(page, { currentMessages: [user, assistant(false, true, undefined, true)] })
|
||||||
|
await expect(page.locator('[data-component="task-tool-card"]')).not.toContainText("(background)")
|
||||||
|
})
|
||||||
|
|
||||||
test("navigates from a running subagent card and hides background controls in the child", async ({ page }) => {
|
test("navigates from a running subagent card and hides background controls in the child", async ({ page }) => {
|
||||||
const childID = "ses_running_child"
|
const childID = "ses_running_child"
|
||||||
await setupTimeline(page, {
|
await setupTimeline(page, {
|
||||||
@@ -105,7 +124,7 @@ test("shows a badge for active background work", async ({ page }) => {
|
|||||||
test("separates blocking and already-backgrounded work into two rows", async ({ page }) => {
|
test("separates blocking and already-backgrounded work into two rows", async ({ page }) => {
|
||||||
const backgroundID = "ses_background_existing"
|
const backgroundID = "ses_background_existing"
|
||||||
const blockingID = "ses_background_blocking"
|
const blockingID = "ses_background_blocking"
|
||||||
await setupTimeline(page, {
|
const timeline = await setupTimeline(page, {
|
||||||
currentMessages: [
|
currentMessages: [
|
||||||
user,
|
user,
|
||||||
{
|
{
|
||||||
@@ -175,9 +194,21 @@ test("separates blocking and already-backgrounded work into two rows", async ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const dock = page.locator('[data-component="session-background-dock"]')
|
const dock = page.locator('[data-component="session-background-dock"]')
|
||||||
|
const backgroundCard = page.locator('[data-timeline-part-id="call_backgrounded"]')
|
||||||
await expect(dock).toContainText("Move 1 subagent to background")
|
await expect(dock).toContainText("Move 1 subagent to background")
|
||||||
await expect(dock.getByText("Running 1 shell and 1 subagent in background", { exact: true })).toBeVisible()
|
await expect(dock.getByText("Running 1 shell and 1 subagent in background", { exact: true })).toBeVisible()
|
||||||
|
await expect(backgroundCard).toContainText("Background task (background)")
|
||||||
|
await expect(backgroundCard.locator('[data-component="session-progress-indicator-v2"]')).toBeVisible()
|
||||||
await expect(
|
await expect(
|
||||||
page.locator('[data-timeline-part-id="call_shell_backgrounded"] [data-component="text-shimmer"]'),
|
page.locator('[data-timeline-part-id="call_shell_backgrounded"] [data-component="text-shimmer"]'),
|
||||||
).toHaveAttribute("data-active", "true")
|
).toHaveAttribute("data-active", "true")
|
||||||
|
|
||||||
|
await timeline.transport.send({
|
||||||
|
id: "evt_background_succeeded",
|
||||||
|
created: Date.now(),
|
||||||
|
type: "session.execution.succeeded",
|
||||||
|
data: { sessionID: backgroundID },
|
||||||
|
} as never)
|
||||||
|
await expect(backgroundCard.locator('[data-component="session-progress-indicator-v2"]')).toHaveCount(0)
|
||||||
|
await expect(backgroundCard).toContainText("Background task (background)")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -146,10 +146,7 @@ test.describe("session timeline projection", () => {
|
|||||||
parentID: "msg_2000_diff_next_user",
|
parentID: "msg_2000_diff_next_user",
|
||||||
created: 1700000011000,
|
created: 1700000011000,
|
||||||
})
|
})
|
||||||
await setupTimeline(page, {
|
await setupTimeline(page, { messages: [user, assistantMessage(), nextUser, nextAssistant] })
|
||||||
messages: [user, assistantMessage(), nextUser, nextAssistant],
|
|
||||||
settings: { newLayoutDesigns: false },
|
|
||||||
})
|
|
||||||
const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") })
|
const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") })
|
||||||
await scroller.evaluate((element) => (element.scrollTop = 0))
|
await scroller.evaluate((element) => (element.scrollTop = 0))
|
||||||
|
|
||||||
@@ -159,7 +156,6 @@ test.describe("session timeline projection", () => {
|
|||||||
{ exact: true },
|
{ exact: true },
|
||||||
),
|
),
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await expect(page.locator('[data-timeline-row="CommentStrip"]')).toHaveCount(0)
|
|
||||||
await expect(page.locator('[data-timeline-row="DiffSummary"]')).toHaveCount(0)
|
await expect(page.locator('[data-timeline-row="DiffSummary"]')).toHaveCount(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -39,5 +39,7 @@ test("reducer-hardening: converges when idle arrives before final part and messa
|
|||||||
await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 250)
|
await timeline.send(messageUpdated(completedAssistantInfo(assistant.info)), 250)
|
||||||
|
|
||||||
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
|
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
|
||||||
await expect(page.locator(`[data-timeline-part-id="${textID}"]`)).toContainText("Final after early idle")
|
await expect(page.locator('[data-timeline-part-id="msg_1001_timeline_assistant:text:1"]')).toContainText(
|
||||||
|
"Final after early idle",
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ for (const deviceScaleFactor of [1.25, 1.5]) {
|
|||||||
const shellID = "prt_shell_outline"
|
const shellID = "prt_shell_outline"
|
||||||
const timeline = await setupTimeline(page, {
|
const timeline = await setupTimeline(page, {
|
||||||
messages: [userMessage(), assistantMessage([shell(shellID, "completed", "shell output")])],
|
messages: [userMessage(), assistantMessage([shell(shellID, "completed", "shell output")])],
|
||||||
settings: { newLayoutDesigns: true, shellToolPartsExpanded: true },
|
settings: { shellToolPartsExpanded: true },
|
||||||
reducedMotion: true,
|
reducedMotion: true,
|
||||||
deviceScaleFactor,
|
deviceScaleFactor,
|
||||||
})
|
})
|
||||||
@@ -82,7 +82,7 @@ test("keeps the patch card inside a fractionally short virtual row", async ({ pa
|
|||||||
toolPart(patchID, "apply_patch", "completed", { files: [file.filePath] }, { metadata: { files: [file] } }),
|
toolPart(patchID, "apply_patch", "completed", { files: [file.filePath] }, { metadata: { files: [file] } }),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
settings: { editToolPartsExpanded: true, newLayoutDesigns: true },
|
settings: { editToolPartsExpanded: true },
|
||||||
reducedMotion: true,
|
reducedMotion: true,
|
||||||
})
|
})
|
||||||
const part = page.locator(`[data-timeline-part-id="${patchID}"]`)
|
const part = page.locator(`[data-timeline-part-id="${patchID}"]`)
|
||||||
|
|||||||
@@ -83,6 +83,26 @@ test("labels all web search provider variants", async ({ page }) => {
|
|||||||
await expect(page.getByRole("button", { name: /^Web Search/ })).toBeVisible()
|
await expect(page.getByRole("button", { name: /^Web Search/ })).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("labels completed searches with result counts", async ({ page }) => {
|
||||||
|
const glob = "prt_glob_count"
|
||||||
|
const grep = "prt_grep_count"
|
||||||
|
await setupTimeline(page, {
|
||||||
|
messages: [
|
||||||
|
userMessage(),
|
||||||
|
assistantMessage([
|
||||||
|
toolPart(glob, "glob", "completed", { path: ".", pattern: "**/*.ts" }, { metadata: { count: 1 } }),
|
||||||
|
toolPart(grep, "grep", "completed", { path: ".", pattern: "value" }, { metadata: { matches: 12 } }),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const group = page.locator(`[data-timeline-part-ids="${glob},${grep}"]`)
|
||||||
|
await group.locator('[data-slot="collapsible-trigger"]').click()
|
||||||
|
const rows = group.locator('[data-component="tool-trigger"]')
|
||||||
|
await expect(rows.nth(0)).toContainText("(1 match)")
|
||||||
|
await expect(rows.nth(1)).toContainText("(12 matches)")
|
||||||
|
})
|
||||||
|
|
||||||
test("labels V2 read tools from their path input", async ({ page }) => {
|
test("labels V2 read tools from their path input", async ({ page }) => {
|
||||||
const id = "prt_read_path"
|
const id = "prt_read_path"
|
||||||
await setupTimeline(page, {
|
await setupTimeline(page, {
|
||||||
@@ -115,6 +135,12 @@ test("labels V2 skill tools from IDs and result metadata", async ({ page }) => {
|
|||||||
"aria-label",
|
"aria-label",
|
||||||
"OpenCode",
|
"OpenCode",
|
||||||
)
|
)
|
||||||
|
for (const id of [pending, completed]) {
|
||||||
|
const skill = page.locator(`[data-timeline-part-id="${id}"]`)
|
||||||
|
await expect(skill.locator('[data-slot="skill-tool-label"]')).toHaveText("Skill")
|
||||||
|
await expect(skill.locator('[data-slot="skill-tool-separator"]')).toHaveText("·")
|
||||||
|
await expect(skill.locator('use[href="#opencode-icon-post-skill"]')).toBeVisible()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function questionInput() {
|
function questionInput() {
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { partUpdated, setupTimeline, textPart } from "../performance/timeline-stability/fixture"
|
import { partUpdated, setupTimeline, textPart } from "../performance/timeline-stability/fixture"
|
||||||
|
|
||||||
|
const streamedTextID = (ordinal: number) => `msg_1001_timeline_assistant:text:${ordinal}`
|
||||||
|
|
||||||
test("keeps one connection open while delivering multiple events", async ({ page }) => {
|
test("keeps one connection open while delivering multiple events", async ({ page }) => {
|
||||||
const timeline = await setupTimeline(page)
|
const timeline = await setupTimeline(page)
|
||||||
|
|
||||||
const first = await timeline.transport.send(partUpdated(textPart("prt_transport_first", "first event")))
|
const first = await timeline.transport.send(partUpdated(textPart("prt_transport_first", "first event")))
|
||||||
const second = await timeline.transport.send(partUpdated(textPart("prt_transport_second", "second event")))
|
const second = await timeline.transport.send(partUpdated(textPart("prt_transport_second", "second event")))
|
||||||
|
|
||||||
await timeline.waitForPart("prt_transport_first")
|
await timeline.waitForPart(streamedTextID(0))
|
||||||
await timeline.waitForPart("prt_transport_second")
|
await timeline.waitForPart(streamedTextID(1))
|
||||||
expect(first.connectionID).toBe(second.connectionID)
|
expect(first.connectionID).toBe(second.connectionID)
|
||||||
await expect.poll(async () => (await timeline.transport.connections()).length).toBe(1)
|
await expect.poll(async () => (await timeline.transport.connections()).length).toBe(1)
|
||||||
expect(await timeline.transport.acknowledgements()).toHaveLength(2)
|
expect(await timeline.transport.acknowledgements()).toHaveLength(2)
|
||||||
@@ -21,8 +23,8 @@ test("delivers a burst from one stream chunk", async ({ page }) => {
|
|||||||
partUpdated(textPart("prt_transport_burst_b", "burst b")),
|
partUpdated(textPart("prt_transport_burst_b", "burst b")),
|
||||||
])
|
])
|
||||||
|
|
||||||
await timeline.waitForPart("prt_transport_burst_a")
|
await timeline.waitForPart(streamedTextID(0))
|
||||||
await timeline.waitForPart("prt_transport_burst_b")
|
await timeline.waitForPart(streamedTextID(1))
|
||||||
expect(acknowledgements.map((item) => item.chunkCount)).toEqual([1, 1])
|
expect(acknowledgements.map((item) => item.chunkCount)).toEqual([1, 1])
|
||||||
expect(new Set(acknowledgements.map((item) => item.deliveryID)).size).toBe(2)
|
expect(new Set(acknowledgements.map((item) => item.deliveryID)).size).toBe(2)
|
||||||
})
|
})
|
||||||
@@ -36,8 +38,8 @@ test("parses split JSON and a split multibyte code point", async ({ page }) => {
|
|||||||
|
|
||||||
const acknowledgement = await timeline.transport.split(payload, [9, multibyte + 1, multibyte + 2])
|
const acknowledgement = await timeline.transport.split(payload, [9, multibyte + 1, multibyte + 2])
|
||||||
|
|
||||||
await timeline.waitForPart("prt_transport_split")
|
await timeline.waitForPart(streamedTextID(0))
|
||||||
await expect(page.locator('[data-timeline-part-id="prt_transport_split"]')).toContainText(
|
await expect(page.locator(`[data-timeline-part-id="${streamedTextID(0)}"]`)).toContainText(
|
||||||
"split snowman \u2603\u2603\u2603",
|
"split snowman \u2603\u2603\u2603",
|
||||||
)
|
)
|
||||||
expect(acknowledgement.chunkCount).toBe(4)
|
expect(acknowledgement.chunkCount).toBe(4)
|
||||||
@@ -45,10 +47,9 @@ test("parses split JSON and a split multibyte code point", async ({ page }) => {
|
|||||||
|
|
||||||
test("delivers server heartbeat without mutating the timeline", async ({ page }) => {
|
test("delivers server heartbeat without mutating the timeline", async ({ page }) => {
|
||||||
const timeline = await setupTimeline(page)
|
const timeline = await setupTimeline(page)
|
||||||
const partID = "prt_transport_heartbeat_sentinel"
|
const sentinel = await timeline.transport.send(partUpdated(textPart("prt_transport_heartbeat_sentinel", "heartbeat sentinel")))
|
||||||
const sentinel = await timeline.transport.send(partUpdated(textPart(partID, "heartbeat sentinel")))
|
await timeline.waitForPart(streamedTextID(0))
|
||||||
await timeline.waitForPart(partID)
|
await expect(page.locator(`[data-timeline-part-id="${streamedTextID(0)}"] [data-component="markdown"]`)).toHaveAttribute(
|
||||||
await expect(page.locator(`[data-timeline-part-id="${partID}"] [data-component="markdown"]`)).toHaveAttribute(
|
|
||||||
"data-markdown-ready",
|
"data-markdown-ready",
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
@@ -68,7 +69,7 @@ test("reconnects after a clean close", async ({ page }) => {
|
|||||||
const second = await timeline.transport.waitForConnection({ after: first.id })
|
const second = await timeline.transport.waitForConnection({ after: first.id })
|
||||||
await timeline.transport.send(partUpdated(textPart("prt_transport_close", "after close")))
|
await timeline.transport.send(partUpdated(textPart("prt_transport_close", "after close")))
|
||||||
|
|
||||||
await timeline.waitForPart("prt_transport_close")
|
await timeline.waitForPart(streamedTextID(0))
|
||||||
expect(second.id).toBeGreaterThan(first.id)
|
expect(second.id).toBeGreaterThan(first.id)
|
||||||
expect((await timeline.transport.connections())[0]?.endedBy).toBe("close")
|
expect((await timeline.transport.connections())[0]?.endedBy).toBe("close")
|
||||||
})
|
})
|
||||||
@@ -81,7 +82,7 @@ test("reconnects after a stream error", async ({ page }) => {
|
|||||||
const second = await timeline.transport.waitForConnection({ after: first.id })
|
const second = await timeline.transport.waitForConnection({ after: first.id })
|
||||||
await timeline.transport.send(partUpdated(textPart("prt_transport_error", "after error")))
|
await timeline.transport.send(partUpdated(textPart("prt_transport_error", "after error")))
|
||||||
|
|
||||||
await timeline.waitForPart("prt_transport_error")
|
await timeline.waitForPart(streamedTextID(0))
|
||||||
await expect.poll(async () => (await timeline.transport.connections()).length).toBe(2)
|
await expect.poll(async () => (await timeline.transport.connections()).length).toBe(2)
|
||||||
expect(second.id).toBeGreaterThan(first.id)
|
expect(second.id).toBeGreaterThan(first.id)
|
||||||
expect((await timeline.transport.connections())[0]?.endedBy).toBe("error")
|
expect((await timeline.transport.connections())[0]?.endedBy).toBe("error")
|
||||||
@@ -92,7 +93,7 @@ test("does not request replay when reconnecting the volatile V2 event stream", a
|
|||||||
const first = await timeline.transport.send(partUpdated(textPart("prt_transport_id", "event with id")), {
|
const first = await timeline.transport.send(partUpdated(textPart("prt_transport_id", "event with id")), {
|
||||||
id: "timeline-event-7",
|
id: "timeline-event-7",
|
||||||
})
|
})
|
||||||
await timeline.waitForPart("prt_transport_id")
|
await timeline.waitForPart(streamedTextID(0))
|
||||||
|
|
||||||
await timeline.transport.error("retry with event id")
|
await timeline.transport.error("retry with event id")
|
||||||
const connection = await timeline.transport.waitForConnection({ after: first.connectionID })
|
const connection = await timeline.transport.waitForConnection({ after: first.connectionID })
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
|
||||||
import { expect, test, type Page } from "@playwright/test"
|
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
|
||||||
import { expectSessionTitle } from "../utils/waits"
|
|
||||||
|
|
||||||
const directory = "C:/OpenCode/TodoDockNavigation"
|
|
||||||
const projectID = "proj_todo_dock_navigation"
|
|
||||||
const sourceID = "ses_todo_dock_source"
|
|
||||||
const otherID = "ses_todo_dock_other"
|
|
||||||
const sourceTitle = "Todo dock animation"
|
|
||||||
const otherTitle = "Separate session"
|
|
||||||
|
|
||||||
const activeTodos = [
|
|
||||||
{ id: "todo-1", content: "Receive todos in the active session", status: "completed", priority: "high" },
|
|
||||||
{ id: "todo-2", content: "Keep the dock visible across tabs", status: "completed", priority: "high" },
|
|
||||||
{ id: "todo-3", content: "Close after the final todo", status: "in_progress", priority: "high" },
|
|
||||||
]
|
|
||||||
|
|
||||||
type EventPayload = {
|
|
||||||
directory: string
|
|
||||||
payload: Record<string, unknown>
|
|
||||||
}
|
|
||||||
|
|
||||||
test.use({ viewport: { width: 1440, height: 900 } })
|
|
||||||
|
|
||||||
test("animates todo opening without replaying it across session tabs", async ({ page }) => {
|
|
||||||
test.setTimeout(90_000)
|
|
||||||
const events: EventPayload[] = []
|
|
||||||
const todos: Record<string, typeof activeTodos> = { [sourceID]: [], [otherID]: [] }
|
|
||||||
const sessionStatus: Record<string, { type: "busy" | "idle" }> = {}
|
|
||||||
|
|
||||||
await mockOpenCodeServer(page, {
|
|
||||||
directory,
|
|
||||||
project: {
|
|
||||||
id: projectID,
|
|
||||||
worktree: directory,
|
|
||||||
vcs: "git",
|
|
||||||
name: "todo-dock-navigation",
|
|
||||||
time: { created: 1700000000000, updated: 1700000000000 },
|
|
||||||
sandboxes: [],
|
|
||||||
},
|
|
||||||
provider: {
|
|
||||||
all: [
|
|
||||||
{
|
|
||||||
id: "opencode",
|
|
||||||
name: "OpenCode",
|
|
||||||
models: {
|
|
||||||
"claude-opus-4-6": {
|
|
||||||
id: "claude-opus-4-6",
|
|
||||||
name: "Claude Opus 4.6",
|
|
||||||
limit: { context: 200_000 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
connected: ["opencode"],
|
|
||||||
default: { providerID: "opencode", modelID: "claude-opus-4-6" },
|
|
||||||
},
|
|
||||||
sessions: [session(sourceID, sourceTitle, 1700000000000), session(otherID, otherTitle, 1700000001000)],
|
|
||||||
pageMessages: () => ({ items: [] }),
|
|
||||||
events: () => events.splice(0, 1),
|
|
||||||
eventRetry: 16,
|
|
||||||
sessionStatus: () => sessionStatus,
|
|
||||||
todos: (sessionID) => todos[sessionID] ?? [],
|
|
||||||
})
|
|
||||||
await configurePage(page)
|
|
||||||
|
|
||||||
await page.goto(sessionHref(sourceID))
|
|
||||||
await expectSessionTitle(page, sourceTitle)
|
|
||||||
const dock = page.locator('[data-component="session-todo-dock"]')
|
|
||||||
await expect(dock).toHaveCount(0)
|
|
||||||
|
|
||||||
sessionStatus[sourceID] = { type: "busy" }
|
|
||||||
events.push(statusEvent(sourceID, "busy"))
|
|
||||||
await expect(page.getByRole("button", { name: "Stop" })).toBeVisible()
|
|
||||||
|
|
||||||
await page.waitForTimeout(700)
|
|
||||||
const opening = sampleDock(page, 1_000)
|
|
||||||
todos[sourceID] = activeTodos
|
|
||||||
events.push(todoEvent(sourceID, activeTodos))
|
|
||||||
await expect(dock).toBeVisible()
|
|
||||||
await expect(dock.locator('[data-state="in_progress"]')).toHaveCount(1)
|
|
||||||
expect((await opening).some((sample) => sample.opacity > 0.05 && sample.opacity < 0.95)).toBe(true)
|
|
||||||
|
|
||||||
await switchSession(page, otherID, otherTitle)
|
|
||||||
await expect(dock).toHaveCount(0)
|
|
||||||
|
|
||||||
await switchSession(page, sourceID, sourceTitle)
|
|
||||||
await expect(dock).toHaveCount(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
function session(id: string, title: string, created: number) {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
slug: id,
|
|
||||||
projectID,
|
|
||||||
directory,
|
|
||||||
title,
|
|
||||||
version: "dev",
|
|
||||||
time: { created, updated: created },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusEvent(sessionID: string, type: "busy" | "idle"): EventPayload {
|
|
||||||
return {
|
|
||||||
directory,
|
|
||||||
payload: { type: "session.status", properties: { sessionID, status: { type } } },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function todoEvent(sessionID: string, next: typeof activeTodos): EventPayload {
|
|
||||||
return {
|
|
||||||
directory,
|
|
||||||
payload: { type: "todo.updated", properties: { sessionID, todos: next } },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function configurePage(page: Page) {
|
|
||||||
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
|
|
||||||
await page.addInitScript(
|
|
||||||
({ directory, dirBase64, server, sessionIDs }) => {
|
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem(
|
|
||||||
"opencode.global.dat:server",
|
|
||||||
JSON.stringify({
|
|
||||||
projects: { local: [{ worktree: directory, expanded: true }] },
|
|
||||||
lastProject: { local: directory },
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
localStorage.setItem(
|
|
||||||
"opencode.window.browser.dat:tabs",
|
|
||||||
JSON.stringify(sessionIDs.map((sessionId) => ({ type: "session", server, dirBase64, sessionId }))),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{ directory, dirBase64: base64Encode(directory), server, sessionIDs: [sourceID, otherID] },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function sessionHref(sessionID: string) {
|
|
||||||
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
|
|
||||||
return `/server/${base64Encode(server)}/session/${sessionID}`
|
|
||||||
}
|
|
||||||
|
|
||||||
async function switchSession(page: Page, sessionID: string, title: string) {
|
|
||||||
const href = sessionHref(sessionID)
|
|
||||||
const tab = page.locator(`[data-slot="titlebar-tabs"] a[href="${href}"]`).first()
|
|
||||||
await expect(tab).toBeVisible()
|
|
||||||
await tab.click()
|
|
||||||
await expectSessionTitle(page, title)
|
|
||||||
}
|
|
||||||
|
|
||||||
function sampleDock(page: Page, duration: number) {
|
|
||||||
return page.evaluate(async (duration) => {
|
|
||||||
const samples: { present: boolean; height: number; opacity: number }[] = []
|
|
||||||
const start = performance.now()
|
|
||||||
while (performance.now() - start < duration) {
|
|
||||||
const dock = document.querySelector<HTMLElement>('[data-component="session-todo-dock"]')
|
|
||||||
const clip = dock?.parentElement?.parentElement
|
|
||||||
const label = dock?.querySelector<HTMLElement>('[data-action="session-todo-toggle"] span[aria-label]')
|
|
||||||
samples.push({
|
|
||||||
present: !!dock,
|
|
||||||
height: clip?.getBoundingClientRect().height ?? 0,
|
|
||||||
opacity: label ? Number.parseFloat(getComputedStyle(label).opacity) : 0,
|
|
||||||
})
|
|
||||||
await new Promise(requestAnimationFrame)
|
|
||||||
}
|
|
||||||
return samples
|
|
||||||
}, duration)
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,7 @@ test("navigates to a subagent child session missing from the session list", asyn
|
|||||||
await expect(titlebarRight.getByRole("button", { name: "Toggle review" })).toHaveCount(1)
|
await expect(titlebarRight.getByRole("button", { name: "Toggle review" })).toHaveCount(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("keeps the parent visible while child lineage resolves", async ({ page }) => {
|
test("keeps the parent visible while the child session resolves", async ({ page }) => {
|
||||||
await setup(page)
|
await setup(page)
|
||||||
const requested = Promise.withResolvers<void>()
|
const requested = Promise.withResolvers<void>()
|
||||||
const release = Promise.withResolvers<void>()
|
const release = Promise.withResolvers<void>()
|
||||||
@@ -59,7 +59,7 @@ test("shows the not found fallback when the viewed session is deleted", async ({
|
|||||||
|
|
||||||
events.push({
|
events.push({
|
||||||
directory,
|
directory,
|
||||||
payload: { type: "session.deleted", properties: { info: childSession() } },
|
payload: { type: "session.deleted", properties: { sessionID: childID } },
|
||||||
})
|
})
|
||||||
|
|
||||||
await expect(page.getByText("This session cannot be found")).toBeVisible()
|
await expect(page.getByText("This session cannot be found")).toBeVisible()
|
||||||
@@ -207,7 +207,6 @@ async function configurePage(page: Page) {
|
|||||||
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
|
const server = `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ directory, server, sessionId }) => {
|
({ directory, server, sessionId }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.global.dat:server",
|
"opencode.global.dat:server",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ test("pressing mouse down on a tab navigates before mouse up", async ({ page })
|
|||||||
await mockServer(page)
|
await mockServer(page)
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ server, sessionA, sessionB }) => {
|
({ server, sessionA, sessionB }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.window.browser.dat:tabs",
|
"opencode.window.browser.dat:tabs",
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
@@ -46,7 +45,6 @@ test("keyboard navigation follows the visible tab order", async ({ page }) => {
|
|||||||
await mockServer(page)
|
await mockServer(page)
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ server, sessionA, unresolved, sessionC }) => {
|
({ server, sessionA, unresolved, sessionC }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.window.browser.dat:tabs",
|
"opencode.window.browser.dat:tabs",
|
||||||
JSON.stringify([
|
JSON.stringify([
|
||||||
|
|||||||
@@ -71,9 +71,6 @@ test.beforeEach(async ({ page }) => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
await page.routeWebSocket(new RegExp(`/api/pty/${ptyID}/connect`), () => undefined)
|
await page.routeWebSocket(new RegExp(`/api/pty/${ptyID}/connect`), () => undefined)
|
||||||
await page.addInitScript(() => {
|
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("routes typing to the composer unless the open terminal is focused", async ({ page }) => {
|
test("routes typing to the composer unless the open terminal is focused", async ({ page }) => {
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ async function setup(page: Page) {
|
|||||||
|
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ directory, server, sessions }) => {
|
({ directory, server, sessions }) => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.global.dat:server",
|
"opencode.global.dat:server",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ test("creates a session in a new project and selects its model", async ({ page }
|
|||||||
findFiles: () => ["NewProject"],
|
findFiles: () => ["NewProject"],
|
||||||
})
|
})
|
||||||
await page.addInitScript(() => {
|
await page.addInitScript(() => {
|
||||||
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
|
||||||
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ projects: { local: [] } }))
|
localStorage.setItem("opencode.global.dat:server", JSON.stringify({ projects: { local: [] } }))
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"opencode.global.dat:model",
|
"opencode.global.dat:model",
|
||||||
@@ -72,7 +71,12 @@ test("creates a session in a new project and selects its model", async ({ page }
|
|||||||
const addProject = page.locator('[data-action="home-add-project-row"]')
|
const addProject = page.locator('[data-action="home-add-project-row"]')
|
||||||
await expectAppVisible(addProject)
|
await expectAppVisible(addProject)
|
||||||
await addProject.click()
|
await addProject.click()
|
||||||
await page.locator("[data-directory-path]").click()
|
const directoryItem = page.getByRole("treeitem", { name: "NewProject" })
|
||||||
|
await expect(directoryItem).toBeVisible()
|
||||||
|
await directoryItem.click()
|
||||||
|
const selectFolder = page.getByRole("button", { name: "Select folder" })
|
||||||
|
await expect(selectFolder).toBeEnabled()
|
||||||
|
await selectFolder.click()
|
||||||
|
|
||||||
await page.locator('[data-action="home-new-session"]').click()
|
await page.locator('[data-action="home-new-session"]').click()
|
||||||
await expectAppVisible(page.locator('[data-component="prompt-input-v2"]'))
|
await expectAppVisible(page.locator('[data-component="prompt-input-v2"]'))
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export interface MockServerConfig {
|
|||||||
onMessage?: (input: { sessionID: string; messageID: string }) => void
|
onMessage?: (input: { sessionID: string; messageID: string }) => void
|
||||||
events?: () => unknown[]
|
events?: () => unknown[]
|
||||||
eventRetry?: number
|
eventRetry?: number
|
||||||
todos?: (sessionID: string) => unknown[]
|
|
||||||
permissions?: unknown[] | (() => unknown[])
|
permissions?: unknown[] | (() => unknown[])
|
||||||
questions?: unknown[] | (() => unknown[])
|
questions?: unknown[] | (() => unknown[])
|
||||||
forms?: unknown[] | (() => unknown[])
|
forms?: unknown[] | (() => unknown[])
|
||||||
@@ -303,6 +302,16 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
|
|||||||
currentPermission,
|
currentPermission,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
const sessionPermissions = path.match(/^\/api\/session\/([^/]+)\/permission$/)?.[1]
|
||||||
|
if (sessionPermissions)
|
||||||
|
return json(
|
||||||
|
route,
|
||||||
|
{
|
||||||
|
data: (typeof config.permissions === "function" ? config.permissions() : (config.permissions ?? []))
|
||||||
|
.map(currentPermission)
|
||||||
|
.filter((permission) => permission.sessionID === sessionPermissions),
|
||||||
|
},
|
||||||
|
)
|
||||||
if (path === "/api/question/request")
|
if (path === "/api/question/request")
|
||||||
return json(route, {
|
return json(route, {
|
||||||
location: location(config),
|
location: location(config),
|
||||||
@@ -466,8 +475,6 @@ export async function mockOpenCodeServer(page: Page, config: MockServerConfig) {
|
|||||||
return json(route, message)
|
return json(route, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
const todoMatch = path.match(/^\/session\/([^/]+)\/todo$/)
|
|
||||||
if (todoMatch) return json(route, config.todos?.(todoMatch[1]!) ?? [])
|
|
||||||
if (/^\/session\/[^/]+\/(children|diff)$/.test(path)) return json(route, [])
|
if (/^\/session\/[^/]+\/(children|diff)$/.test(path)) return json(route, [])
|
||||||
|
|
||||||
const currentMessagesMatch = path.match(/^\/api\/session\/([^/]+)\/message$/)
|
const currentMessagesMatch = path.match(/^\/api\/session\/([^/]+)\/message$/)
|
||||||
|
|||||||
@@ -57,11 +57,11 @@ type BrowserTransport = Window & {
|
|||||||
|
|
||||||
export async function installSseTransport<T>(
|
export async function installSseTransport<T>(
|
||||||
page: Page,
|
page: Page,
|
||||||
options: { server: string; retry?: number },
|
options: { server: string; retry?: number; existingTools?: readonly string[] },
|
||||||
): Promise<SseTransport<T>> {
|
): Promise<SseTransport<T>> {
|
||||||
const server = new URL(options.server).origin
|
const server = new URL(options.server).origin
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ server, retry }) => {
|
({ server, retry, existingTools }) => {
|
||||||
type Connection = SseConnectionRecord & { controller: ReadableStreamDefaultController<Uint8Array> }
|
type Connection = SseConnectionRecord & { controller: ReadableStreamDefaultController<Uint8Array> }
|
||||||
type ProbeWindow = Window & {
|
type ProbeWindow = Window & {
|
||||||
__visualStabilityProbe?: { startedAt: number; markers: { at: number; label: string }[] }
|
__visualStabilityProbe?: { startedAt: number; markers: { at: number; label: string }[] }
|
||||||
@@ -72,6 +72,9 @@ export async function installSseTransport<T>(
|
|||||||
const encoder = new TextEncoder()
|
const encoder = new TextEncoder()
|
||||||
let nextConnectionID = 0
|
let nextConnectionID = 0
|
||||||
let nextDeliveryID = 0
|
let nextDeliveryID = 0
|
||||||
|
const textOrdinals = new Map<string, number>()
|
||||||
|
const reasoningOrdinals = new Map<string, number>()
|
||||||
|
const startedTools = new Set(existingTools)
|
||||||
|
|
||||||
const current = () => connections.findLast((connection) => connection.endedAt === undefined)
|
const current = () => connections.findLast((connection) => connection.endedAt === undefined)
|
||||||
const chunks = (bytes: Uint8Array, cuts?: readonly number[]) => {
|
const chunks = (bytes: Uint8Array, cuts?: readonly number[]) => {
|
||||||
@@ -93,20 +96,169 @@ export async function installSseTransport<T>(
|
|||||||
eventOptions.retry === undefined ? "" : `retry: ${eventOptions.retry}\n`,
|
eventOptions.retry === undefined ? "" : `retry: ${eventOptions.retry}\n`,
|
||||||
`data: ${JSON.stringify(payload)}\n\n`,
|
`data: ${JSON.stringify(payload)}\n\n`,
|
||||||
].join("")
|
].join("")
|
||||||
const currentEvent = (input: unknown) => {
|
const currentEvents = (input: unknown) => {
|
||||||
if (!input || typeof input !== "object" || !("payload" in input)) return input
|
if (!input || typeof input !== "object" || !("payload" in input)) return [input]
|
||||||
const envelope = input as { directory?: string; payload?: unknown }
|
const envelope = input as { directory?: string; payload?: unknown }
|
||||||
if (!envelope.payload || typeof envelope.payload !== "object") return input
|
if (!envelope.payload || typeof envelope.payload !== "object") return [input]
|
||||||
const payload = envelope.payload as { id?: string; type?: string; properties?: unknown }
|
const payload = envelope.payload as { id?: string; type?: string; properties?: unknown }
|
||||||
if (!payload.type) return input
|
if (!payload.type) return [input]
|
||||||
return {
|
const properties = (payload.properties ?? {}) as Record<string, unknown>
|
||||||
id: payload.id ?? `evt_mock_${Date.now()}`,
|
const base = {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
type: payload.type,
|
location: envelope.directory && envelope.directory !== "global" ? { directory: envelope.directory } : undefined,
|
||||||
data: payload.properties ?? {},
|
|
||||||
location:
|
|
||||||
envelope.directory && envelope.directory !== "global" ? { directory: envelope.directory } : undefined,
|
|
||||||
}
|
}
|
||||||
|
const events = (items: { type: string; data: Record<string, unknown> }[]) =>
|
||||||
|
items.map((item, index) => ({
|
||||||
|
...base,
|
||||||
|
id: `${payload.id ?? `evt_mock_${Date.now()}`}${index ? `_${index}` : ""}`,
|
||||||
|
...item,
|
||||||
|
}))
|
||||||
|
if (payload.type === "session.status") {
|
||||||
|
const status = properties.status as { type?: string; attempt?: number; message?: string; next?: number }
|
||||||
|
if (status.type === "busy")
|
||||||
|
return events([{ type: "session.execution.started", data: { sessionID: properties.sessionID } }])
|
||||||
|
if (status.type === "retry")
|
||||||
|
return events([
|
||||||
|
{
|
||||||
|
type: "session.retry.scheduled",
|
||||||
|
data: {
|
||||||
|
sessionID: properties.sessionID,
|
||||||
|
assistantMessageID: "msg_1001_timeline_assistant",
|
||||||
|
attempt: status.attempt ?? 1,
|
||||||
|
at: status.next ?? Date.now(),
|
||||||
|
error: { type: "provider.error", message: status.message ?? "Retry scheduled" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
return events([{ type: "session.execution.succeeded", data: { sessionID: properties.sessionID } }])
|
||||||
|
}
|
||||||
|
if (payload.type === "message.updated") {
|
||||||
|
const info = properties.info as Record<string, unknown>
|
||||||
|
const time = info.time as { completed?: number } | undefined
|
||||||
|
if (info.role !== "assistant" || time?.completed === undefined) return []
|
||||||
|
const error = info.error as { name?: string; data?: { message?: string } } | undefined
|
||||||
|
if (error)
|
||||||
|
return events([
|
||||||
|
{
|
||||||
|
type: "session.step.failed",
|
||||||
|
data: {
|
||||||
|
sessionID: properties.sessionID,
|
||||||
|
assistantMessageID: info.id,
|
||||||
|
error: { type: error.name ?? "session.error", message: error.data?.message ?? "Session failed" },
|
||||||
|
cost: info.cost ?? 0,
|
||||||
|
tokens: info.tokens ?? { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
return events([
|
||||||
|
{
|
||||||
|
type: "session.step.ended",
|
||||||
|
data: {
|
||||||
|
sessionID: properties.sessionID,
|
||||||
|
assistantMessageID: info.id,
|
||||||
|
finish: "stop",
|
||||||
|
cost: info.cost ?? 0,
|
||||||
|
tokens: info.tokens ?? { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
if (payload.type === "message.part.updated") {
|
||||||
|
const part = properties.part as Record<string, unknown>
|
||||||
|
const sessionID = properties.sessionID
|
||||||
|
const assistantMessageID = part.messageID
|
||||||
|
if (part.type === "text") {
|
||||||
|
const existing = document.querySelector(`[data-timeline-part-id="${String(part.id)}"]`)
|
||||||
|
if (existing) {
|
||||||
|
const parts = Array.from(
|
||||||
|
document.querySelectorAll(`[data-message-id="${assistantMessageID}"] [data-component="text-part"]`),
|
||||||
|
)
|
||||||
|
return events([
|
||||||
|
{
|
||||||
|
type: "session.text.ended",
|
||||||
|
data: { sessionID, assistantMessageID, ordinal: parts.indexOf(existing), text: part.text },
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
const ordinal =
|
||||||
|
textOrdinals.get(String(assistantMessageID)) ??
|
||||||
|
document.querySelectorAll(`[data-message-id="${assistantMessageID}"] [data-component="text-part"]`).length
|
||||||
|
textOrdinals.set(String(assistantMessageID), ordinal + 1)
|
||||||
|
return events([
|
||||||
|
{ type: "session.text.started", data: { sessionID, assistantMessageID, ordinal } },
|
||||||
|
{ type: "session.text.ended", data: { sessionID, assistantMessageID, ordinal, text: part.text } },
|
||||||
|
])
|
||||||
|
}
|
||||||
|
if (part.type === "reasoning") {
|
||||||
|
const ordinal =
|
||||||
|
reasoningOrdinals.get(String(assistantMessageID)) ??
|
||||||
|
document.querySelectorAll(`[data-message-id="${assistantMessageID}"] [data-component="reasoning-part"]`)
|
||||||
|
.length
|
||||||
|
reasoningOrdinals.set(String(assistantMessageID), ordinal + 1)
|
||||||
|
return events([
|
||||||
|
{ type: "session.reasoning.started", data: { sessionID, assistantMessageID, ordinal } },
|
||||||
|
{ type: "session.reasoning.ended", data: { sessionID, assistantMessageID, ordinal, text: part.text } },
|
||||||
|
])
|
||||||
|
}
|
||||||
|
if (part.type === "tool") {
|
||||||
|
const state = part.state as Record<string, unknown>
|
||||||
|
const id = part.callID ?? part.id
|
||||||
|
const toolID = String(id)
|
||||||
|
const exists =
|
||||||
|
startedTools.has(toolID) ||
|
||||||
|
document.querySelector(`[data-timeline-part-id="${String(part.id)}"]`) !== null ||
|
||||||
|
Array.from(document.querySelectorAll("[data-timeline-part-ids]")).some((element) =>
|
||||||
|
element.getAttribute("data-timeline-part-ids")?.split(",").includes(String(part.id)),
|
||||||
|
)
|
||||||
|
startedTools.add(toolID)
|
||||||
|
const started = exists
|
||||||
|
? []
|
||||||
|
: [{ type: "session.tool.input.started", data: { sessionID, assistantMessageID, id, name: part.tool } }]
|
||||||
|
if (state.status === "pending") return events(started)
|
||||||
|
const called = {
|
||||||
|
type: "session.tool.called",
|
||||||
|
data: { sessionID, assistantMessageID, id, input: state.input ?? {}, executed: false },
|
||||||
|
}
|
||||||
|
if (state.status === "running")
|
||||||
|
return events([
|
||||||
|
...started,
|
||||||
|
called,
|
||||||
|
{ type: "session.tool.progress", data: { sessionID, assistantMessageID, id, metadata: state.metadata ?? {} } },
|
||||||
|
])
|
||||||
|
if (state.status === "error")
|
||||||
|
return events([
|
||||||
|
...started,
|
||||||
|
called,
|
||||||
|
{
|
||||||
|
type: "session.tool.failed",
|
||||||
|
data: {
|
||||||
|
sessionID,
|
||||||
|
assistantMessageID,
|
||||||
|
id,
|
||||||
|
error: { type: "tool.error", message: state.error ?? "Tool failed" },
|
||||||
|
metadata: state.metadata ?? {},
|
||||||
|
executed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
return events([
|
||||||
|
...started,
|
||||||
|
called,
|
||||||
|
{
|
||||||
|
type: "session.tool.success",
|
||||||
|
data: {
|
||||||
|
sessionID,
|
||||||
|
assistantMessageID,
|
||||||
|
id,
|
||||||
|
content: [{ type: "text", text: state.output ?? "" }],
|
||||||
|
metadata: state.metadata ?? {},
|
||||||
|
executed: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events([{ type: payload.type, data: properties }])
|
||||||
}
|
}
|
||||||
const acknowledge = (
|
const acknowledge = (
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
@@ -156,12 +308,15 @@ export async function installSseTransport<T>(
|
|||||||
return acknowledge(connection, input.bytes.length, output.length)
|
return acknowledge(connection, input.bytes.length, output.length)
|
||||||
}
|
}
|
||||||
const encoded = input.deliveries.map((delivery) => {
|
const encoded = input.deliveries.map((delivery) => {
|
||||||
const payload = connection.path === "/api/event" ? currentEvent(delivery.payload) : delivery.payload
|
const payloads = connection.path === "/api/event" ? currentEvents(delivery.payload) : [delivery.payload]
|
||||||
return { delivery, payload, bytes: encoder.encode(frame(payload, delivery.options)) }
|
return {
|
||||||
|
delivery,
|
||||||
|
bytes: encoder.encode(payloads.map((payload) => frame(payload, delivery.options)).join("")),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
encoded.forEach((item) => marker(item.delivery.options?.marker))
|
encoded.forEach((item) => marker(item.delivery.options?.marker))
|
||||||
if (input.burst) {
|
if (input.burst) {
|
||||||
const bytes = encoder.encode(encoded.map((item) => frame(item.payload, item.delivery.options)).join(""))
|
const bytes = encoder.encode(encoded.map((item) => new TextDecoder().decode(item.bytes)).join(""))
|
||||||
connection.controller.enqueue(bytes)
|
connection.controller.enqueue(bytes)
|
||||||
return encoded.map((item) => acknowledge(connection, item.bytes.byteLength, 1, item.delivery.options?.id))
|
return encoded.map((item) => acknowledge(connection, item.bytes.byteLength, 1, item.delivery.options?.id))
|
||||||
}
|
}
|
||||||
@@ -221,7 +376,7 @@ export async function installSseTransport<T>(
|
|||||||
}
|
}
|
||||||
Object.defineProperty(window, "fetch", { configurable: true, writable: true, value: fetch })
|
Object.defineProperty(window, "fetch", { configurable: true, writable: true, value: fetch })
|
||||||
},
|
},
|
||||||
{ server, retry: options.retry },
|
{ server, retry: options.retry, existingTools: options.existingTools ?? [] },
|
||||||
)
|
)
|
||||||
|
|
||||||
const command = <Result>(input: BrowserCommand<T>) =>
|
const command = <Result>(input: BrowserCommand<T>) =>
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
|
<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
data-new-layout
|
|
||||||
class="antialiased overscroll-none font-(family-name:--font-family-text) text-[13px] font-[440] overflow-hidden bg-v2-background-bg-deep"
|
class="antialiased overscroll-none font-(family-name:--font-family-text) text-[13px] font-[440] overflow-hidden bg-v2-background-bg-deep"
|
||||||
>
|
>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
@@ -74,7 +74,6 @@
|
|||||||
"@solidjs/router": "catalog:",
|
"@solidjs/router": "catalog:",
|
||||||
"@tanstack/solid-query": "5.91.4",
|
"@tanstack/solid-query": "5.91.4",
|
||||||
"@tanstack/solid-virtual": "catalog:",
|
"@tanstack/solid-virtual": "catalog:",
|
||||||
"@thisbeyond/solid-dnd": "0.7.5",
|
|
||||||
"diff": "catalog:",
|
"diff": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
|
|||||||
+20
-26
@@ -35,9 +35,9 @@ import { PromptProvider } from "@/context/prompt"
|
|||||||
import { ServerConnection, ServersProvider, useServers } from "@/context/servers"
|
import { ServerConnection, ServersProvider, useServers } from "@/context/servers"
|
||||||
import { SettingsProvider } from "@/context/settings"
|
import { SettingsProvider } from "@/context/settings"
|
||||||
import { TabsProvider, useTabs, type DraftTab } from "@/context/tabs"
|
import { TabsProvider, useTabs, type DraftTab } from "@/context/tabs"
|
||||||
import { SDKProvider } from "@/context/sdk"
|
import { LocationProvider } from "@/context/location"
|
||||||
import { WslServersProvider } from "@/wsl/context"
|
import { WslServersProvider } from "@/wsl/context"
|
||||||
import { DirectoryDataProvider } from "@/pages/directory-layout"
|
import { SessionUIProvider } from "@/pages/directory-layout"
|
||||||
import Layout from "@/pages/layout"
|
import Layout from "@/pages/layout"
|
||||||
import { ErrorPage } from "./pages/error"
|
import { ErrorPage } from "./pages/error"
|
||||||
import { legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route"
|
import { legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route"
|
||||||
@@ -66,19 +66,16 @@ const DirectoryDraftRedirect = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TargetServerRoute(props: ParentProps) {
|
function TargetServerRoute(props: ParentProps) {
|
||||||
const params = useParams<{ serverKey: string; id: string }>()
|
const params = useParams<{ serverKey: string }>()
|
||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const conn = createMemo(() => {
|
const conn = createMemo(() =>
|
||||||
const key = requireServerKey(params.serverKey)
|
global.servers.list().find((item) => ServerConnection.key(item) === requireServerKey(params.serverKey)),
|
||||||
return global.servers.list().find((item) => ServerConnection.key(item) === key)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// Owns the server-identity remount. Session changes must not remount this subtree.
|
// Owns the server-identity remount. Session changes must not remount this subtree.
|
||||||
<Show when={requireServerKey(params.serverKey)} keyed>
|
<Show when={conn()} keyed>
|
||||||
<Show when={conn()} keyed>
|
{(conn) => <ServerProvider conn={conn}>{props.children}</ServerProvider>}
|
||||||
{(conn) => <ServerProvider conn={conn}>{props.children}</ServerProvider>}
|
|
||||||
</Show>
|
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -87,14 +84,12 @@ function DraftRoute() {
|
|||||||
const [search] = useSearchParams<{ draftId?: string }>()
|
const [search] = useSearchParams<{ draftId?: string }>()
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
return (
|
return (
|
||||||
<Show when={tabs.ready()}>
|
<Show
|
||||||
<Show
|
when={tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === search.draftId)}
|
||||||
when={tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === search.draftId)}
|
keyed
|
||||||
keyed
|
fallback={tabs.ready() && <Navigate href="/" />}
|
||||||
fallback={<Navigate href="/" />}
|
>
|
||||||
>
|
{(draft) => <ResolvedDraftRoute draft={draft} />}
|
||||||
{(draft) => <ResolvedDraftRoute draft={draft} />}
|
|
||||||
</Show>
|
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -109,13 +104,13 @@ function ResolvedDraftRoute(props: { draft: DraftTab }) {
|
|||||||
{(conn) => (
|
{(conn) => (
|
||||||
<ServerProvider conn={conn}>
|
<ServerProvider conn={conn}>
|
||||||
<ModelsProvider directory={props.draft.directory}>
|
<ModelsProvider directory={props.draft.directory}>
|
||||||
<SDKProvider directory={props.draft.directory}>
|
<LocationProvider directory={props.draft.directory}>
|
||||||
<DirectoryDataProvider directory={props.draft.directory} server={props.draft.server}>
|
<SessionUIProvider directory={props.draft.directory} server={props.draft.server}>
|
||||||
<DraftProviders>
|
<DraftProviders>
|
||||||
<NewSession draftId={props.draft.draftID} />
|
<NewSession draftId={props.draft.draftID} />
|
||||||
</DraftProviders>
|
</DraftProviders>
|
||||||
</DirectoryDataProvider>
|
</SessionUIProvider>
|
||||||
</SDKProvider>
|
</LocationProvider>
|
||||||
</ModelsProvider>
|
</ModelsProvider>
|
||||||
</ServerProvider>
|
</ServerProvider>
|
||||||
)}
|
)}
|
||||||
@@ -166,10 +161,9 @@ function QueryProvider(props: ParentProps) {
|
|||||||
return <QueryClientProvider client={client}>{props.children}</QueryClientProvider>
|
return <QueryClientProvider client={client}>{props.children}</QueryClientProvider>
|
||||||
}
|
}
|
||||||
|
|
||||||
function BodyDesignClass() {
|
function BodyTypography() {
|
||||||
createRenderEffect(() => {
|
createRenderEffect(() => {
|
||||||
if (typeof document === "undefined") return
|
if (typeof document === "undefined") return
|
||||||
document.body.toggleAttribute("data-new-layout", true)
|
|
||||||
document.body.classList.remove("text-12-regular")
|
document.body.classList.remove("text-12-regular")
|
||||||
document.body.classList.add("font-(family-name:--font-family-text)", "text-[13px]", "font-[440]")
|
document.body.classList.add("font-(family-name:--font-family-text)", "text-[13px]", "font-[440]")
|
||||||
})
|
})
|
||||||
@@ -273,7 +267,7 @@ export function AppInterface(props: {
|
|||||||
// providers beneath it.
|
// providers beneath it.
|
||||||
const Root = (rootProps: ParentProps) => (
|
const Root = (rootProps: ParentProps) => (
|
||||||
<TabsProvider>
|
<TabsProvider>
|
||||||
<BodyDesignClass />
|
<BodyTypography />
|
||||||
<CommandProvider>
|
<CommandProvider>
|
||||||
<DesktopCommands />
|
<DesktopCommands />
|
||||||
<HighlightsProvider>
|
<HighlightsProvider>
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { List, type ListRef } from "@opencode-ai/ui/list"
|
|
||||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||||
import { Spinner } from "@opencode-ai/ui/spinner"
|
import { Spinner } from "@opencode-ai/ui/spinner"
|
||||||
import { Tag } from "@opencode-ai/ui/tag"
|
|
||||||
import { TextField } from "@opencode-ai/ui/text-field"
|
import { TextField } from "@opencode-ai/ui/text-field"
|
||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||||
import { DialogBody, DialogHeader, DialogTitle, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2"
|
import { DialogBody, DialogHeader, DialogTitle, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2"
|
||||||
@@ -16,10 +13,8 @@ import { type Component, createMemo, createUniqueId, For, Match, onMount, Show,
|
|||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import { ExternalLink } from "@/components/external-link"
|
import { ExternalLink } from "@/components/external-link"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useSettings } from "@/context/settings"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
import { popularProviders, useProviders } from "@/hooks/use-providers"
|
|
||||||
import { useIntegrations } from "@/hooks/use-integrations"
|
import { useIntegrations } from "@/hooks/use-integrations"
|
||||||
import { CustomProviderForm } from "./dialog-custom-provider"
|
import { CustomProviderForm } from "./dialog-custom-provider"
|
||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
@@ -47,8 +42,6 @@ export const DialogConnectProvider: Component<{
|
|||||||
const fallback = useProviderConnectController()
|
const fallback = useProviderConnectController()
|
||||||
const controller = props.controller ?? fallback
|
const controller = props.controller ?? fallback
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const settings = useSettings()
|
|
||||||
const newLayout = settings.general.newLayoutDesigns
|
|
||||||
const reset = controller.back
|
const reset = controller.back
|
||||||
const back = { current: reset }
|
const back = { current: reset }
|
||||||
let focusHost: HTMLDivElement | undefined
|
let focusHost: HTMLDivElement | undefined
|
||||||
@@ -62,7 +55,7 @@ export const DialogConnectProvider: Component<{
|
|||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={controller.selected() === CUSTOM_ID}>
|
<Match when={controller.selected() === CUSTOM_ID}>
|
||||||
<CustomProviderForm autofocus={!newLayout()} />
|
<CustomProviderForm autofocus={false} />
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={controller.selected() && controller.selected() !== CUSTOM_ID ? controller.selected() : undefined}>
|
<Match when={controller.selected() && controller.selected() !== CUSTOM_ID ? controller.selected() : undefined}>
|
||||||
{(provider) => (
|
{(provider) => (
|
||||||
@@ -75,140 +68,42 @@ export const DialogConnectProvider: Component<{
|
|||||||
)}
|
)}
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<ProviderPicker
|
<ProviderPicker directory={props.directory} onSelect={select} onPrepare={holdFocus} />
|
||||||
directory={props.directory}
|
|
||||||
onSelect={select}
|
|
||||||
onPrepare={newLayout() ? holdFocus : undefined}
|
|
||||||
/>
|
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show
|
<DialogV2
|
||||||
when={newLayout()}
|
containerClass="!h-[min(calc(100vh_-_16px),512px)] !w-[min(calc(100vw_-_16px),640px)]"
|
||||||
fallback={
|
class="[font-family:var(--v2-font-family-sans)] [&_[data-slot=dialog-header]]:!px-5 [&_[data-slot=dialog-header-title]]:!text-[15px] [&_[data-slot=dialog-header-title]]:!tracking-[-0.13px]"
|
||||||
<Dialog
|
|
||||||
class="h-full"
|
|
||||||
transition
|
|
||||||
title={
|
|
||||||
<Show when={controller.selected()} fallback={language.t("command.provider.connect")}>
|
|
||||||
<IconButton
|
|
||||||
tabIndex={-1}
|
|
||||||
icon="arrow-left"
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => back.current()}
|
|
||||||
aria-label={language.t("common.goBack")}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Content />
|
|
||||||
</Dialog>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<DialogV2
|
<DialogHeader closeLabel={language.t("common.close")}>
|
||||||
containerClass="!h-[min(calc(100vh_-_16px),512px)] !w-[min(calc(100vw_-_16px),640px)]"
|
<Show
|
||||||
class="[font-family:var(--v2-font-family-sans)] [&_[data-slot=dialog-header]]:!px-5 [&_[data-slot=dialog-header-title]]:!text-[15px] [&_[data-slot=dialog-header-title]]:!tracking-[-0.13px]"
|
when={controller.selected()}
|
||||||
>
|
fallback={<DialogTitle>{language.t("command.provider.connect")}</DialogTitle>}
|
||||||
<DialogHeader closeLabel={language.t("common.close")}>
|
>
|
||||||
<Show
|
<button
|
||||||
when={controller.selected()}
|
type="button"
|
||||||
fallback={<DialogTitle>{language.t("command.provider.connect")}</DialogTitle>}
|
class="flex size-5 items-center justify-center rounded-sm text-v2-icon-icon-muted hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none"
|
||||||
|
onClick={() => back.current()}
|
||||||
|
aria-label={language.t("common.goBack")}
|
||||||
>
|
>
|
||||||
<button
|
<Icon name="arrow-left" size="small" />
|
||||||
type="button"
|
</button>
|
||||||
class="flex size-5 items-center justify-center rounded-sm text-v2-icon-icon-muted hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none"
|
</Show>
|
||||||
onClick={() => back.current()}
|
</DialogHeader>
|
||||||
aria-label={language.t("common.goBack")}
|
<DialogBody class="min-h-0 flex-1 overflow-hidden px-2 pb-2">
|
||||||
>
|
<div ref={focusHost} tabIndex={-1} class="flex min-h-0 flex-1 flex-col outline-none">
|
||||||
<Icon name="arrow-left" size="small" />
|
<Content />
|
||||||
</button>
|
</div>
|
||||||
</Show>
|
</DialogBody>
|
||||||
</DialogHeader>
|
</DialogV2>
|
||||||
<DialogBody class="min-h-0 flex-1 overflow-hidden px-2 pb-2">
|
|
||||||
<div ref={focusHost} tabIndex={-1} class="flex min-h-0 flex-1 flex-col outline-none">
|
|
||||||
<Content />
|
|
||||||
</div>
|
|
||||||
</DialogBody>
|
|
||||||
</DialogV2>
|
|
||||||
</Show>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProviderPicker(props: { directory?: string; onSelect: (provider: string) => void; onPrepare?: () => void }) {
|
function ProviderPicker(props: { directory?: string; onSelect: (provider: string) => void; onPrepare?: () => void }) {
|
||||||
const settings = useSettings()
|
|
||||||
if (settings.general.newLayoutDesigns())
|
|
||||||
return <ProviderPickerV2 directory={props.directory} onSelect={props.onSelect} onPrepare={props.onPrepare} />
|
|
||||||
const integrations = useIntegrations(() => props.directory)
|
|
||||||
const language = useLanguage()
|
|
||||||
const popularGroup = () => language.t("dialog.provider.group.popular")
|
|
||||||
const otherGroup = () => language.t("dialog.provider.group.other")
|
|
||||||
const customLabel = () => language.t("settings.providers.tag.custom")
|
|
||||||
const note = (id: string) => {
|
|
||||||
if (id === "anthropic") return language.t("dialog.provider.anthropic.note")
|
|
||||||
if (id === "openai") return language.t("dialog.provider.openai.note")
|
|
||||||
if (id.startsWith("github-copilot")) return language.t("dialog.provider.copilot.note")
|
|
||||||
if (id === "opencode-go") return language.t("dialog.provider.opencodeGo.tagline")
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<List
|
|
||||||
class="px-3"
|
|
||||||
search={{ placeholder: language.t("dialog.provider.search.placeholder"), autofocus: true }}
|
|
||||||
emptyMessage={language.t("dialog.provider.empty")}
|
|
||||||
activeIcon="plus-small"
|
|
||||||
key={(x) => x?.id}
|
|
||||||
items={() => {
|
|
||||||
language.locale()
|
|
||||||
return [{ id: CUSTOM_ID, name: customLabel() }, ...integrations.list()]
|
|
||||||
}}
|
|
||||||
filterKeys={["id", "name"]}
|
|
||||||
groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())}
|
|
||||||
sortBy={(a, b) => {
|
|
||||||
if (a.id === CUSTOM_ID) return -1
|
|
||||||
if (b.id === CUSTOM_ID) return 1
|
|
||||||
if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
|
|
||||||
return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
|
|
||||||
return a.name.localeCompare(b.name)
|
|
||||||
}}
|
|
||||||
sortGroupsBy={(a, b) => {
|
|
||||||
const popular = popularGroup()
|
|
||||||
if (a.category === popular && b.category !== popular) return -1
|
|
||||||
if (b.category === popular && a.category !== popular) return 1
|
|
||||||
return 0
|
|
||||||
}}
|
|
||||||
onSelect={(x) => {
|
|
||||||
if (!x) return
|
|
||||||
props.onSelect(x.id)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(i) => (
|
|
||||||
<div class="px-1.25 w-full flex items-center gap-x-3">
|
|
||||||
<ProviderIcon data-slot="list-item-extra-icon" id={i.id} />
|
|
||||||
<span>{i.name}</span>
|
|
||||||
<Show when={i.id === "opencode"}>
|
|
||||||
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.opencode.tagline")}</div>
|
|
||||||
</Show>
|
|
||||||
<Show when={i.id === CUSTOM_ID}>
|
|
||||||
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
|
||||||
</Show>
|
|
||||||
<Show when={i.id === "opencode"}>
|
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
|
||||||
</Show>
|
|
||||||
<Show when={note(i.id)}>{(value) => <div class="text-14-regular text-text-weak">{value()}</div>}</Show>
|
|
||||||
<Show when={i.id === "opencode-go"}>
|
|
||||||
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ProviderPickerV2(props: { directory?: string; onSelect: (provider: string) => void; onPrepare?: () => void }) {
|
|
||||||
const integrations = useIntegrations(() => props.directory)
|
const integrations = useIntegrations(() => props.directory)
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
@@ -362,11 +257,8 @@ function ProviderConnection(props: {
|
|||||||
setBack: (handler: () => void) => void
|
setBack: (handler: () => void) => void
|
||||||
}) {
|
}) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const serverSync = useServerSync()
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const settings = useSettings()
|
|
||||||
const newLayout = settings.general.newLayoutDesigns
|
|
||||||
const providers = useProviders(() => props.directory)
|
const providers = useProviders(() => props.directory)
|
||||||
const directory = () => props.directory ?? decode64(params.dir)
|
const directory = () => props.directory ?? decode64(params.dir)
|
||||||
|
|
||||||
@@ -386,10 +278,7 @@ function ProviderConnection(props: {
|
|||||||
const provider = createMemo(() => ({
|
const provider = createMemo(() => ({
|
||||||
id: props.provider,
|
id: props.provider,
|
||||||
name:
|
name:
|
||||||
providers.all().get(props.provider)?.name ??
|
providers.all().get(props.provider)?.name ?? controller.integration()?.name ?? props.provider,
|
||||||
serverSync.data.provider.all.get(props.provider)?.name ??
|
|
||||||
controller.integration()?.name ??
|
|
||||||
props.provider,
|
|
||||||
}))
|
}))
|
||||||
const methodLabel = (value?: { type?: string; label?: string }) => {
|
const methodLabel = (value?: { type?: string; label?: string }) => {
|
||||||
if (!value) return ""
|
if (!value) return ""
|
||||||
@@ -534,15 +423,6 @@ function ProviderConnection(props: {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let listRef: ListRef | undefined
|
|
||||||
function handleKey(e: KeyboardEvent) {
|
|
||||||
if (e.key === "Enter" && e.target instanceof HTMLInputElement) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (e.key === "Escape") return
|
|
||||||
listRef?.onKeyDown(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
if (controller.methods().length > 1 && controller.methodIndex() !== undefined) {
|
if (controller.methods().length > 1 && controller.methodIndex() !== undefined) {
|
||||||
controller.auth.reset()
|
controller.auth.reset()
|
||||||
@@ -554,66 +434,34 @@ function ProviderConnection(props: {
|
|||||||
props.setBack(goBack)
|
props.setBack(goBack)
|
||||||
|
|
||||||
function MethodSelection() {
|
function MethodSelection() {
|
||||||
if (newLayout())
|
|
||||||
return (
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<div class="px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
|
||||||
{language.t("provider.connect.selectMethod", { provider: provider().name })}
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<For each={controller.methods()}>
|
|
||||||
{(item, index) => {
|
|
||||||
const details = () => methodDetails(item)
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="group flex h-9 w-full items-center gap-2 rounded-md px-3 text-left text-[13px] leading-5 tracking-[-0.04px] hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none"
|
|
||||||
onClick={() => void controller.auth.select(index())}
|
|
||||||
>
|
|
||||||
<span class="flex h-2 w-4 shrink-0 items-center justify-center rounded-[1px] bg-v2-background-bg-base shadow-[var(--v2-elevation-button-neutral)]">
|
|
||||||
<span class="hidden h-0.5 w-2.5 bg-v2-icon-icon-base group-hover:block group-focus-visible:block" />
|
|
||||||
</span>
|
|
||||||
<span class="font-[530] text-v2-text-text-base">{details().label}</span>
|
|
||||||
<Show when={details().hint}>
|
|
||||||
{(hint) => <span class="font-[440] text-v2-text-text-muted">{hint()}</span>}
|
|
||||||
</Show>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div class="flex flex-col gap-2">
|
||||||
<div class="text-14-regular text-text-base">
|
<div class="px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
||||||
{language.t("provider.connect.selectMethod", { provider: provider().name })}
|
{language.t("provider.connect.selectMethod", { provider: provider().name })}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="flex flex-col">
|
||||||
<List
|
<For each={controller.methods()}>
|
||||||
class="px-3"
|
{(item, index) => {
|
||||||
ref={(ref) => {
|
const details = () => methodDetails(item)
|
||||||
listRef = ref
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="group flex h-9 w-full items-center gap-2 rounded-md px-3 text-left text-[13px] leading-5 tracking-[-0.04px] hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none"
|
||||||
|
onClick={() => void controller.auth.select(index())}
|
||||||
|
>
|
||||||
|
<span class="flex h-2 w-4 shrink-0 items-center justify-center rounded-[1px] bg-v2-background-bg-base shadow-[var(--v2-elevation-button-neutral)]">
|
||||||
|
<span class="hidden h-0.5 w-2.5 bg-v2-icon-icon-base group-hover:block group-focus-visible:block" />
|
||||||
|
</span>
|
||||||
|
<span class="font-[530] text-v2-text-text-base">{details().label}</span>
|
||||||
|
<Show when={details().hint}>
|
||||||
|
{(hint) => <span class="font-[440] text-v2-text-text-muted">{hint()}</span>}
|
||||||
|
</Show>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
items={controller.methods}
|
</For>
|
||||||
key={(m) => m?.label ?? m?.type}
|
|
||||||
onSelect={async (selected, index) => {
|
|
||||||
if (!selected) return
|
|
||||||
void controller.auth.select(index)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(i) => (
|
|
||||||
<div class="w-full flex items-center gap-x-2">
|
|
||||||
<div class="w-4 h-2 rounded-[1px] bg-input-base shadow-xs-border-base flex items-center justify-center">
|
|
||||||
<div class="w-2.5 h-0.5 ml-0 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" />
|
|
||||||
</div>
|
|
||||||
<span>{methodLabel(i)}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,7 +474,6 @@ function ProviderConnection(props: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!newLayout()) return
|
|
||||||
apiKey?.focus({ preventScroll: true })
|
apiKey?.focus({ preventScroll: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -646,97 +493,54 @@ function ProviderConnection(props: {
|
|||||||
await controller.auth.connectKey(apiKey)
|
await controller.auth.connectKey(apiKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newLayout())
|
|
||||||
return (
|
|
||||||
<div class="flex flex-col gap-5 px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
|
||||||
<Show
|
|
||||||
when={provider().id === "opencode"}
|
|
||||||
fallback={language.t("provider.connect.apiKey.description", { provider: provider().name })}
|
|
||||||
>
|
|
||||||
<div class="flex flex-col gap-5">
|
|
||||||
<div>{language.t("provider.connect.opencodeZen.line1")}</div>
|
|
||||||
<div>{language.t("provider.connect.opencodeZen.line2")}</div>
|
|
||||||
<div>
|
|
||||||
{language.t("provider.connect.opencodeZen.visit.prefix")}
|
|
||||||
<ExternalLink
|
|
||||||
href="https://opencode.ai/zen"
|
|
||||||
class="text-v2-text-text-base focus-visible:rounded-xs focus-visible:outline-2 focus-visible:outline-v2-border-border-focus"
|
|
||||||
>
|
|
||||||
{language.t("provider.connect.opencodeZen.visit.link")}
|
|
||||||
</ExternalLink>
|
|
||||||
{language.t("provider.connect.opencodeZen.visit.suffix")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-5 self-stretch">
|
|
||||||
<label class="flex w-full flex-col gap-1 font-[530] leading-4 text-v2-text-text-base">
|
|
||||||
{language.t("provider.connect.apiKey.label", { provider: provider().name })}
|
|
||||||
<TextInputV2
|
|
||||||
ref={apiKey}
|
|
||||||
class="!w-full"
|
|
||||||
name="apiKey"
|
|
||||||
data-input="provider-api-key"
|
|
||||||
placeholder={language.t("provider.connect.apiKey.placeholder")}
|
|
||||||
value={formStore.value}
|
|
||||||
invalid={formStore.error !== undefined}
|
|
||||||
aria-describedby={formStore.error ? errorID : undefined}
|
|
||||||
autocomplete="off"
|
|
||||||
spellcheck={false}
|
|
||||||
onInput={(event) => setFormStore("value", event.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<Show when={formStore.error}>
|
|
||||||
{(error) => (
|
|
||||||
<div id={errorID} role="alert" class="-mt-4 text-xs text-v2-state-fg-danger">
|
|
||||||
{error()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
<ButtonV2 type="submit" variant="contrast" data-action="provider-connect-submit">
|
|
||||||
{language.t("common.continue")}
|
|
||||||
</ButtonV2>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-5 px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
||||||
<Switch>
|
<Show
|
||||||
<Match when={provider().id === "opencode"}>
|
when={provider().id === "opencode"}
|
||||||
<div class="flex flex-col gap-4">
|
fallback={language.t("provider.connect.apiKey.description", { provider: provider().name })}
|
||||||
<div class="text-14-regular text-text-base">{language.t("provider.connect.opencodeZen.line1")}</div>
|
>
|
||||||
<div class="text-14-regular text-text-base">{language.t("provider.connect.opencodeZen.line2")}</div>
|
<div class="flex flex-col gap-5">
|
||||||
<div class="text-14-regular text-text-base">
|
<div>{language.t("provider.connect.opencodeZen.line1")}</div>
|
||||||
{language.t("provider.connect.opencodeZen.visit.prefix")}
|
<div>{language.t("provider.connect.opencodeZen.line2")}</div>
|
||||||
<ExternalLink href="https://opencode.ai/zen" tabIndex={-1}>
|
<div>
|
||||||
{language.t("provider.connect.opencodeZen.visit.link")}
|
{language.t("provider.connect.opencodeZen.visit.prefix")}
|
||||||
</ExternalLink>
|
<ExternalLink
|
||||||
{language.t("provider.connect.opencodeZen.visit.suffix")}
|
href="https://opencode.ai/zen"
|
||||||
|
class="text-v2-text-text-base focus-visible:rounded-xs focus-visible:outline-2 focus-visible:outline-v2-border-border-focus"
|
||||||
|
>
|
||||||
|
{language.t("provider.connect.opencodeZen.visit.link")}
|
||||||
|
</ExternalLink>
|
||||||
|
{language.t("provider.connect.opencodeZen.visit.suffix")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-5 self-stretch">
|
||||||
|
<label class="flex w-full flex-col gap-1 font-[530] leading-4 text-v2-text-text-base">
|
||||||
|
{language.t("provider.connect.apiKey.label", { provider: provider().name })}
|
||||||
|
<TextInputV2
|
||||||
|
ref={apiKey}
|
||||||
|
class="!w-full"
|
||||||
|
name="apiKey"
|
||||||
|
data-input="provider-api-key"
|
||||||
|
placeholder={language.t("provider.connect.apiKey.placeholder")}
|
||||||
|
value={formStore.value}
|
||||||
|
invalid={formStore.error !== undefined}
|
||||||
|
aria-describedby={formStore.error ? errorID : undefined}
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck={false}
|
||||||
|
onInput={(event) => setFormStore("value", event.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<Show when={formStore.error}>
|
||||||
|
{(error) => (
|
||||||
|
<div id={errorID} role="alert" class="-mt-4 text-xs text-v2-state-fg-danger">
|
||||||
|
{error()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</Match>
|
</Show>
|
||||||
<Match when={true}>
|
<ButtonV2 type="submit" variant="contrast" data-action="provider-connect-submit">
|
||||||
<div class="text-14-regular text-text-base">
|
|
||||||
{language.t("provider.connect.apiKey.description", { provider: provider().name })}
|
|
||||||
</div>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
|
|
||||||
<TextField
|
|
||||||
autofocus={!newLayout()}
|
|
||||||
ref={apiKey}
|
|
||||||
type="text"
|
|
||||||
label={language.t("provider.connect.apiKey.label", { provider: provider().name })}
|
|
||||||
placeholder={language.t("provider.connect.apiKey.placeholder")}
|
|
||||||
name="apiKey"
|
|
||||||
value={formStore.value}
|
|
||||||
onChange={(v) => setFormStore("value", v)}
|
|
||||||
validationState={formStore.error ? "invalid" : undefined}
|
|
||||||
error={formStore.error}
|
|
||||||
/>
|
|
||||||
<Button class="w-auto" type="submit" size="large" variant="primary">
|
|
||||||
{language.t("common.continue")}
|
{language.t("common.continue")}
|
||||||
</Button>
|
</ButtonV2>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -751,7 +555,6 @@ function ProviderConnection(props: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!newLayout()) return
|
|
||||||
codeInput?.focus({ preventScroll: true })
|
codeInput?.focus({ preventScroll: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -771,73 +574,41 @@ function ProviderConnection(props: {
|
|||||||
setFormStore("error", await controller.auth.completeCode(code))
|
setFormStore("error", await controller.auth.completeCode(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newLayout())
|
|
||||||
return (
|
|
||||||
<div class="flex flex-col gap-5 px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
|
||||||
<div>
|
|
||||||
{language.t("provider.connect.oauth.code.visit.prefix")}
|
|
||||||
<ExternalLink href={controller.authorization()!.url} class="text-v2-text-text-base">
|
|
||||||
{language.t("provider.connect.oauth.code.visit.link")}
|
|
||||||
</ExternalLink>
|
|
||||||
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
|
|
||||||
</div>
|
|
||||||
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-5 self-stretch">
|
|
||||||
<label class="flex w-full flex-col gap-1 font-[530] leading-4 text-v2-text-text-base">
|
|
||||||
{language.t("provider.connect.oauth.code.label", { method: controller.currentMethod()?.label ?? "" })}
|
|
||||||
<TextInputV2
|
|
||||||
ref={codeInput}
|
|
||||||
class="!w-full"
|
|
||||||
name="code"
|
|
||||||
placeholder={language.t("provider.connect.oauth.code.placeholder")}
|
|
||||||
value={formStore.value}
|
|
||||||
invalid={formStore.error !== undefined}
|
|
||||||
aria-describedby={formStore.error ? errorID : undefined}
|
|
||||||
autocomplete="off"
|
|
||||||
spellcheck={false}
|
|
||||||
onInput={(event) => setFormStore("value", event.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<Show when={formStore.error}>
|
|
||||||
{(error) => (
|
|
||||||
<div id={errorID} role="alert" class="-mt-4 text-xs text-v2-state-fg-danger">
|
|
||||||
{error()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
<ButtonV2 type="submit" variant="contrast">
|
|
||||||
{language.t("common.continue")}
|
|
||||||
</ButtonV2>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-5 px-3 text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
||||||
<div class="text-14-regular text-text-base">
|
<div>
|
||||||
{language.t("provider.connect.oauth.code.visit.prefix")}
|
{language.t("provider.connect.oauth.code.visit.prefix")}
|
||||||
<ExternalLink href={controller.authorization()!.url}>
|
<ExternalLink href={controller.authorization()!.url} class="text-v2-text-text-base">
|
||||||
{language.t("provider.connect.oauth.code.visit.link")}
|
{language.t("provider.connect.oauth.code.visit.link")}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
|
{language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
|
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-5 self-stretch">
|
||||||
<TextField
|
<label class="flex w-full flex-col gap-1 font-[530] leading-4 text-v2-text-text-base">
|
||||||
autofocus={!newLayout()}
|
{language.t("provider.connect.oauth.code.label", { method: controller.currentMethod()?.label ?? "" })}
|
||||||
ref={codeInput}
|
<TextInputV2
|
||||||
type="text"
|
ref={codeInput}
|
||||||
label={language.t("provider.connect.oauth.code.label", {
|
class="!w-full"
|
||||||
method: controller.currentMethod()?.label ?? "",
|
name="code"
|
||||||
})}
|
placeholder={language.t("provider.connect.oauth.code.placeholder")}
|
||||||
placeholder={language.t("provider.connect.oauth.code.placeholder")}
|
value={formStore.value}
|
||||||
name="code"
|
invalid={formStore.error !== undefined}
|
||||||
value={formStore.value}
|
aria-describedby={formStore.error ? errorID : undefined}
|
||||||
onChange={(v) => setFormStore("value", v)}
|
autocomplete="off"
|
||||||
validationState={formStore.error ? "invalid" : undefined}
|
spellcheck={false}
|
||||||
error={formStore.error}
|
onInput={(event) => setFormStore("value", event.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
<Button class="w-auto" type="submit" size="large" variant="primary">
|
</label>
|
||||||
|
<Show when={formStore.error}>
|
||||||
|
{(error) => (
|
||||||
|
<div id={errorID} role="alert" class="-mt-4 text-xs text-v2-state-fg-danger">
|
||||||
|
{error()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
<ButtonV2 type="submit" variant="contrast">
|
||||||
{language.t("common.continue")}
|
{language.t("common.continue")}
|
||||||
</Button>
|
</ButtonV2>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -877,19 +648,10 @@ function ProviderConnection(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={newLayout() ? "flex min-h-0 flex-1 flex-col" : "flex flex-col gap-6 px-2.5 pb-3"}>
|
<div class="flex min-h-0 flex-1 flex-col">
|
||||||
<div class={newLayout() ? "flex h-10 shrink-0 items-start gap-2 px-3" : "flex items-center gap-4 px-2.5"}>
|
<div class="flex h-10 shrink-0 items-start gap-2 px-3">
|
||||||
<ProviderIcon
|
<ProviderIcon id={props.provider} class="mt-0.5 size-4 shrink-0 text-v2-icon-icon-base" />
|
||||||
id={props.provider}
|
<div class="text-[15px] font-[530] leading-5 tracking-[-0.13px] text-v2-text-text-base">
|
||||||
class={newLayout() ? "mt-0.5 size-4 shrink-0 text-v2-icon-icon-base" : "size-5 shrink-0 icon-strong-base"}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class={
|
|
||||||
newLayout()
|
|
||||||
? "text-[15px] font-[530] leading-5 tracking-[-0.13px] text-v2-text-text-base"
|
|
||||||
: "text-16-medium text-text-strong"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match
|
<Match
|
||||||
when={props.provider === "anthropic" && controller.currentMethod()?.label?.toLowerCase().includes("max")}
|
when={props.provider === "anthropic" && controller.currentMethod()?.label?.toLowerCase().includes("max")}
|
||||||
@@ -900,12 +662,8 @@ function ProviderConnection(props: {
|
|||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class={newLayout() ? "flex min-h-0 flex-1 flex-col" : "flex flex-col gap-6 px-2.5 pb-10"}>
|
<div class="flex min-h-0 flex-1 flex-col">
|
||||||
<div
|
<div>
|
||||||
onKeyDown={handleKey}
|
|
||||||
tabIndex={newLayout() ? undefined : 0}
|
|
||||||
autofocus={!newLayout() && controller.methodIndex() === undefined ? true : undefined}
|
|
||||||
>
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={controller.loading()}>
|
<Match when={controller.loading()}>
|
||||||
<div class="text-14-regular text-text-base">
|
<div class="text-14-regular text-text-base">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { showToast } from "@/utils/toast"
|
|||||||
import { batch, For } from "solid-js"
|
import { batch, For } from "solid-js"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { ExternalLink } from "@/components/external-link"
|
import { ExternalLink } from "@/components/external-link"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useData } from "@/context/server"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { type FormState, headerRow, modelRow, validateCustomProvider } from "./dialog-custom-provider-form"
|
import { type FormState, headerRow, modelRow, validateCustomProvider } from "./dialog-custom-provider-form"
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export function DialogCustomProvider(props: Props) {
|
|||||||
|
|
||||||
export function CustomProviderForm(props: { autofocus?: boolean } = {}) {
|
export function CustomProviderForm(props: { autofocus?: boolean } = {}) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const serverSync = useServerSync()
|
const data = useData()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
const [form, setForm] = createStore<FormState>({
|
const [form, setForm] = createStore<FormState>({
|
||||||
@@ -116,8 +116,9 @@ export function CustomProviderForm(props: { autofocus?: boolean } = {}) {
|
|||||||
const output = validateCustomProvider({
|
const output = validateCustomProvider({
|
||||||
form,
|
form,
|
||||||
t: language.t,
|
t: language.t,
|
||||||
disabledProviders: serverSync.data.config.disabled_providers ?? [],
|
// TODO: Restore disabled-provider validation when V2 exposes config reads.
|
||||||
existingProviderIDs: new Set(serverSync.data.provider.all.keys()),
|
disabledProviders: [],
|
||||||
|
existingProviderIDs: new Set((data.location.provider.list() ?? []).map((provider) => provider.id)),
|
||||||
})
|
})
|
||||||
batch(() => {
|
batch(() => {
|
||||||
setForm("err", output.err)
|
setForm("err", output.err)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { For, Show, createSignal, startTransition } from "solid-js"
|
|||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { getProjectAvatarVariant, type LocalProject } from "@/context/layout"
|
import { getProjectAvatarVariant, type LocalProject } from "@/context/layout"
|
||||||
import { ServerConnection } from "@/context/servers"
|
import { ServerConnection } from "@/context/servers"
|
||||||
import { SDKProvider } from "@/context/sdk"
|
import { LocationProvider } from "@/context/location"
|
||||||
import { displayName, getProjectAvatarSource } from "@/pages/layout/helpers"
|
import { displayName, getProjectAvatarSource } from "@/pages/layout/helpers"
|
||||||
import { createEditProjectModel } from "./edit-project"
|
import { createEditProjectModel } from "./edit-project"
|
||||||
import { ProjectSettingsExtensions } from "./project-settings-extensions"
|
import { ProjectSettingsExtensions } from "./project-settings-extensions"
|
||||||
@@ -22,9 +22,9 @@ import "./dialog-edit-project-v2.css"
|
|||||||
export function DialogEditProjectV2(props: { project: LocalProject; server: ServerConnection.Any }) {
|
export function DialogEditProjectV2(props: { project: LocalProject; server: ServerConnection.Any }) {
|
||||||
return (
|
return (
|
||||||
<SettingsServerDataScope server={props.server}>
|
<SettingsServerDataScope server={props.server}>
|
||||||
<SDKProvider directory={props.project.worktree}>
|
<LocationProvider directory={props.project.worktree}>
|
||||||
<ProjectSettingsDialog project={props.project} server={props.server} />
|
<ProjectSettingsDialog project={props.project} server={props.server} />
|
||||||
</SDKProvider>
|
</LocationProvider>
|
||||||
</SettingsServerDataScope>
|
</SettingsServerDataScope>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import { Component, createMemo } from "solid-js"
|
import { Component, createMemo } from "solid-js"
|
||||||
import { useNavigate, useParams } from "@solidjs/router"
|
import { useNavigate, useParams } from "@solidjs/router"
|
||||||
import { useSync } from "@/context/sync"
|
import { useData } from "@/context/server"
|
||||||
import { useSDK } from "@/context/sdk"
|
|
||||||
import { usePrompt } from "@/context/prompt"
|
import { usePrompt } from "@/context/prompt"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { extractPromptFromParts } from "@/utils/prompt"
|
|
||||||
import type { TextPart as SDKTextPart } from "@/types"
|
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
|
import { sessionHref } from "@/utils/session-route"
|
||||||
|
import { ServerConnection } from "@/context/servers"
|
||||||
|
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||||
|
import { extractPromptFromParts } from "@/utils/prompt"
|
||||||
|
import { normalizeSessionMessages } from "@/utils/session-message"
|
||||||
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
|
|
||||||
interface ForkableMessage {
|
interface ForkableMessage {
|
||||||
id: string
|
id: string
|
||||||
@@ -25,8 +28,9 @@ function formatTime(date: Date): string {
|
|||||||
export const DialogFork: Component = () => {
|
export const DialogFork: Component = () => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const sync = useSync()
|
const data = useData()
|
||||||
const sdk = useSDK()
|
const serverSDK = useServerSDK()
|
||||||
|
const location = useWorkspaceLocation()
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
@@ -35,19 +39,15 @@ export const DialogFork: Component = () => {
|
|||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return []
|
if (!sessionID) return []
|
||||||
|
|
||||||
const msgs = sync().data.message[sessionID] ?? []
|
const msgs = data.session.message.list(sessionID)
|
||||||
const result: ForkableMessage[] = []
|
const result: ForkableMessage[] = []
|
||||||
|
|
||||||
for (const message of msgs) {
|
for (const message of msgs) {
|
||||||
if (message.role !== "user") continue
|
if (message.type !== "user" || !message.text) continue
|
||||||
|
|
||||||
const parts = sync().data.part[message.id] ?? []
|
|
||||||
const textPart = parts.find((x): x is SDKTextPart => x.type === "text" && !x.synthetic && !x.ignored)
|
|
||||||
if (!textPart) continue
|
|
||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
text: textPart.text.replace(/\n/g, " ").slice(0, 200),
|
text: message.text.replace(/\n/g, " ").slice(0, 200),
|
||||||
time: formatTime(new Date(message.time.created)),
|
time: formatTime(new Date(message.time.created)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -60,20 +60,21 @@ export const DialogFork: Component = () => {
|
|||||||
|
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
|
const restored = extractPromptFromParts(
|
||||||
|
normalizeSessionMessages(sessionID, data.session.message.list(sessionID)).parts.get(item.id) ?? [],
|
||||||
|
{
|
||||||
|
directory: location().directory,
|
||||||
|
attachmentName: language.t("common.attachment"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const parts = sync().data.part[item.id] ?? []
|
serverSDK.api.session
|
||||||
const restored = extractPromptFromParts(parts, {
|
.fork({ sessionID, boundary: { type: "before", messageID: item.id } })
|
||||||
directory: sdk().directory,
|
|
||||||
attachmentName: language.t("common.attachment"),
|
|
||||||
})
|
|
||||||
const dir = base64Encode(sdk().directory)
|
|
||||||
|
|
||||||
sdk()
|
|
||||||
.api.session.fork({ sessionID, boundary: { type: "before", messageID: item.id } })
|
|
||||||
.then((forked) => {
|
.then((forked) => {
|
||||||
|
data.session.remember(forked)
|
||||||
dialog.close()
|
dialog.close()
|
||||||
prompt.set(restored, undefined, { dir, id: forked.id })
|
prompt.set(restored, undefined, { dir: base64Encode(location().directory), id: forked.id })
|
||||||
navigate(`/${dir}/session/${forked.id}`)
|
navigate(sessionHref(ServerConnection.key(serverSDK.server), forked.id))
|
||||||
})
|
})
|
||||||
.catch((err: unknown) => {
|
.catch((err: unknown) => {
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
|
||||||
import { List } from "@opencode-ai/ui/list"
|
|
||||||
import type { ListRef } from "@opencode-ai/ui/list"
|
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
|
||||||
import { createMemo, createResource, createSignal } from "solid-js"
|
|
||||||
import { useLanguage } from "@/context/language"
|
|
||||||
import { ServerConnection } from "@/context/servers"
|
|
||||||
import { useGlobal } from "@/context/global"
|
|
||||||
import { cleanPickerInput, createDirectorySearch, displayPickerPath } from "./directory-picker-domain"
|
|
||||||
import type { Path } from "@/types"
|
|
||||||
|
|
||||||
interface DialogSelectDirectoryProps {
|
|
||||||
title?: string
|
|
||||||
multiple?: boolean
|
|
||||||
onSelect: (result: string | string[] | null) => void
|
|
||||||
server: ServerConnection.Any
|
|
||||||
}
|
|
||||||
|
|
||||||
const RECENT_PROJECT_LIMIT = 5
|
|
||||||
|
|
||||||
type Row = {
|
|
||||||
absolute: string
|
|
||||||
search: string
|
|
||||||
group: "recent" | "folders"
|
|
||||||
}
|
|
||||||
|
|
||||||
function toRow(absolute: string, home: string, group: Row["group"]): Row {
|
|
||||||
const full = displayPickerPath(absolute, "", "")
|
|
||||||
const tilde = displayPickerPath(full, "~", home)
|
|
||||||
const withSlash = (value: string) => {
|
|
||||||
if (!value) return ""
|
|
||||||
if (value.endsWith("/")) return value
|
|
||||||
return value + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
const search = Array.from(
|
|
||||||
new Set([full, withSlash(full), tilde, withSlash(tilde), getFilename(full)].filter(Boolean)),
|
|
||||||
).join("\n")
|
|
||||||
return { absolute: full, search, group }
|
|
||||||
}
|
|
||||||
|
|
||||||
function uniqueRows(rows: Row[]) {
|
|
||||||
const seen = new Set<string>()
|
|
||||||
return rows.filter((row) => {
|
|
||||||
if (seen.has(row.absolute)) return false
|
|
||||||
seen.add(row.absolute)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DialogSelectDirectory(props: DialogSelectDirectoryProps) {
|
|
||||||
const global = useGlobal()
|
|
||||||
const { sync, sdk, ...serverCtx } = global.ensureServerCtx(props.server)
|
|
||||||
const dialog = useDialog()
|
|
||||||
const language = useLanguage()
|
|
||||||
|
|
||||||
const [filter, setFilter] = createSignal("")
|
|
||||||
let list: ListRef | undefined
|
|
||||||
|
|
||||||
const [fallbackPath] = createResource(
|
|
||||||
() => (!(sync.data.path.home || sync.data.path.directory) ? true : undefined),
|
|
||||||
() =>
|
|
||||||
sdk.api.location
|
|
||||||
.get()
|
|
||||||
.then(
|
|
||||||
(location): Path => ({
|
|
||||||
state: "",
|
|
||||||
config: "",
|
|
||||||
worktree: location.project.directory,
|
|
||||||
directory: location.directory,
|
|
||||||
home: "",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.catch(() => undefined),
|
|
||||||
{ initialValue: undefined },
|
|
||||||
)
|
|
||||||
|
|
||||||
const home = createMemo(() => sync.data.path.home || fallbackPath()?.home || "")
|
|
||||||
const start = createMemo(
|
|
||||||
() => sync.data.path.home || sync.data.path.directory || fallbackPath()?.home || fallbackPath()?.directory,
|
|
||||||
)
|
|
||||||
|
|
||||||
const directories = createDirectorySearch({
|
|
||||||
sdk,
|
|
||||||
home,
|
|
||||||
base: start,
|
|
||||||
})
|
|
||||||
|
|
||||||
const recentProjects = createMemo(() => {
|
|
||||||
const projects = serverCtx.projects.list()
|
|
||||||
const byProject = new Map<string, number>()
|
|
||||||
|
|
||||||
for (const project of projects) {
|
|
||||||
let at = 0
|
|
||||||
const dirs = [project.worktree, ...(project.sandboxes ?? [])]
|
|
||||||
for (const directory of dirs) {
|
|
||||||
const sessions = sync.child(directory, { bootstrap: false })[0].session
|
|
||||||
for (const session of sessions) {
|
|
||||||
if (session.time.archived) continue
|
|
||||||
const updated = session.time.updated ?? session.time.created
|
|
||||||
if (updated > at) at = updated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
byProject.set(project.worktree, at)
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects
|
|
||||||
.map((project, index) => ({ project, at: byProject.get(project.worktree) ?? 0, index }))
|
|
||||||
.sort((a, b) => b.at - a.at || a.index - b.index)
|
|
||||||
.map(({ project }) => {
|
|
||||||
const row = toRow(project.worktree, home(), "recent")
|
|
||||||
const name = project.name || getFilename(project.worktree)
|
|
||||||
return {
|
|
||||||
...row,
|
|
||||||
search: `${row.search}\n${name}`,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const items = async (value: string) => {
|
|
||||||
const results = await directories(value)
|
|
||||||
const directoryRows = results.map((absolute) => toRow(absolute, home(), "folders"))
|
|
||||||
// Cap the idle list only. Once a query narrows the results, every project stays searchable.
|
|
||||||
const recent = recentProjects()
|
|
||||||
const visible = value ? recent : recent.slice(0, RECENT_PROJECT_LIMIT)
|
|
||||||
return uniqueRows([...visible, ...directoryRows])
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolve(absolute: string) {
|
|
||||||
props.onSelect(props.multiple ? [absolute] : absolute)
|
|
||||||
dialog.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog title={props.title ?? language.t("command.project.open")}>
|
|
||||||
<List
|
|
||||||
class="px-3"
|
|
||||||
search={{ placeholder: language.t("dialog.directory.search.placeholder"), autofocus: true }}
|
|
||||||
emptyMessage={language.t("dialog.directory.empty")}
|
|
||||||
loadingMessage={language.t("common.loading")}
|
|
||||||
items={items}
|
|
||||||
key={(x) => x.absolute}
|
|
||||||
filterKeys={["search"]}
|
|
||||||
groupBy={(item) => item.group}
|
|
||||||
sortGroupsBy={(a, b) => {
|
|
||||||
if (a.category === b.category) return 0
|
|
||||||
return a.category === "recent" ? -1 : 1
|
|
||||||
}}
|
|
||||||
groupHeader={(group) =>
|
|
||||||
group.category === "recent" ? language.t("home.recentProjects") : language.t("command.project.open")
|
|
||||||
}
|
|
||||||
ref={(r) => (list = r)}
|
|
||||||
onFilter={(value) => setFilter(cleanPickerInput(value))}
|
|
||||||
onKeyEvent={(e, item) => {
|
|
||||||
if (e.key !== "Tab") return
|
|
||||||
if (e.shiftKey) return
|
|
||||||
if (!item) return
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
const value = displayPickerPath(item.absolute, filter(), home())
|
|
||||||
list?.setFilter(value.endsWith("/") ? value : value + "/")
|
|
||||||
}}
|
|
||||||
onSelect={(path) => {
|
|
||||||
if (!path) return
|
|
||||||
resolve(path.absolute)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(item) => {
|
|
||||||
const path = displayPickerPath(item.absolute, filter(), home())
|
|
||||||
if (path === "~") {
|
|
||||||
return (
|
|
||||||
<div data-directory-path={item.absolute} class="w-full flex items-center justify-between rounded-md">
|
|
||||||
<div class="flex items-center gap-x-3 grow min-w-0">
|
|
||||||
<FileIcon node={{ path: item.absolute, type: "directory" }} class="shrink-0 size-4" />
|
|
||||||
<div class="flex items-center text-14-regular min-w-0">
|
|
||||||
<span class="text-text-strong whitespace-nowrap">~</span>
|
|
||||||
<span class="text-text-weak whitespace-nowrap">/</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div data-directory-path={item.absolute} class="w-full flex items-center justify-between rounded-md">
|
|
||||||
<div class="flex items-center gap-x-3 grow min-w-0">
|
|
||||||
<FileIcon node={{ path: item.absolute, type: "directory" }} class="shrink-0 size-4" />
|
|
||||||
<div class="flex items-center text-14-regular min-w-0">
|
|
||||||
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
|
|
||||||
{getDirectory(path)}
|
|
||||||
</span>
|
|
||||||
<span class="text-text-strong whitespace-nowrap">{getFilename(path)}</span>
|
|
||||||
<span class="text-text-weak whitespace-nowrap">/</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</List>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component, createMemo, Show } from "solid-js"
|
import { Component, createMemo, Show } from "solid-js"
|
||||||
import { useSync } from "@/context/sync"
|
import { useData } from "@/context/server"
|
||||||
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { Switch } from "@opencode-ai/ui/switch"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
@@ -14,12 +15,13 @@ const statusLabels = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const DialogSelectMcp: Component = () => {
|
export const DialogSelectMcp: Component = () => {
|
||||||
const sync = useSync()
|
const data = useData()
|
||||||
|
const sdk = useWorkspaceLocation()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
const items = createMemo(() =>
|
const items = createMemo(() =>
|
||||||
Object.entries(sync().data.mcp ?? {})
|
(data.location.mcp.server.list({ directory: sdk().directory }) ?? [])
|
||||||
.map(([name, status]) => ({ name, status: status.status }))
|
.map((server) => ({ name: server.name, status: server.status.status }))
|
||||||
.sort((a, b) => a.name.localeCompare(b.name)),
|
.sort((a, b) => a.name.localeCompare(b.name)),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,7 +49,8 @@ export const DialogSelectMcp: Component = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(i) => {
|
{(i) => {
|
||||||
const mcpStatus = () => sync().data.mcp[i.name]
|
const mcpStatus = () =>
|
||||||
|
data.location.mcp.server.list({ directory: sdk().directory })?.find((server) => server.name === i.name)?.status
|
||||||
const status = () => mcpStatus()?.status
|
const status = () => mcpStatus()?.status
|
||||||
const statusLabel = () => {
|
const statusLabel = () => {
|
||||||
const key = status() ? statusLabels[status() as keyof typeof statusLabels] : undefined
|
const key = status() ? statusLabels[status() as keyof typeof statusLabels] : undefined
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { ServerConnection } from "@/context/servers"
|
import { ServerConnection } from "@/context/servers"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useSettings } from "@/context/settings"
|
|
||||||
import { lazy } from "solid-js"
|
import { lazy } from "solid-js"
|
||||||
import { DialogSelectDirectory } from "./dialog-select-directory"
|
|
||||||
import { directoryPickerKind } from "./directory-picker-policy"
|
import { directoryPickerKind } from "./directory-picker-policy"
|
||||||
|
|
||||||
const DialogSelectDirectoryV2 = lazy(() =>
|
const DialogSelectDirectoryV2 = lazy(() =>
|
||||||
@@ -19,7 +17,6 @@ type DirectoryPickerInput = {
|
|||||||
|
|
||||||
export function useDirectoryPicker() {
|
export function useDirectoryPicker() {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const settings = useSettings()
|
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
|
||||||
return (input: DirectoryPickerInput) => {
|
return (input: DirectoryPickerInput) => {
|
||||||
@@ -36,10 +33,6 @@ export function useDirectoryPicker() {
|
|||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
if (!selected) input.onSelect(null)
|
if (!selected) input.onSelect(null)
|
||||||
}
|
}
|
||||||
if (platform.platform === "desktop" && settings.general.newLayoutDesigns()) {
|
dialog.show(() => <DialogSelectDirectoryV2 {...input} onSelect={onSelect} />, cancel)
|
||||||
dialog.show(() => <DialogSelectDirectoryV2 {...input} onSelect={onSelect} />, cancel)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dialog.show(() => <DialogSelectDirectory {...input} onSelect={onSelect} />, cancel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
||||||
import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
|
import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
|
||||||
import { type Component, For, Show, createMemo, createResource, createSignal } from "solid-js"
|
import { type Component, For, Show, createEffect, createMemo, createResource, createSignal } from "solid-js"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useMcpToggle } from "@/context/mcp"
|
import { useMcpToggle } from "@/context/mcp"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useData } from "@/context/server"
|
||||||
import { useSync } from "@/context/sync"
|
|
||||||
import { ExternalLink } from "./external-link"
|
import { ExternalLink } from "./external-link"
|
||||||
|
|
||||||
type SkillItem = {
|
type SkillItem = {
|
||||||
@@ -15,7 +14,6 @@ type SkillItem = {
|
|||||||
location: string
|
location: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginName = (item: string | [string, Record<string, unknown>]) => (typeof item === "string" ? item : item[0])
|
|
||||||
const skillKey = (item: SkillItem) => `${item.name}\n${item.location}`
|
const skillKey = (item: SkillItem) => `${item.name}\n${item.location}`
|
||||||
|
|
||||||
const ExtensionCard: Component<{ children: unknown }> = (props) => (
|
const ExtensionCard: Component<{ children: unknown }> = (props) => (
|
||||||
@@ -23,9 +21,8 @@ const ExtensionCard: Component<{ children: unknown }> = (props) => (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const ExtensionRow: Component<{
|
const ExtensionRow: Component<{
|
||||||
icon: "mcp" | "cube" | "post-skill" | "code"
|
icon: "mcp" | "cube" | "post-skill"
|
||||||
name: string
|
name: string
|
||||||
status?: string
|
|
||||||
children?: unknown
|
children?: unknown
|
||||||
}> = (props) => (
|
}> = (props) => (
|
||||||
<div class="project-settings-extension-row">
|
<div class="project-settings-extension-row">
|
||||||
@@ -33,14 +30,6 @@ const ExtensionRow: Component<{
|
|||||||
<Icon name={props.icon} class="project-settings-extension-row-icon" />
|
<Icon name={props.icon} class="project-settings-extension-row-icon" />
|
||||||
<span class="project-settings-extension-row-name">{props.name}</span>
|
<span class="project-settings-extension-row-name">{props.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<Show when={props.status}>
|
|
||||||
{(status) => (
|
|
||||||
<span class="project-settings-extension-row-status">
|
|
||||||
<span class="project-settings-extension-row-status-dot" />
|
|
||||||
{status()}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
{props.children as any}
|
{props.children as any}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -75,56 +64,55 @@ const SharedSection: Component<{
|
|||||||
export const ProjectSettingsExtensions: Component = () => {
|
export const ProjectSettingsExtensions: Component = () => {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
const directorySDK = useSDK()
|
const directorySDK = useWorkspaceLocation()
|
||||||
const serverSync = useServerSync()
|
const data = useData()
|
||||||
const sync = useSync()
|
const toggleMcp = useMcpToggle(() => directorySDK().directory)
|
||||||
const toggleMcp = useMcpToggle()
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (serverSDK.connection.status() !== "connected") return
|
||||||
|
const ref = { directory: directorySDK().directory }
|
||||||
|
void Promise.all([
|
||||||
|
data.location.mcp.server.sync(),
|
||||||
|
data.location.skill.sync(),
|
||||||
|
data.location.mcp.server.sync(ref),
|
||||||
|
data.location.skill.sync(ref),
|
||||||
|
]).catch(() => undefined)
|
||||||
|
})
|
||||||
|
|
||||||
const [serverMcp] = createResource(
|
|
||||||
serverSDK,
|
|
||||||
(sdk) =>
|
|
||||||
sdk.api.mcp
|
|
||||||
.list()
|
|
||||||
.then((result) => Object.fromEntries(result.data.map((server) => [server.name, server.status])))
|
|
||||||
.catch(() => ({})),
|
|
||||||
{ initialValue: {} },
|
|
||||||
)
|
|
||||||
const globalMcpNames = createMemo(() =>
|
const globalMcpNames = createMemo(() =>
|
||||||
[...new Set([...Object.keys(serverSync.data.config.mcp ?? {}), ...Object.keys(serverMcp.latest)])].sort(),
|
[...new Set((data.location.mcp.server.list() ?? []).map((server) => server.name))].sort(),
|
||||||
)
|
)
|
||||||
const projectMcpNames = createMemo(() => {
|
const projectMcpNames = createMemo(() => {
|
||||||
const shared = new Set(globalMcpNames())
|
const shared = new Set(globalMcpNames())
|
||||||
const configured = Object.keys(sync().data.config.mcp ?? {}).filter((name) => !shared.has(name))
|
return (data.location.mcp.server.list({ directory: directorySDK().directory }) ?? [])
|
||||||
if (configured.length > 0) return configured.sort()
|
.map((server) => server.name)
|
||||||
return Object.keys(sync().data.mcp ?? {})
|
|
||||||
.filter((name) => !shared.has(name))
|
.filter((name) => !shared.has(name))
|
||||||
.sort()
|
.sort()
|
||||||
})
|
})
|
||||||
const mcpEnabled = (name: string) => sync().data.mcp?.[name]?.status === "connected"
|
const mcpEnabled = (name: string) =>
|
||||||
|
data.location.mcp.server.list({ directory: directorySDK().directory })?.find((server) => server.name === name)?.status
|
||||||
|
.status === "connected"
|
||||||
|
|
||||||
const globalPlugins = createMemo(() => (serverSync.data.config.plugin ?? []).map(pluginName))
|
const [globalPluginList] = createResource(
|
||||||
|
() => serverSDK.connection.status() === "connected",
|
||||||
|
() => serverSDK.api.plugin.list().then((result) => result.data),
|
||||||
|
)
|
||||||
|
const [projectPluginList] = createResource(
|
||||||
|
() => (serverSDK.connection.status() === "connected" ? directorySDK().directory : undefined),
|
||||||
|
(directory) => serverSDK.api.plugin.list({ location: { directory } }).then((result) => result.data),
|
||||||
|
)
|
||||||
|
const globalPlugins = createMemo(() => (globalPluginList.latest ?? []).map((item) => item.id))
|
||||||
const projectPlugins = createMemo(() => {
|
const projectPlugins = createMemo(() => {
|
||||||
const shared = new Set(globalPlugins())
|
const shared = new Set(globalPlugins())
|
||||||
return (sync().data.config.plugin ?? []).map(pluginName).filter((name) => !shared.has(name))
|
return (projectPluginList.latest ?? []).map((item) => item.id).filter((name) => !shared.has(name))
|
||||||
})
|
})
|
||||||
|
|
||||||
const [serverSkills] = createResource(
|
const serverSkills = createMemo(() => data.location.skill.list() ?? [])
|
||||||
serverSDK,
|
|
||||||
(sdk): Promise<SkillItem[]> =>
|
|
||||||
sdk.api.skill.list().then((result) => result.data.map((item) => ({ name: item.name, location: item.location }))),
|
|
||||||
{ initialValue: [] },
|
|
||||||
)
|
|
||||||
const [directorySkills] = createResource(
|
|
||||||
directorySDK,
|
|
||||||
(sdk): Promise<SkillItem[]> =>
|
|
||||||
sdk.api.skill
|
|
||||||
.list({ location: { directory: sdk.directory } })
|
|
||||||
.then((result) => result.data.map((item) => ({ name: item.name, location: item.location }))),
|
|
||||||
{ initialValue: [] },
|
|
||||||
)
|
|
||||||
const projectSkills = createMemo(() => {
|
const projectSkills = createMemo(() => {
|
||||||
const shared = new Set(serverSkills.latest.map(skillKey))
|
const shared = new Set(serverSkills().map(skillKey))
|
||||||
return directorySkills.latest.filter((item) => !shared.has(skillKey(item)))
|
return (data.location.skill.list({ directory: directorySDK().directory }) ?? []).filter(
|
||||||
|
(item) => !shared.has(skillKey(item)),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const mcpRows = (items: string[]) => (
|
const mcpRows = (items: string[]) => (
|
||||||
@@ -165,7 +153,7 @@ export const ProjectSettingsExtensions: Component = () => {
|
|||||||
<TabsV2.Trigger value="mcps">{language.t("settings.extensions.tab.mcps")}</TabsV2.Trigger>
|
<TabsV2.Trigger value="mcps">{language.t("settings.extensions.tab.mcps")}</TabsV2.Trigger>
|
||||||
<TabsV2.Trigger value="plugins">{language.t("status.popover.tab.plugins")}</TabsV2.Trigger>
|
<TabsV2.Trigger value="plugins">{language.t("status.popover.tab.plugins")}</TabsV2.Trigger>
|
||||||
<TabsV2.Trigger value="skills">{language.t("settings.extensions.tab.skills")}</TabsV2.Trigger>
|
<TabsV2.Trigger value="skills">{language.t("settings.extensions.tab.skills")}</TabsV2.Trigger>
|
||||||
<TabsV2.Trigger value="lsps">{language.t("project.settings.extensions.tab.lsps")}</TabsV2.Trigger>
|
{/* TODO: Restore LSP status when V2 exposes it. */}
|
||||||
</TabsV2.List>
|
</TabsV2.List>
|
||||||
|
|
||||||
<TabsV2.Content value="mcps">
|
<TabsV2.Content value="mcps">
|
||||||
@@ -205,33 +193,10 @@ export const ProjectSettingsExtensions: Component = () => {
|
|||||||
<Show when={projectSkills().length > 0}>
|
<Show when={projectSkills().length > 0}>
|
||||||
<ExtensionCard>{skillRows(projectSkills())}</ExtensionCard>
|
<ExtensionCard>{skillRows(projectSkills())}</ExtensionCard>
|
||||||
</Show>
|
</Show>
|
||||||
<SharedSection count={serverSkills.latest.length}>{skillRows(serverSkills.latest)}</SharedSection>
|
<SharedSection count={serverSkills().length}>{skillRows(serverSkills())}</SharedSection>
|
||||||
</div>
|
</div>
|
||||||
</TabsV2.Content>
|
</TabsV2.Content>
|
||||||
|
|
||||||
<TabsV2.Content value="lsps">
|
|
||||||
<div class="project-settings-extension-section">
|
|
||||||
<div class="project-settings-extension-section-header">
|
|
||||||
<span>{language.t("project.settings.extensions.lsp.detected")}</span>
|
|
||||||
<span>{language.t("project.settings.extensions.lsp.description")}</span>
|
|
||||||
</div>
|
|
||||||
<Show when={sync().data.lsp.length > 0}>
|
|
||||||
<ExtensionCard>
|
|
||||||
<For each={sync().data.lsp}>
|
|
||||||
{(item) => (
|
|
||||||
<ExtensionRow
|
|
||||||
icon="code"
|
|
||||||
name={item.name || item.id}
|
|
||||||
status={
|
|
||||||
item.status === "error" ? language.t("project.settings.extensions.setupRequired") : undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</ExtensionCard>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</TabsV2.Content>
|
|
||||||
</TabsV2>
|
</TabsV2>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import { useLayout } from "@/context/layout"
|
|||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { type ImageAttachmentPart, usePrompt } from "@/context/prompt"
|
import { type ImageAttachmentPart, usePrompt } from "@/context/prompt"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { useSync } from "@/context/sync"
|
import { useData } from "@/context/server"
|
||||||
import { createSessionTabs } from "@/pages/session/helpers"
|
import { createSessionTabs } from "@/pages/session/helpers"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { PromptInputV2, type PromptInputV2Suggestion } from "@opencode-ai/session-ui/v2/prompt-input"
|
import { PromptInputV2, type PromptInputV2Suggestion } from "@opencode-ai/session-ui/v2/prompt-input"
|
||||||
@@ -40,7 +40,7 @@ export type PromptInputV2ComposerProps = {
|
|||||||
accentSubmit?: boolean
|
accentSubmit?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptInputV2ControllerProps = Omit<PromptInputProps, "class" | "submission">
|
export type PromptInputV2ControllerProps = Omit<PromptInputProps, "class">
|
||||||
export type PromptInputV2ComposerController = PromptInputV2Interaction & {
|
export type PromptInputV2ComposerController = PromptInputV2Interaction & {
|
||||||
readonly model: PromptInputProps["controls"]["model"]
|
readonly model: PromptInputProps["controls"]["model"]
|
||||||
}
|
}
|
||||||
@@ -81,8 +81,8 @@ export function PromptInputV2Composer(props: PromptInputV2ComposerProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function usePromptInputV2Controller(props: PromptInputV2ControllerProps): PromptInputV2ComposerController {
|
export function usePromptInputV2Controller(props: PromptInputV2ControllerProps): PromptInputV2ComposerController {
|
||||||
const sdk = useSDK()
|
const sdk = useWorkspaceLocation()
|
||||||
const sync = useSync()
|
const data = useData()
|
||||||
const files = useFile()
|
const files = useFile()
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const comments = useComments()
|
const comments = useComments()
|
||||||
@@ -113,8 +113,8 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps):
|
|||||||
return [...result, path]
|
return [...result, path]
|
||||||
}, [])
|
}, [])
|
||||||
})
|
})
|
||||||
const info = createMemo(() => (props.controls.session.id ? sync().session.get(props.controls.session.id) : undefined))
|
const info = createMemo(() => (props.controls.session.id ? data.session.get(props.controls.session.id) : undefined))
|
||||||
const working = createMemo(() => sync().data.session_working(props.controls.session.id ?? ""))
|
const working = createMemo(() => data.session.status(props.controls.session.id ?? "") === "running")
|
||||||
const attachments = createMemo(() =>
|
const attachments = createMemo(() =>
|
||||||
prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"),
|
prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"),
|
||||||
)
|
)
|
||||||
@@ -197,37 +197,39 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps):
|
|||||||
if (!id) return permission.isAutoAcceptingDirectory(sdk().directory)
|
if (!id) return permission.isAutoAcceptingDirectory(sdk().directory)
|
||||||
return permission.isAutoAccepting(id, sdk().directory)
|
return permission.isAutoAccepting(id, sdk().directory)
|
||||||
})
|
})
|
||||||
const submission = createPromptSubmit({
|
const submission =
|
||||||
prompt,
|
props.submission ??
|
||||||
info,
|
createPromptSubmit({
|
||||||
imageAttachments: attachments,
|
prompt,
|
||||||
commentCount,
|
info,
|
||||||
autoAccept: accepting,
|
imageAttachments: attachments,
|
||||||
mode,
|
commentCount,
|
||||||
working,
|
autoAccept: accepting,
|
||||||
editor: () => editor,
|
mode,
|
||||||
queueScroll: () => requestAnimationFrame(() => editor?.scrollIntoView({ block: "nearest" })),
|
working,
|
||||||
promptLength,
|
editor: () => editor,
|
||||||
addToHistory: (value, mode) => controller.addHistory(value, mode),
|
queueScroll: () => requestAnimationFrame(() => editor?.scrollIntoView({ block: "nearest" })),
|
||||||
resetHistoryNavigation: () => controller.resetHistory(),
|
promptLength,
|
||||||
setMode: (next) => controller.dispatch({ type: next === "shell" ? "mode.shell" : "mode.normal" }),
|
addToHistory: (value, mode) => controller.addHistory(value, mode),
|
||||||
setPopover: (popover) => {
|
resetHistoryNavigation: () => controller.resetHistory(),
|
||||||
if (!popover) controller.dispatch({ type: "popover.close" })
|
setMode: (next) => controller.dispatch({ type: next === "shell" ? "mode.shell" : "mode.normal" }),
|
||||||
},
|
setPopover: (popover) => {
|
||||||
newSessionWorktree: () => props.newSessionWorktree,
|
if (!popover) controller.dispatch({ type: "popover.close" })
|
||||||
onNewSessionWorktreeReset: props.onNewSessionWorktreeReset,
|
},
|
||||||
shouldQueue: props.shouldQueue,
|
newSessionWorktree: () => props.newSessionWorktree,
|
||||||
onQueue: props.onQueue,
|
onNewSessionWorktreeReset: props.onNewSessionWorktreeReset,
|
||||||
onAbort: props.onAbort,
|
shouldQueue: props.shouldQueue,
|
||||||
onSubmit: props.onSubmit,
|
onQueue: props.onQueue,
|
||||||
model: props.controls.model.selection,
|
onAbort: props.onAbort,
|
||||||
})
|
onSubmit: props.onSubmit,
|
||||||
|
model: props.controls.model.selection,
|
||||||
|
})
|
||||||
|
|
||||||
const referenceDescription = (reference: ReferenceInfo) =>
|
const referenceDescription = (reference: ReferenceInfo) =>
|
||||||
reference.source.type === "git" ? reference.source.repository : reference.source.path
|
reference.source.type === "git" ? reference.source.repository : reference.source.path
|
||||||
const references = createMemo(() =>
|
const references = createMemo(() =>
|
||||||
sync()
|
(data.location.reference.list({ directory: sdk().directory }) ?? [])
|
||||||
.data.reference.filter((reference) => !reference.hidden)
|
.filter((reference) => !reference.hidden)
|
||||||
.map((reference) => ({
|
.map((reference) => ({
|
||||||
id: `reference:${reference.name}`,
|
id: `reference:${reference.name}`,
|
||||||
kind: "reference" as const,
|
kind: "reference" as const,
|
||||||
@@ -246,7 +248,7 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps):
|
|||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
const resources = createMemo(() =>
|
const resources = createMemo(() =>
|
||||||
Object.values(sync().data.mcp_resource).map((resource) => ({
|
(data.location.mcp.resource.list({ directory: sdk().directory }) ?? []).map((resource) => ({
|
||||||
id: `resource:${resource.server}:${resource.uri}`,
|
id: `resource:${resource.server}:${resource.uri}`,
|
||||||
kind: "resource" as const,
|
kind: "resource" as const,
|
||||||
label: `@${resource.name}`,
|
label: `@${resource.name}`,
|
||||||
@@ -292,7 +294,7 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps):
|
|||||||
})),
|
})),
|
||||||
])
|
])
|
||||||
const slashCommands = createMemo(() => [
|
const slashCommands = createMemo(() => [
|
||||||
...sync().data.command.map((item) => ({
|
...(data.location.command.list({ directory: sdk().directory }) ?? []).map((item) => ({
|
||||||
id: `custom.${item.name}`,
|
id: `custom.${item.name}`,
|
||||||
trigger: item.name,
|
trigger: item.name,
|
||||||
title: item.name,
|
title: item.name,
|
||||||
@@ -352,7 +354,7 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps):
|
|||||||
dialog.show(() => <ImagePreview src={attachment.blob.url} alt={attachment.filename} />),
|
dialog.show(() => <ImagePreview src={attachment.blob.url} alt={attachment.filename} />),
|
||||||
openContext(key) {
|
openContext(key) {
|
||||||
const item = controller.contextItem(key)
|
const item = controller.contextItem(key)
|
||||||
if (item) openComment(item, props, sync, layout, files, comments)
|
if (item) openComment(item, props, layout, files, comments)
|
||||||
},
|
},
|
||||||
onEditor(element) {
|
onEditor(element) {
|
||||||
editor = element as HTMLDivElement
|
editor = element as HTMLDivElement
|
||||||
@@ -555,7 +557,6 @@ function PromptInputV2ModelControl(props: {
|
|||||||
function openComment(
|
function openComment(
|
||||||
item: { path: string; commentID?: string; commentOrigin?: "review" | "file" },
|
item: { path: string; commentID?: string; commentOrigin?: "review" | "file" },
|
||||||
props: PromptInputV2ControllerProps,
|
props: PromptInputV2ControllerProps,
|
||||||
sync: ReturnType<typeof useSync>,
|
|
||||||
layout: ReturnType<typeof useLayout>,
|
layout: ReturnType<typeof useLayout>,
|
||||||
files: ReturnType<typeof useFile>,
|
files: ReturnType<typeof useFile>,
|
||||||
comments: ReturnType<typeof useComments>,
|
comments: ReturnType<typeof useComments>,
|
||||||
@@ -573,9 +574,7 @@ function openComment(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const diffs = props.controls.session.id ? sync().data.session_diff[props.controls.session.id] : undefined
|
const review = item.commentOrigin === "review"
|
||||||
const review =
|
|
||||||
item.commentOrigin === "review" || (item.commentOrigin !== "file" && diffs?.some((diff) => diff.file === item.path))
|
|
||||||
if (!props.controls.session.reviewPanel.opened()) props.controls.session.reviewPanel.open()
|
if (!props.controls.session.reviewPanel.opened()) props.controls.session.reviewPanel.open()
|
||||||
if (review) {
|
if (review) {
|
||||||
layout.fileTree.setTab("changes")
|
layout.fileTree.setTab("changes")
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import type { Todo } from "@/types"
|
|
||||||
import { createPromptState } from "@/context/prompt"
|
import { createPromptState } from "@/context/prompt"
|
||||||
import { SessionComposerRegion, createSessionComposerRegionController } from "@/pages/session/composer"
|
|
||||||
import { createPromptInputHistory, PromptInput } from "./prompt-input"
|
import { createPromptInputHistory, PromptInput } from "./prompt-input"
|
||||||
|
|
||||||
function createPromptInputStoryRuntime() {
|
function createPromptInputStoryRuntime() {
|
||||||
@@ -111,93 +109,6 @@ function PromptInputExample() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const todos: Todo[] = [
|
|
||||||
{ id: "todo-1", content: "Inspect the session composer animation", status: "completed" },
|
|
||||||
{ id: "todo-2", content: "Keep the dock settled on initial render", status: "in_progress" },
|
|
||||||
{ id: "todo-3", content: "Verify session navigation behavior", status: "pending" },
|
|
||||||
]
|
|
||||||
|
|
||||||
function PromptInputWithOpenDock() {
|
|
||||||
const input = createPromptInputStoryRuntime()
|
|
||||||
const [controls, setControls] = createStore({
|
|
||||||
agent: "build",
|
|
||||||
activeTab: undefined as string | undefined,
|
|
||||||
todoCollapsed: false,
|
|
||||||
})
|
|
||||||
const inputControls = {
|
|
||||||
agents: {
|
|
||||||
available: [],
|
|
||||||
options: ["build"],
|
|
||||||
get current() {
|
|
||||||
return controls.agent
|
|
||||||
},
|
|
||||||
loading: false,
|
|
||||||
visible: true,
|
|
||||||
select: (agent?: string) => setControls("agent", agent ?? "build"),
|
|
||||||
},
|
|
||||||
model: {
|
|
||||||
selection: {
|
|
||||||
current: () => ({ id: "claude-3-7-sonnet", name: "Claude 3.7 Sonnet", provider: { id: "anthropic" } }),
|
|
||||||
variant: { list: () => [], current: () => undefined, set: () => {} },
|
|
||||||
},
|
|
||||||
paid: true,
|
|
||||||
loading: false,
|
|
||||||
},
|
|
||||||
session: {
|
|
||||||
id: "story-session",
|
|
||||||
tabs: {
|
|
||||||
active: () => controls.activeTab,
|
|
||||||
all: () => [],
|
|
||||||
open: () => {},
|
|
||||||
setActive: (tab: string) => setControls("activeTab", tab),
|
|
||||||
},
|
|
||||||
reviewPanel: { opened: () => false, open: () => {} },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const state = {
|
|
||||||
blocked: () => false,
|
|
||||||
questionRequest: () => undefined,
|
|
||||||
permissionRequest: () => undefined,
|
|
||||||
permissionResponding: () => false,
|
|
||||||
decide: () => {},
|
|
||||||
todos: () => todos,
|
|
||||||
dock: () => true,
|
|
||||||
closing: () => false,
|
|
||||||
opening: () => false,
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<SessionComposerRegion
|
|
||||||
controller={createSessionComposerRegionController({
|
|
||||||
state,
|
|
||||||
sessionKey: () => "story-session",
|
|
||||||
sessionID: () => "story-session",
|
|
||||||
prompt: input.state,
|
|
||||||
ready: () => true,
|
|
||||||
centered: () => false,
|
|
||||||
todo: {
|
|
||||||
collapsed: () => controls.todoCollapsed,
|
|
||||||
onToggle: () => setControls("todoCollapsed", (collapsed) => !collapsed),
|
|
||||||
},
|
|
||||||
followup: () => undefined,
|
|
||||||
revert: () => undefined,
|
|
||||||
onResponseSubmit: () => {},
|
|
||||||
openParent: () => {},
|
|
||||||
setPromptRef: () => {},
|
|
||||||
setDockRef: () => {},
|
|
||||||
})}
|
|
||||||
promptInput={
|
|
||||||
<PromptInput
|
|
||||||
controls={inputControls}
|
|
||||||
{...input}
|
|
||||||
ref={() => {}}
|
|
||||||
newSessionWorktree=""
|
|
||||||
onNewSessionWorktreeReset={() => {}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "App/PromptInput",
|
title: "App/PromptInput",
|
||||||
id: "app-prompt-input",
|
id: "app-prompt-input",
|
||||||
@@ -212,12 +123,3 @@ export const Basic = {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DockAlreadyOpen = {
|
|
||||||
render: () => (
|
|
||||||
<div class="pt-10">
|
|
||||||
<h1 class="mb-4">Prompt Input with open Todo dock</h1>
|
|
||||||
<PromptInputWithOpenDock />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,98 +0,0 @@
|
|||||||
import { Component, For, Show } from "solid-js"
|
|
||||||
import { Dynamic } from "solid-js/web"
|
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
|
||||||
import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/core/util/path"
|
|
||||||
import type { ContextItem } from "@/context/prompt"
|
|
||||||
|
|
||||||
type PromptContextItem = ContextItem & { key: string }
|
|
||||||
|
|
||||||
type ContextItemsProps = {
|
|
||||||
items: PromptContextItem[]
|
|
||||||
active: (item: PromptContextItem) => boolean
|
|
||||||
openComment: (item: PromptContextItem) => void
|
|
||||||
remove: (item: PromptContextItem) => void
|
|
||||||
newLayoutDesigns: boolean
|
|
||||||
t: (key: string) => string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PromptContextItems: Component<ContextItemsProps> = (props) => {
|
|
||||||
return (
|
|
||||||
<Show when={props.items.length > 0}>
|
|
||||||
<div class="flex flex-nowrap items-start gap-2 p-2 overflow-x-auto no-scrollbar">
|
|
||||||
<For each={props.items}>
|
|
||||||
{(item) => {
|
|
||||||
const directory = getDirectory(item.path)
|
|
||||||
const filename = getFilename(item.path)
|
|
||||||
const label = getFilenameTruncated(item.path, 14)
|
|
||||||
const selected = props.active(item)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dynamic
|
|
||||||
component={props.newLayoutDesigns ? TooltipV2 : Tooltip}
|
|
||||||
value={
|
|
||||||
<span class="flex max-w-[300px]">
|
|
||||||
<span
|
|
||||||
classList={{
|
|
||||||
"truncate-start [unicode-bidi:plaintext] min-w-0": true,
|
|
||||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
|
||||||
"text-text-invert-base": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{directory}
|
|
||||||
</span>
|
|
||||||
<span class="shrink-0">{filename}</span>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
placement="top"
|
|
||||||
openDelay={800}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
classList={{
|
|
||||||
"group shrink-0 flex flex-col rounded-[6px] pl-2 pr-1 py-1 max-w-[200px] h-12 cursor-default transition-all transition-transform shadow-xs-border hover:shadow-xs-border-hover": true,
|
|
||||||
"hover:bg-surface-interactive-weak": !!item.commentID && !selected,
|
|
||||||
"bg-surface-interactive-hover hover:bg-surface-interactive-hover shadow-xs-border-hover": selected,
|
|
||||||
"bg-background-stronger": !selected,
|
|
||||||
}}
|
|
||||||
onClick={() => props.openComment(item)}
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-1.5">
|
|
||||||
<FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" />
|
|
||||||
<div class="flex items-center text-[12px] min-w-0 font-medium leading-5">
|
|
||||||
<span class="text-text-strong whitespace-nowrap">{label}</span>
|
|
||||||
<Show when={item.selection}>
|
|
||||||
{(sel) => (
|
|
||||||
<span class="text-text-weak whitespace-nowrap shrink-0">
|
|
||||||
{sel().startLine === sel().endLine
|
|
||||||
? `:${sel().startLine}`
|
|
||||||
: `:${sel().startLine}-${sel().endLine}`}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<IconButton
|
|
||||||
type="button"
|
|
||||||
icon="close-small"
|
|
||||||
variant="ghost"
|
|
||||||
class="ml-auto size-3.5 text-text-weak hover:text-text-strong transition-all"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
props.remove(item)
|
|
||||||
}}
|
|
||||||
aria-label={props.t("prompt.context.removeFile")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Show when={item.comment}>
|
|
||||||
{(comment) => <div class="text-12-regular text-text-strong ml-5 pr-1 truncate">{comment()}</div>}
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</Dynamic>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { Component, Show } from "solid-js"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
|
|
||||||
type PromptDragOverlayProps = {
|
|
||||||
type: "image" | "@mention" | null
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const kindToIcon = {
|
|
||||||
image: "photo",
|
|
||||||
"@mention": "link",
|
|
||||||
} as const
|
|
||||||
|
|
||||||
export const PromptDragOverlay: Component<PromptDragOverlayProps> = (props) => {
|
|
||||||
return (
|
|
||||||
<Show when={props.type !== null}>
|
|
||||||
<div class="absolute inset-0 z-10 flex items-center justify-center bg-surface-raised-stronger-non-alpha/90 pointer-events-none">
|
|
||||||
<div class="flex flex-col items-center gap-2 text-text-weak">
|
|
||||||
<Icon name={props.type ? kindToIcon[props.type] : kindToIcon.image} class="size-8" />
|
|
||||||
<span class="text-14-regular">{props.label}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
@keyframes prompt-attachments-fade-left {
|
|
||||||
from {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes prompt-attachments-fade-right {
|
|
||||||
from {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="prompt-attachments"] {
|
|
||||||
timeline-scope: --prompt-attachments-scroll;
|
|
||||||
|
|
||||||
[data-slot^="prompt-attachments-fade-"] {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@supports (animation-timeline: --prompt-attachments-scroll) and (timeline-scope: --prompt-attachments-scroll) {
|
|
||||||
[data-slot="prompt-attachments-scroll"] {
|
|
||||||
scroll-timeline: --prompt-attachments-scroll x;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="prompt-attachments-fade-left"] {
|
|
||||||
animation: prompt-attachments-fade-left linear both;
|
|
||||||
animation-timeline: --prompt-attachments-scroll;
|
|
||||||
animation-range: 0 0.1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="prompt-attachments-fade-right"] {
|
|
||||||
animation: prompt-attachments-fade-right linear both;
|
|
||||||
animation-timeline: --prompt-attachments-scroll;
|
|
||||||
animation-range: calc(100% - 1.1px) calc(100% - 1px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
import { Component, For, Show } from "solid-js"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
|
||||||
import { AttachmentCardV2 } from "@opencode-ai/session-ui/v2/attachment-card-v2"
|
|
||||||
import { CommentCardV2 } from "@opencode-ai/session-ui/v2/comment-card-v2"
|
|
||||||
import { typeLabel } from "@opencode-ai/session-ui/message-file"
|
|
||||||
import type { ContextItem, ImageAttachmentPart } from "@/context/prompt"
|
|
||||||
import "./image-attachments.css"
|
|
||||||
|
|
||||||
type PromptCommentItem = ContextItem & { key: string }
|
|
||||||
|
|
||||||
type PromptImageAttachmentsProps = {
|
|
||||||
attachments: ImageAttachmentPart[]
|
|
||||||
onOpen: (attachment: ImageAttachmentPart) => void
|
|
||||||
onRemove: (id: string) => void
|
|
||||||
removeLabel: string
|
|
||||||
fileLabel: string
|
|
||||||
newLayoutDesigns: boolean
|
|
||||||
comments?: PromptCommentItem[]
|
|
||||||
commentActive?: (item: PromptCommentItem) => boolean
|
|
||||||
onOpenComment?: (item: PromptCommentItem) => void
|
|
||||||
onRemoveComment?: (item: PromptCommentItem) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const fallbackClass = "size-16 rounded-md bg-surface-base flex items-center justify-center border border-border-base"
|
|
||||||
const imageClass =
|
|
||||||
"size-16 rounded-md object-cover border border-border-base hover:border-border-strong-base transition-colors"
|
|
||||||
const imageClassV2 = "w-[58px] h-[46px] rounded-[6px] object-cover"
|
|
||||||
// inset box-shadows do not paint over <img> content, so the hairline is a separate overlay
|
|
||||||
const imageHairlineClassV2 =
|
|
||||||
"absolute inset-0 rounded-[6px] shadow-[inset_0_0_0_0.5px_var(--v2-border-border-base)] pointer-events-none"
|
|
||||||
const removeClass =
|
|
||||||
"absolute -top-1.5 -right-1.5 size-5 rounded-full bg-surface-raised-stronger-non-alpha border border-border-base flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity hover:bg-surface-raised-base-hover"
|
|
||||||
const removeClassV2 =
|
|
||||||
"absolute -top-1 -right-1 size-4 rounded-full bg-v2-icon-icon-muted outline-solid outline-1 outline-v2-icon-icon-contrast flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
|
|
||||||
const nameClass = "absolute bottom-0 left-0 right-0 px-1 py-0.5 bg-black/50 rounded-b-md"
|
|
||||||
|
|
||||||
export const PromptImageAttachments: Component<PromptImageAttachmentsProps> = (props) => {
|
|
||||||
return (
|
|
||||||
<Show when={props.attachments.length > 0 || (props.newLayoutDesigns && (props.comments?.length ?? 0) > 0)}>
|
|
||||||
<div data-slot="prompt-attachments" classList={{ relative: props.newLayoutDesigns }}>
|
|
||||||
<div
|
|
||||||
data-slot="prompt-attachments-scroll"
|
|
||||||
classList={{
|
|
||||||
"flex gap-2": true,
|
|
||||||
"flex-nowrap overflow-x-auto no-scrollbar px-2 pt-2 pb-1": props.newLayoutDesigns,
|
|
||||||
"flex-wrap px-3 pt-3": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Show when={props.newLayoutDesigns}>
|
|
||||||
<For each={props.comments ?? []}>
|
|
||||||
{(item) => (
|
|
||||||
<div class="relative group shrink-0">
|
|
||||||
<TooltipV2
|
|
||||||
value={item.comment}
|
|
||||||
placement="top"
|
|
||||||
openDelay={800}
|
|
||||||
contentClass="max-w-[300px] break-words"
|
|
||||||
>
|
|
||||||
<CommentCardV2
|
|
||||||
comment={item.comment ?? ""}
|
|
||||||
path={item.path}
|
|
||||||
selection={item.selection}
|
|
||||||
active={props.commentActive?.(item)}
|
|
||||||
onClick={() => props.onOpenComment?.(item)}
|
|
||||||
/>
|
|
||||||
</TooltipV2>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => props.onRemoveComment?.(item)}
|
|
||||||
class={removeClassV2}
|
|
||||||
aria-label={props.removeLabel}
|
|
||||||
>
|
|
||||||
<IconV2 name="outline-xmark" class="text-v2-icon-icon-contrast" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
<For each={props.attachments}>
|
|
||||||
{(attachment) => {
|
|
||||||
const image = attachment.mime.startsWith("image/")
|
|
||||||
const media = () => (
|
|
||||||
<Show
|
|
||||||
when={image}
|
|
||||||
fallback={
|
|
||||||
<Show
|
|
||||||
when={props.newLayoutDesigns}
|
|
||||||
fallback={
|
|
||||||
<div class={fallbackClass}>
|
|
||||||
<Icon name="folder" class="size-6 text-text-weak" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<AttachmentCardV2 title={attachment.filename}>
|
|
||||||
{typeLabel(attachment.filename, attachment.mime, props.fileLabel)}
|
|
||||||
</AttachmentCardV2>
|
|
||||||
</Show>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={attachment.blob.url}
|
|
||||||
alt={attachment.filename}
|
|
||||||
class={props.newLayoutDesigns ? imageClassV2 : imageClass}
|
|
||||||
onClick={() => props.onOpen(attachment)}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
const name = () => (
|
|
||||||
<div class={nameClass}>
|
|
||||||
<span class="text-10-regular text-white truncate block">{attachment.filename}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
const remove = () => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => props.onRemove(attachment.id)}
|
|
||||||
class={props.newLayoutDesigns ? removeClassV2 : removeClass}
|
|
||||||
aria-label={props.removeLabel}
|
|
||||||
>
|
|
||||||
<Show when={props.newLayoutDesigns} fallback={<Icon name="close" class="size-3 text-text-weak" />}>
|
|
||||||
<IconV2 name="outline-xmark" class="text-v2-icon-icon-contrast" />
|
|
||||||
</Show>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
// v2 keeps the remove button outside the tooltip trigger so hovering it dismisses the tooltip
|
|
||||||
return (
|
|
||||||
<Show
|
|
||||||
when={props.newLayoutDesigns}
|
|
||||||
fallback={
|
|
||||||
<Tooltip value={attachment.filename} placement="top" contentClass="break-all">
|
|
||||||
<div class="relative group">
|
|
||||||
{media()}
|
|
||||||
{name()}
|
|
||||||
{remove()}
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div class="relative group shrink-0">
|
|
||||||
<TooltipV2 value={attachment.filename} placement="top" contentClass="break-all">
|
|
||||||
{media()}
|
|
||||||
<Show when={image}>
|
|
||||||
<div class={imageHairlineClassV2} />
|
|
||||||
</Show>
|
|
||||||
</TooltipV2>
|
|
||||||
{remove()}
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
<Show when={props.newLayoutDesigns}>
|
|
||||||
<div
|
|
||||||
data-slot="prompt-attachments-fade-left"
|
|
||||||
class="pointer-events-none absolute inset-y-0 left-0 z-10 w-6 bg-[linear-gradient(to_right,var(--v2-background-bg-base),transparent)]"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
data-slot="prompt-attachments-fade-right"
|
|
||||||
class="pointer-events-none absolute inset-y-0 right-0 z-10 w-6 bg-[linear-gradient(to_left,var(--v2-background-bg-base),transparent)]"
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,376 +0,0 @@
|
|||||||
import { Component, For, Match, Show, Switch } from "solid-js"
|
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
import { Tag } from "@opencode-ai/ui/v2/badge-v2"
|
|
||||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
|
||||||
|
|
||||||
export type AtOption =
|
|
||||||
| { type: "agent"; name: string; display: string }
|
|
||||||
| {
|
|
||||||
type: "resource"
|
|
||||||
name: string
|
|
||||||
uri: string
|
|
||||||
client: string
|
|
||||||
display: string
|
|
||||||
description?: string
|
|
||||||
mime?: string
|
|
||||||
}
|
|
||||||
| { type: "reference"; name: string; path: string; display: string; description: string }
|
|
||||||
| { type: "file"; path: string; display: string; recent?: boolean }
|
|
||||||
|
|
||||||
export interface SlashCommand {
|
|
||||||
id: string
|
|
||||||
trigger: string
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
keybind?: string
|
|
||||||
type: "builtin" | "custom"
|
|
||||||
source?: "command" | "mcp" | "skill"
|
|
||||||
}
|
|
||||||
|
|
||||||
type PromptPopoverProps = {
|
|
||||||
popover: "at" | "slash" | null
|
|
||||||
setSlashPopoverRef: (el: HTMLDivElement) => void
|
|
||||||
atFlat: AtOption[]
|
|
||||||
atActive?: string
|
|
||||||
atKey: (item: AtOption) => string
|
|
||||||
setAtActive: (id: string) => void
|
|
||||||
onAtSelect: (item: AtOption) => void
|
|
||||||
slashFlat: SlashCommand[]
|
|
||||||
slashActive?: string
|
|
||||||
setSlashActive: (id: string) => void
|
|
||||||
onSlashSelect: (item: SlashCommand) => void
|
|
||||||
slashMenu: boolean
|
|
||||||
slashMenuQuery: string
|
|
||||||
onSlashMenuInput: (value: string) => void
|
|
||||||
onSlashMenuKeyDown: (event: KeyboardEvent) => void
|
|
||||||
commandKeybind: (id: string) => string | undefined
|
|
||||||
commandKeybindParts: (id: string) => string[]
|
|
||||||
newLayoutDesigns: boolean
|
|
||||||
t: (key: string) => string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PromptPopover: Component<PromptPopoverProps> = (props) => {
|
|
||||||
return (
|
|
||||||
<Show when={props.popover}>
|
|
||||||
<div
|
|
||||||
ref={(el) => {
|
|
||||||
if (props.popover === "slash") props.setSlashPopoverRef(el)
|
|
||||||
}}
|
|
||||||
class="absolute inset-x-0 -top-2 -translate-y-full origin-bottom-left max-h-80 min-h-10
|
|
||||||
overflow-auto no-scrollbar flex flex-col p-2"
|
|
||||||
classList={{
|
|
||||||
"z-[70] rounded-[10px] bg-v2-background-bg-base shadow-[var(--v2-elevation-raised)]": props.newLayoutDesigns,
|
|
||||||
"rounded-[12px] bg-surface-raised-stronger-non-alpha shadow-[var(--shadow-lg-border-base)]":
|
|
||||||
!props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<Switch>
|
|
||||||
<Match when={props.popover === "at"}>
|
|
||||||
<Show
|
|
||||||
when={props.atFlat.length > 0}
|
|
||||||
fallback={
|
|
||||||
<div
|
|
||||||
class="px-2 py-1"
|
|
||||||
classList={{
|
|
||||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
|
||||||
"text-text-weak": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.t("prompt.popover.emptyResults")}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<For each={props.atFlat.slice(0, 10)}>
|
|
||||||
{(item) => {
|
|
||||||
const key = props.atKey(item)
|
|
||||||
|
|
||||||
if (item.type === "agent") {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
class="w-full flex items-center gap-x-2 px-2 py-0.5"
|
|
||||||
classList={{
|
|
||||||
"rounded-[4px]": props.newLayoutDesigns,
|
|
||||||
"rounded-md": !props.newLayoutDesigns,
|
|
||||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
|
|
||||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
|
|
||||||
}}
|
|
||||||
onClick={() => props.onAtSelect(item)}
|
|
||||||
onPointerMove={() => props.setAtActive(key)}
|
|
||||||
>
|
|
||||||
<Icon name="brain" size="small" class="text-icon-info-active shrink-0" />
|
|
||||||
<span
|
|
||||||
class="whitespace-nowrap"
|
|
||||||
classList={{
|
|
||||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
|
||||||
props.newLayoutDesigns,
|
|
||||||
"text-v2-text-text-base": props.newLayoutDesigns,
|
|
||||||
"text-14-regular": !props.newLayoutDesigns,
|
|
||||||
"text-text-strong": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
@{item.name}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.type === "resource") {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
class="w-full flex items-center gap-x-2 px-2 py-0.5"
|
|
||||||
classList={{
|
|
||||||
"rounded-[4px]": props.newLayoutDesigns,
|
|
||||||
"rounded-md": !props.newLayoutDesigns,
|
|
||||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
|
|
||||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
|
|
||||||
}}
|
|
||||||
onClick={() => props.onAtSelect(item)}
|
|
||||||
onPointerMove={() => props.setAtActive(key)}
|
|
||||||
>
|
|
||||||
<FileIcon node={{ path: item.uri, type: "file" }} class="shrink-0 size-4" />
|
|
||||||
<div
|
|
||||||
class="flex items-center min-w-0"
|
|
||||||
classList={{
|
|
||||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
|
||||||
props.newLayoutDesigns,
|
|
||||||
"text-14-regular": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="text-text-strong whitespace-nowrap"
|
|
||||||
classList={{ "text-v2-text-text-base": props.newLayoutDesigns }}
|
|
||||||
>
|
|
||||||
@{item.name}
|
|
||||||
</span>
|
|
||||||
<Show when={item.description}>
|
|
||||||
{(description) => (
|
|
||||||
<span
|
|
||||||
class="whitespace-nowrap truncate min-w-0 ml-2"
|
|
||||||
classList={{
|
|
||||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
|
||||||
"text-text-weak": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{description()}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.type === "reference") {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
class="w-full flex items-center gap-x-2 px-2 py-0.5"
|
|
||||||
classList={{
|
|
||||||
"rounded-[4px]": props.newLayoutDesigns,
|
|
||||||
"rounded-md": !props.newLayoutDesigns,
|
|
||||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
|
|
||||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
|
|
||||||
}}
|
|
||||||
onClick={() => props.onAtSelect(item)}
|
|
||||||
onPointerMove={() => props.setAtActive(key)}
|
|
||||||
>
|
|
||||||
<FileIcon node={{ path: item.path, type: "directory" }} class="shrink-0 size-4" />
|
|
||||||
<div
|
|
||||||
class="flex items-center min-w-0"
|
|
||||||
classList={{
|
|
||||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
|
||||||
props.newLayoutDesigns,
|
|
||||||
"text-14-regular": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="text-text-strong whitespace-nowrap"
|
|
||||||
classList={{ "text-v2-text-text-base": props.newLayoutDesigns }}
|
|
||||||
>
|
|
||||||
@{item.name}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="whitespace-nowrap truncate min-w-0 ml-2"
|
|
||||||
classList={{
|
|
||||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
|
||||||
"text-text-weak": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.description}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDirectory = item.path.endsWith("/")
|
|
||||||
const directory = isDirectory ? item.path : getDirectory(item.path)
|
|
||||||
const filename = isDirectory ? "" : getFilename(item.path)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
class="w-full flex items-center gap-x-2 px-2 py-0.5"
|
|
||||||
classList={{
|
|
||||||
"rounded-[4px]": props.newLayoutDesigns,
|
|
||||||
"rounded-md": !props.newLayoutDesigns,
|
|
||||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.atActive === key,
|
|
||||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.atActive === key,
|
|
||||||
}}
|
|
||||||
onClick={() => props.onAtSelect(item)}
|
|
||||||
onPointerMove={() => props.setAtActive(key)}
|
|
||||||
>
|
|
||||||
<FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-4" />
|
|
||||||
<div
|
|
||||||
class="flex items-center min-w-0"
|
|
||||||
classList={{
|
|
||||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
|
||||||
props.newLayoutDesigns,
|
|
||||||
"text-14-regular": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="whitespace-nowrap truncate min-w-0"
|
|
||||||
classList={{
|
|
||||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
|
||||||
"text-text-weak": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{directory}
|
|
||||||
</span>
|
|
||||||
<Show when={!isDirectory}>
|
|
||||||
<span
|
|
||||||
class="whitespace-nowrap"
|
|
||||||
classList={{
|
|
||||||
"text-v2-text-text-base": props.newLayoutDesigns,
|
|
||||||
"text-text-strong": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{filename}
|
|
||||||
</span>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
</Match>
|
|
||||||
<Match when={props.popover === "slash"}>
|
|
||||||
<Show when={props.slashMenu}>
|
|
||||||
<div class="px-2 py-1">
|
|
||||||
<input
|
|
||||||
ref={(el) => requestAnimationFrame(() => el.focus())}
|
|
||||||
value={props.slashMenuQuery}
|
|
||||||
onInput={(event) => props.onSlashMenuInput(event.currentTarget.value)}
|
|
||||||
onKeyDown={props.onSlashMenuKeyDown}
|
|
||||||
onMouseDown={(event) => event.stopPropagation()}
|
|
||||||
aria-label={props.t("prompt.menu.commands")}
|
|
||||||
placeholder="/"
|
|
||||||
class="w-full bg-transparent outline-none text-[13px] leading-5 text-v2-text-text-base placeholder:text-v2-text-text-faint"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<Show
|
|
||||||
when={props.slashFlat.length > 0}
|
|
||||||
fallback={
|
|
||||||
<div
|
|
||||||
class="px-2 py-1"
|
|
||||||
classList={{
|
|
||||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
|
||||||
"text-text-weak": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.t("prompt.popover.emptyCommands")}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<For each={props.slashFlat}>
|
|
||||||
{(cmd) => {
|
|
||||||
const keybind = () => props.commandKeybind(cmd.id)
|
|
||||||
const keybindParts = () => props.commandKeybindParts(cmd.id)
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
data-slash-id={cmd.id}
|
|
||||||
classList={{
|
|
||||||
"w-full flex items-center justify-between gap-4 px-2 py-1": true,
|
|
||||||
"rounded-[4px] scroll-my-2": props.newLayoutDesigns,
|
|
||||||
"rounded-md": !props.newLayoutDesigns,
|
|
||||||
"bg-v2-overlay-simple-overlay-hover": props.newLayoutDesigns && props.slashActive === cmd.id,
|
|
||||||
"bg-surface-raised-base-hover": !props.newLayoutDesigns && props.slashActive === cmd.id,
|
|
||||||
}}
|
|
||||||
onClick={() => props.onSlashSelect(cmd)}
|
|
||||||
onPointerMove={() => props.setSlashActive(cmd.id)}
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
|
||||||
<span
|
|
||||||
class="whitespace-nowrap"
|
|
||||||
classList={{
|
|
||||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
|
||||||
props.newLayoutDesigns,
|
|
||||||
"text-v2-text-text-base": props.newLayoutDesigns,
|
|
||||||
"text-14-regular": !props.newLayoutDesigns,
|
|
||||||
"text-text-strong": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
/{cmd.trigger}
|
|
||||||
</span>
|
|
||||||
<Show when={cmd.description}>
|
|
||||||
<span
|
|
||||||
class="truncate"
|
|
||||||
classList={{
|
|
||||||
"text-[13px] leading-[calc(var(--font-size-base)*1.8)] tracking-[-0.04px] [font-weight:440]":
|
|
||||||
props.newLayoutDesigns,
|
|
||||||
"text-v2-text-text-muted": props.newLayoutDesigns,
|
|
||||||
"text-14-regular": !props.newLayoutDesigns,
|
|
||||||
"text-text-weak": !props.newLayoutDesigns,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{cmd.description}
|
|
||||||
</span>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2 shrink-0">
|
|
||||||
<Show when={cmd.type === "custom" && cmd.source !== "command"}>
|
|
||||||
<Show
|
|
||||||
when={props.newLayoutDesigns}
|
|
||||||
fallback={
|
|
||||||
<span class="text-11-regular px-1.5 py-0.5 rounded bg-surface-base text-text-subtle">
|
|
||||||
{cmd.source === "skill"
|
|
||||||
? props.t("prompt.slash.badge.skill")
|
|
||||||
: cmd.source === "mcp"
|
|
||||||
? props.t("prompt.slash.badge.mcp")
|
|
||||||
: props.t("prompt.slash.badge.custom")}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Tag>
|
|
||||||
{cmd.source === "skill"
|
|
||||||
? props.t("prompt.slash.badge.skill")
|
|
||||||
: cmd.source === "mcp"
|
|
||||||
? props.t("prompt.slash.badge.mcp")
|
|
||||||
: props.t("prompt.slash.badge.custom")}
|
|
||||||
</Tag>
|
|
||||||
</Show>
|
|
||||||
</Show>
|
|
||||||
<Show when={props.newLayoutDesigns ? keybindParts().length > 0 : keybind()}>
|
|
||||||
<Show
|
|
||||||
when={props.newLayoutDesigns}
|
|
||||||
fallback={<span class="text-12-regular text-text-subtle">{keybind()}</span>}
|
|
||||||
>
|
|
||||||
<KeybindV2 keys={keybindParts()} variant="neutral" />
|
|
||||||
</Show>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -11,18 +11,6 @@ type SessionCreateInput = {
|
|||||||
model?: { id: string; providerID: string; variant?: string }
|
model?: { id: string; providerID: string; variant?: string }
|
||||||
location?: { directory: string }
|
location?: { directory: string }
|
||||||
}
|
}
|
||||||
const admitted: Array<{
|
|
||||||
directory?: string
|
|
||||||
sessionID: string
|
|
||||||
messageID: string
|
|
||||||
text: string
|
|
||||||
displayText: string
|
|
||||||
agent: string
|
|
||||||
model: { providerID: string; modelID: string; variant?: string }
|
|
||||||
comments: unknown[]
|
|
||||||
}> = []
|
|
||||||
const confirmed: unknown[] = []
|
|
||||||
const storedSessions: Record<string, Array<{ id: string; title?: string }>> = {}
|
|
||||||
const sentShell: Array<{ sessionID: string; id?: string; command: string }> = []
|
const sentShell: Array<{ sessionID: string; id?: string; command: string }> = []
|
||||||
const sentShellDirectories: string[] = []
|
const sentShellDirectories: string[] = []
|
||||||
const promotedDrafts: Array<{ draftID: string; server: string; sessionId: string }> = []
|
const promotedDrafts: Array<{ draftID: string; server: string; sessionId: string }> = []
|
||||||
@@ -36,12 +24,9 @@ const switchedModels: Array<{
|
|||||||
}> = []
|
}> = []
|
||||||
const sessionRequestOrder: string[] = []
|
const sessionRequestOrder: string[] = []
|
||||||
const updatedDrafts: Array<{ draftID: string; worktree?: string }> = []
|
const updatedDrafts: Array<{ draftID: string; worktree?: string }> = []
|
||||||
const syncedServers: string[] = []
|
|
||||||
const admittedServers: string[] = []
|
|
||||||
const promptCaptures: Array<{ scope?: unknown; target?: unknown }> = []
|
const promptCaptures: Array<{ scope?: unknown; target?: unknown }> = []
|
||||||
let serverSessionSyncs = 0
|
let serverSessionSyncs = 0
|
||||||
let restoredPrompts = 0
|
let restoredPrompts = 0
|
||||||
let clearEchoCalls = 0
|
|
||||||
|
|
||||||
let params: { id?: string } = {}
|
let params: { id?: string } = {}
|
||||||
let search: { draftId?: string } = {}
|
let search: { draftId?: string } = {}
|
||||||
@@ -52,11 +37,8 @@ let createWorktreeGate: Promise<void> | undefined
|
|||||||
let worktreeFailure: Error | undefined
|
let worktreeFailure: Error | undefined
|
||||||
let locationFailure: Error | undefined
|
let locationFailure: Error | undefined
|
||||||
let promptFailure: Error | undefined
|
let promptFailure: Error | undefined
|
||||||
let clearEchoResult = true
|
|
||||||
let worktreeCreates = 0
|
let worktreeCreates = 0
|
||||||
let activeSDK = "server-a"
|
let activeSDK = "server-a"
|
||||||
let activeServerSync = "server-a"
|
|
||||||
let activeDirectorySync = "server-a"
|
|
||||||
let commands: Array<{ name: string }> = []
|
let commands: Array<{ name: string }> = []
|
||||||
let worktreeDirectory = "/repo/new-0"
|
let worktreeDirectory = "/repo/new-0"
|
||||||
let worktreeID = 0
|
let worktreeID = 0
|
||||||
@@ -233,83 +215,34 @@ beforeAll(async () => {
|
|||||||
usePrompt: () => prompt,
|
usePrompt: () => prompt,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mock.module("@/context/sdk", () => ({
|
mock.module("@/context/location", () => ({
|
||||||
useSDK: () => {
|
useWorkspaceLocation: () => {
|
||||||
return () => ({
|
return () => ({
|
||||||
scope: activeSDK === "server-a" ? ServerScope.local : "server-b",
|
|
||||||
directory: activeSDK === "server-a" ? "/repo/main" : "/repo/other",
|
directory: activeSDK === "server-a" ? "/repo/main" : "/repo/other",
|
||||||
api: rootClient.api,
|
|
||||||
url: "http://localhost:4096",
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mock.module("@/context/sync", () => ({
|
mock.module("@/context/server-sdk", () => ({
|
||||||
useSync: () => () => {
|
useServerSDK: () => ({
|
||||||
const server = activeDirectorySync
|
scope: activeSDK === "server-a" ? ServerScope.local : "server-b",
|
||||||
return {
|
api: rootClient.api,
|
||||||
data: { command: commands, project: "project" },
|
}),
|
||||||
session: {
|
|
||||||
inbox: {
|
|
||||||
echo: (value: {
|
|
||||||
directory?: string
|
|
||||||
sessionID: string
|
|
||||||
messageID: string
|
|
||||||
text: string
|
|
||||||
displayText: string
|
|
||||||
agent: string
|
|
||||||
model: { providerID: string; modelID: string; variant?: string }
|
|
||||||
comments: unknown[]
|
|
||||||
}) => {
|
|
||||||
admittedServers.push(server)
|
|
||||||
admitted.push(value)
|
|
||||||
},
|
|
||||||
confirm: (value: unknown) => {
|
|
||||||
confirmed.push(value)
|
|
||||||
},
|
|
||||||
clearEcho: () => {
|
|
||||||
clearEchoCalls++
|
|
||||||
return clearEchoResult
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
set: () => undefined,
|
|
||||||
project: { worktree: server === "server-a" ? "/repo/main" : "/repo/other" },
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mock.module("@/context/server-sync", () => ({
|
mock.module("@/context/server", () => ({
|
||||||
useServerSync: () => {
|
useData: () => ({
|
||||||
const server = activeServerSync
|
session: {
|
||||||
return {
|
remember: () => undefined,
|
||||||
session: {
|
setStatus: () => undefined,
|
||||||
remember: () => undefined,
|
},
|
||||||
set: () => undefined,
|
location: {
|
||||||
sync: async () => {
|
info: () => ({ project: { id: "project", directory: "/repo/main" } }),
|
||||||
serverSessionSyncs++
|
command: {
|
||||||
},
|
list: () => commands,
|
||||||
},
|
},
|
||||||
child: (directory: string) => {
|
},
|
||||||
syncedServers.push(server)
|
}),
|
||||||
storedSessions[directory] ??= []
|
|
||||||
return [
|
|
||||||
{ session: storedSessions[directory] },
|
|
||||||
(...args: unknown[]) => {
|
|
||||||
if (args[0] !== "session") return
|
|
||||||
const next = args[1]
|
|
||||||
if (typeof next === "function") {
|
|
||||||
storedSessions[directory] = next(storedSessions[directory]) as Array<{ id: string; title?: string }>
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (Array.isArray(next)) {
|
|
||||||
storedSessions[directory] = next as Array<{ id: string; title?: string }>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
mock.module("@/context/platform", () => ({
|
mock.module("@/context/platform", () => ({
|
||||||
@@ -330,8 +263,6 @@ beforeAll(async () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createdSessions.length = 0
|
createdSessions.length = 0
|
||||||
admitted.length = 0
|
|
||||||
confirmed.length = 0
|
|
||||||
promotedDrafts.length = 0
|
promotedDrafts.length = 0
|
||||||
updatedDrafts.length = 0
|
updatedDrafts.length = 0
|
||||||
sentCommands.length = 0
|
sentCommands.length = 0
|
||||||
@@ -340,11 +271,8 @@ beforeEach(() => {
|
|||||||
switchedAgents.length = 0
|
switchedAgents.length = 0
|
||||||
switchedModels.length = 0
|
switchedModels.length = 0
|
||||||
sessionRequestOrder.length = 0
|
sessionRequestOrder.length = 0
|
||||||
syncedServers.length = 0
|
|
||||||
admittedServers.length = 0
|
|
||||||
promptCaptures.length = 0
|
promptCaptures.length = 0
|
||||||
restoredPrompts = 0
|
restoredPrompts = 0
|
||||||
clearEchoCalls = 0
|
|
||||||
params = {}
|
params = {}
|
||||||
search = {}
|
search = {}
|
||||||
sentShell.length = 0
|
sentShell.length = 0
|
||||||
@@ -352,8 +280,6 @@ beforeEach(() => {
|
|||||||
selected = "/repo/worktree-a"
|
selected = "/repo/worktree-a"
|
||||||
variant = undefined
|
variant = undefined
|
||||||
activeSDK = "server-a"
|
activeSDK = "server-a"
|
||||||
activeServerSync = "server-a"
|
|
||||||
activeDirectorySync = "server-a"
|
|
||||||
commands = []
|
commands = []
|
||||||
promptValue = [{ type: "text", content: "ls", start: 0, end: 2 }]
|
promptValue = [{ type: "text", content: "ls", start: 0, end: 2 }]
|
||||||
worktreeDirectory = `/repo/new-${++worktreeID}`
|
worktreeDirectory = `/repo/new-${++worktreeID}`
|
||||||
@@ -363,11 +289,9 @@ beforeEach(() => {
|
|||||||
worktreeFailure = undefined
|
worktreeFailure = undefined
|
||||||
locationFailure = undefined
|
locationFailure = undefined
|
||||||
promptFailure = undefined
|
promptFailure = undefined
|
||||||
clearEchoResult = true
|
|
||||||
worktreeCreates = 0
|
worktreeCreates = 0
|
||||||
for (const key of Object.keys(draftServers)) delete draftServers[key]
|
for (const key of Object.keys(draftServers)) delete draftServers[key]
|
||||||
for (const key of Object.keys(sessionDirectories)) delete sessionDirectories[key]
|
for (const key of Object.keys(sessionDirectories)) delete sessionDirectories[key]
|
||||||
for (const key of Object.keys(storedSessions)) delete storedSessions[key]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const event = { preventDefault: () => undefined } as unknown as Event
|
const event = { preventDefault: () => undefined } as unknown as Event
|
||||||
@@ -442,8 +366,6 @@ describe("prompt submit worktree selection", () => {
|
|||||||
|
|
||||||
const result = submit.handleSubmit(event)
|
const result = submit.handleSubmit(event)
|
||||||
activeSDK = "server-b"
|
activeSDK = "server-b"
|
||||||
activeServerSync = "server-b"
|
|
||||||
activeDirectorySync = "server-b"
|
|
||||||
search.draftId = "draft-2"
|
search.draftId = "draft-2"
|
||||||
release()
|
release()
|
||||||
await result
|
await result
|
||||||
@@ -451,8 +373,6 @@ describe("prompt submit worktree selection", () => {
|
|||||||
|
|
||||||
expect(updatedDrafts).toEqual([{ draftID: "draft-1", worktree: undefined }])
|
expect(updatedDrafts).toEqual([{ draftID: "draft-1", worktree: undefined }])
|
||||||
expect(promotedDrafts).toEqual([{ draftID: "draft-1", server: "project-server-a", sessionId: "session-1" }])
|
expect(promotedDrafts).toEqual([{ draftID: "draft-1", server: "project-server-a", sessionId: "session-1" }])
|
||||||
expect(syncedServers.every((server) => server === "server-a")).toBe(true)
|
|
||||||
expect(admittedServers).toEqual(["server-a"])
|
|
||||||
expect(promptCaptures.at(-1)?.target).toEqual({ server: "project-server-a", scope: ServerScope.local })
|
expect(promptCaptures.at(-1)?.target).toEqual({ server: "project-server-a", scope: ServerScope.local })
|
||||||
expect(submitted).toBe(0)
|
expect(submitted).toBe(0)
|
||||||
})
|
})
|
||||||
@@ -472,15 +392,6 @@ describe("prompt submit worktree selection", () => {
|
|||||||
await submit.handleSubmit(event)
|
await submit.handleSubmit(event)
|
||||||
await Bun.sleep(0)
|
await Bun.sleep(0)
|
||||||
|
|
||||||
expect(admitted).toHaveLength(1)
|
|
||||||
expect(admitted[0]).toMatchObject({
|
|
||||||
sessionID: "session-1",
|
|
||||||
text: "ls",
|
|
||||||
agent: "agent",
|
|
||||||
model: { providerID: "provider", modelID: "model", variant: "high" },
|
|
||||||
})
|
|
||||||
expect(admitted[0]?.messageID).toStartWith("msg_")
|
|
||||||
expect(confirmed).toMatchObject([{ id: admitted[0]?.messageID, sessionID: "session-1" }])
|
|
||||||
expect(sentPrompts).toEqual(["/repo/main"])
|
expect(sentPrompts).toEqual(["/repo/main"])
|
||||||
expect(switchedAgents).toEqual([{ sessionID: "session-1", agent: "agent" }])
|
expect(switchedAgents).toEqual([{ sessionID: "session-1", agent: "agent" }])
|
||||||
expect(switchedModels).toEqual([
|
expect(switchedModels).toEqual([
|
||||||
@@ -499,10 +410,9 @@ describe("prompt submit worktree selection", () => {
|
|||||||
expect((promptInputs[0] as { id?: string }).id).toStartWith("msg_")
|
expect((promptInputs[0] as { id?: string }).id).toStartWith("msg_")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("keeps a confirmed echo when the prompt response is lost", async () => {
|
test("restores the prompt when sending fails", async () => {
|
||||||
params = { id: "session-1" }
|
params = { id: "session-1" }
|
||||||
promptFailure = new Error("connection lost")
|
promptFailure = new Error("connection lost")
|
||||||
clearEchoResult = false
|
|
||||||
const submit = makeSubmit({
|
const submit = makeSubmit({
|
||||||
info: () => ({ id: "session-1", agent: "agent", model: { id: "model", providerID: "provider" } }),
|
info: () => ({ id: "session-1", agent: "agent", model: { id: "model", providerID: "provider" } }),
|
||||||
})
|
})
|
||||||
@@ -510,9 +420,7 @@ describe("prompt submit worktree selection", () => {
|
|||||||
await submit.handleSubmit(event)
|
await submit.handleSubmit(event)
|
||||||
await settle()
|
await settle()
|
||||||
|
|
||||||
expect(admitted).toHaveLength(1)
|
expect(restoredPrompts).toBe(1)
|
||||||
expect(clearEchoCalls).toBe(1)
|
|
||||||
expect(restoredPrompts).toBe(0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("submits slash commands through the current session API", async () => {
|
test("submits slash commands through the current session API", async () => {
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import type { SessionInfo } from "@opencode-ai/client/promise"
|
import type { Data } from "@opencode-ai/client/solid"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||||
import { Binary } from "@opencode-ai/core/util/binary"
|
|
||||||
import { useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
import { useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
||||||
import { startTransition, type Accessor } from "solid-js"
|
import { startTransition, type Accessor } from "solid-js"
|
||||||
import { useTabs } from "@/context/tabs"
|
import { useTabs } from "@/context/tabs"
|
||||||
import { useServerSync, type ServerSync } from "@/context/server-sync"
|
import { useData } from "@/context/server"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useLocal, type ModelSelection } from "@/context/local"
|
import { useLocal, type ModelSelection } from "@/context/local"
|
||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { type ContextItem, type ImageAttachmentPart, type Prompt, type usePrompt } from "@/context/prompt"
|
import { type ContextItem, type ImageAttachmentPart, type Prompt, type usePrompt } from "@/context/prompt"
|
||||||
import { useSDK, type DirectorySDK } from "@/context/sdk"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { useSync, type DirectorySync } from "@/context/sync"
|
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
|
||||||
import { Identifier } from "@/utils/id"
|
import { Identifier } from "@/utils/id"
|
||||||
import { getDirectory } from "@opencode-ai/core/util/path"
|
import { getDirectory } from "@opencode-ai/core/util/path"
|
||||||
import { buildPromptRequest } from "./build-prompt-request"
|
import { buildPromptRequest } from "./build-prompt-request"
|
||||||
@@ -35,9 +34,8 @@ export type FollowupDraft = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FollowupSendInput = {
|
type FollowupSendInput = {
|
||||||
api: DirectorySDK["api"]["session"]
|
api: ServerSDK["api"]["session"]
|
||||||
serverSync: ServerSync
|
data: Data
|
||||||
sync: DirectorySync
|
|
||||||
session: Accessor<{ agent?: string; model?: { id: string; providerID: string; variant?: string } } | undefined>
|
session: Accessor<{ agent?: string; model?: { id: string; providerID: string; variant?: string } } | undefined>
|
||||||
draft: FollowupDraft
|
draft: FollowupDraft
|
||||||
messageID?: string
|
messageID?: string
|
||||||
@@ -53,17 +51,17 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||||||
const images = draftImages(input.draft.prompt)
|
const images = draftImages(input.draft.prompt)
|
||||||
const setBusy = () => {
|
const setBusy = () => {
|
||||||
if (!input.optimisticBusy) return
|
if (!input.optimisticBusy) return
|
||||||
input.serverSync.session.set("session_status", input.draft.sessionID, { type: "busy" })
|
input.data.session.setStatus(input.draft.sessionID, "running")
|
||||||
}
|
}
|
||||||
|
|
||||||
const setIdle = () => {
|
const setIdle = () => {
|
||||||
if (!input.optimisticBusy) return
|
if (!input.optimisticBusy) return
|
||||||
input.serverSync.session.set("session_status", input.draft.sessionID, { type: "idle" })
|
input.data.session.setStatus(input.draft.sessionID, "idle")
|
||||||
}
|
}
|
||||||
|
|
||||||
const [head, ...tail] = text.split(" ")
|
const [head, ...tail] = text.split(" ")
|
||||||
const cmd = head?.startsWith("/") ? head.slice(1) : undefined
|
const cmd = head?.startsWith("/") ? head.slice(1) : undefined
|
||||||
if (cmd && input.sync.data.command.find((item) => item.name === cmd)) {
|
if (cmd && input.data.location.command.list({ directory: input.draft.sessionDirectory })?.some((item) => item.name === cmd)) {
|
||||||
setBusy()
|
setBusy()
|
||||||
try {
|
try {
|
||||||
const messageID = Identifier.ascending("message")
|
const messageID = Identifier.ascending("message")
|
||||||
@@ -108,14 +106,6 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
setBusy()
|
setBusy()
|
||||||
input.sync.session.inbox.echo({
|
|
||||||
directory: input.draft.sessionDirectory,
|
|
||||||
sessionID: input.draft.sessionID,
|
|
||||||
messageID,
|
|
||||||
agent: input.draft.agent,
|
|
||||||
model: { ...input.draft.model, variant: input.draft.variant },
|
|
||||||
...request,
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const session = input.session()
|
const session = input.session()
|
||||||
@@ -137,22 +127,15 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const admitted = await input.api.prompt({
|
await input.api.prompt({
|
||||||
sessionID: input.draft.sessionID,
|
sessionID: input.draft.sessionID,
|
||||||
id: messageID,
|
id: messageID,
|
||||||
text: request.text,
|
text: request.text,
|
||||||
files: request.files.map((file) => ({ uri: file.uri, name: file.name, mention: file.mention })),
|
files: request.files.map((file) => ({ uri: file.uri, name: file.name, mention: file.mention })),
|
||||||
agents: request.agents,
|
agents: request.agents,
|
||||||
})
|
})
|
||||||
input.sync.session.inbox.confirm(admitted)
|
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const failed = input.sync.session.inbox.clearEcho({
|
|
||||||
directory: input.draft.sessionDirectory,
|
|
||||||
sessionID: input.draft.sessionID,
|
|
||||||
messageID,
|
|
||||||
})
|
|
||||||
if (!failed) return true
|
|
||||||
setIdle()
|
setIdle()
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
@@ -186,9 +169,9 @@ type PromptSubmitInput = {
|
|||||||
|
|
||||||
export function createPromptSubmit(input: PromptSubmitInput) {
|
export function createPromptSubmit(input: PromptSubmitInput) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const sdk = useSDK()
|
const sdk = useWorkspaceLocation()
|
||||||
const sync = useSync()
|
const serverSDK = useServerSDK()
|
||||||
const serverSync = useServerSync()
|
const data = useData()
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
const prompt = input.prompt
|
const prompt = input.prompt
|
||||||
@@ -209,12 +192,9 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
const abort = async () => {
|
const abort = async () => {
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return Promise.resolve()
|
if (!sessionID) return Promise.resolve()
|
||||||
serverSync.session.set("todo", sessionID, [])
|
|
||||||
|
|
||||||
input.onAbort?.()
|
input.onAbort?.()
|
||||||
|
|
||||||
return sdk()
|
return serverSDK.api.session.interrupt({ sessionID })
|
||||||
.api.session.interrupt({ sessionID })
|
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,21 +221,6 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const seed = (target: ServerSync, dir: string, info: SessionInfo) => {
|
|
||||||
target.session.remember(info)
|
|
||||||
const [, setStore] = target.child(dir)
|
|
||||||
setStore("session", (list: SessionInfo[]) => {
|
|
||||||
const result = Binary.search(list, info.id, (item) => item.id)
|
|
||||||
const next = [...list]
|
|
||||||
if (result.found) {
|
|
||||||
next[result.index] = info
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
next.splice(result.index, 0, info)
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async (event: Event) => {
|
const handleSubmit = async (event: Event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
@@ -288,9 +253,9 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const submissionSDK = sdk()
|
const submissionSDK = sdk()
|
||||||
const submissionSync = sync()
|
const submissionServerSDK = serverSDK
|
||||||
const submissionServerSync = serverSync
|
const submissionData = data
|
||||||
const submissionScope = submissionSDK.scope
|
const submissionScope = submissionServerSDK.scope
|
||||||
const projectDirectory = submissionSDK.directory
|
const projectDirectory = submissionSDK.directory
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
const isNewSession = !sessionID
|
const isNewSession = !sessionID
|
||||||
@@ -318,14 +283,16 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
let sessionDirectory = projectDirectory
|
let sessionDirectory = projectDirectory
|
||||||
if (isNewSession) {
|
if (isNewSession) {
|
||||||
if (worktreeSelection === "create") {
|
if (worktreeSelection === "create") {
|
||||||
const createdWorktree = await submissionSDK.api.worktree
|
const createdWorktree = await submissionServerSDK.api.worktree
|
||||||
.create({
|
.create({
|
||||||
projectID: submissionSync.data.project,
|
projectID: submissionData.location.info({ directory: projectDirectory })?.project.id ?? "",
|
||||||
strategy: "git",
|
strategy: "git",
|
||||||
directory: getDirectory(submissionSync.project?.worktree ?? projectDirectory),
|
directory: getDirectory(
|
||||||
|
submissionData.location.info({ directory: projectDirectory })?.project.directory ?? projectDirectory,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.then(async (created) => {
|
.then(async (created) => {
|
||||||
await submissionSDK.api.location.get({ location: { directory: created.directory } })
|
await submissionServerSDK.api.location.get({ location: { directory: created.directory } })
|
||||||
return created
|
return created
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -342,15 +309,11 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
if (worktreeSelection !== "main" && worktreeSelection !== "create") {
|
if (worktreeSelection !== "main" && worktreeSelection !== "create") {
|
||||||
sessionDirectory = worktreeSelection
|
sessionDirectory = worktreeSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sessionDirectory !== projectDirectory) {
|
|
||||||
submissionServerSync.child(sessionDirectory)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let session = currentSession
|
let session = currentSession
|
||||||
if (!session && isNewSession) {
|
if (!session && isNewSession) {
|
||||||
const created = await submissionSDK.api.session
|
const created = await submissionServerSDK.api.session
|
||||||
.create({
|
.create({
|
||||||
agent: currentAgent.name,
|
agent: currentAgent.name,
|
||||||
model: { id: currentModel.id, providerID: currentModel.provider.id, variant },
|
model: { id: currentModel.id, providerID: currentModel.provider.id, variant },
|
||||||
@@ -364,7 +327,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
if (created) {
|
if (created) {
|
||||||
seed(submissionServerSync, sessionDirectory, created)
|
submissionData.session.remember(created)
|
||||||
session = created
|
session = created
|
||||||
await startTransition(() => {
|
await startTransition(() => {
|
||||||
if (!session) return
|
if (!session) return
|
||||||
@@ -445,7 +408,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
if (mode === "shell") {
|
if (mode === "shell") {
|
||||||
clearInput()
|
clearInput()
|
||||||
const eventID = Event.ID.create()
|
const eventID = Event.ID.create()
|
||||||
void submissionSDK.api.session
|
void submissionServerSDK.api.session
|
||||||
.shell({
|
.shell({
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
id: eventID,
|
id: eventID,
|
||||||
@@ -464,12 +427,14 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
if (text.startsWith("/")) {
|
if (text.startsWith("/")) {
|
||||||
const [cmdName, ...args] = text.split(" ")
|
const [cmdName, ...args] = text.split(" ")
|
||||||
const commandName = cmdName.slice(1)
|
const commandName = cmdName.slice(1)
|
||||||
const customCommand = submissionSync.data.command.find((c) => c.name === commandName)
|
const customCommand = submissionData.location.command
|
||||||
|
.list({ directory: sessionDirectory })
|
||||||
|
?.find((command) => command.name === commandName)
|
||||||
if (customCommand) {
|
if (customCommand) {
|
||||||
clearInput()
|
clearInput()
|
||||||
const messageID = Identifier.ascending("message")
|
const messageID = Identifier.ascending("message")
|
||||||
submissionServerSync.session.set("session_status", session.id, { type: "busy" })
|
submissionData.session.setStatus(session.id, "running")
|
||||||
void submissionSDK.api.session
|
void submissionServerSDK.api.session
|
||||||
.command({
|
.command({
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
id: messageID,
|
id: messageID,
|
||||||
@@ -485,7 +450,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
submissionServerSync.session.set("session_status", session.id, { type: "idle" })
|
submissionData.session.setStatus(session.id, "idle")
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("prompt.toast.commandSendFailed.title"),
|
title: language.t("prompt.toast.commandSendFailed.title"),
|
||||||
description: formatServerError(err, language.t, language.t("common.requestFailed")),
|
description: formatServerError(err, language.t, language.t("common.requestFailed")),
|
||||||
@@ -503,16 +468,15 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|||||||
clearInput()
|
clearInput()
|
||||||
|
|
||||||
void sendFollowupDraft({
|
void sendFollowupDraft({
|
||||||
api: submissionSDK.api.session,
|
api: submissionServerSDK.api.session,
|
||||||
sync: submissionSync,
|
data: submissionData,
|
||||||
serverSync: submissionServerSync,
|
|
||||||
session: () => session,
|
session: () => session,
|
||||||
draft,
|
draft,
|
||||||
messageID,
|
messageID,
|
||||||
optimisticBusy: sessionDirectory === projectDirectory,
|
optimisticBusy: sessionDirectory === projectDirectory,
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
if (sessionDirectory === projectDirectory) {
|
if (sessionDirectory === projectDirectory) {
|
||||||
submissionSync.set("session_status", session.id, { type: "idle" })
|
submissionData.session.setStatus(session.id, "idle")
|
||||||
}
|
}
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("prompt.toast.promptSendFailed.title"),
|
title: language.t("prompt.toast.promptSendFailed.title"),
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import { createComputed, on, type Accessor } from "solid-js"
|
|
||||||
import { createStore, type SetStoreFunction } from "solid-js/store"
|
|
||||||
import type { PromptHistoryEntry } from "./history"
|
|
||||||
|
|
||||||
export type PromptInputTransientState = {
|
|
||||||
popover: "at" | "slash" | null
|
|
||||||
slashMenu: boolean
|
|
||||||
slashMenuQuery: string
|
|
||||||
historyIndex: number
|
|
||||||
savedPrompt: PromptHistoryEntry | null
|
|
||||||
placeholder: number
|
|
||||||
draggingType: "image" | "@mention" | null
|
|
||||||
mode: "normal" | "shell"
|
|
||||||
applyingHistory: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetPromptInputTransientState(setStore: SetStoreFunction<PromptInputTransientState>) {
|
|
||||||
setStore({
|
|
||||||
popover: null,
|
|
||||||
slashMenu: false,
|
|
||||||
slashMenuQuery: "",
|
|
||||||
historyIndex: -1,
|
|
||||||
savedPrompt: null,
|
|
||||||
draggingType: null,
|
|
||||||
mode: "normal",
|
|
||||||
applyingHistory: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createPromptInputTransientState(identity: Accessor<unknown>, placeholder: number) {
|
|
||||||
const [store, setStore] = createStore<PromptInputTransientState>({
|
|
||||||
popover: null,
|
|
||||||
slashMenu: false,
|
|
||||||
slashMenuQuery: "",
|
|
||||||
historyIndex: -1,
|
|
||||||
savedPrompt: null,
|
|
||||||
placeholder,
|
|
||||||
draggingType: null,
|
|
||||||
mode: "normal",
|
|
||||||
applyingHistory: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
createComputed(on(identity, () => resetPromptInputTransientState(setStore), { defer: true }))
|
|
||||||
|
|
||||||
return [store, setStore] as const
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import type { FormAnswer, IntegrationMethod, IntegrationOauthConnectOutput } from "@opencode-ai/client/promise"
|
import type { FormAnswer, IntegrationMethod, IntegrationOauthConnectOutput } from "@opencode-ai/client/promise"
|
||||||
import { useQueryClient } from "@tanstack/solid-query"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useData } from "@/context/server"
|
||||||
import { pathKey } from "@/utils/path-key"
|
|
||||||
import { createEffect, createMemo, createResource, onCleanup } from "solid-js"
|
import { createEffect, createMemo, createResource, onCleanup } from "solid-js"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
|
|
||||||
@@ -18,8 +16,7 @@ export function createProviderConnectionController(options: {
|
|||||||
}) {
|
}) {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
const serverSync = useServerSync()
|
const data = useData()
|
||||||
const queryClient = useQueryClient()
|
|
||||||
const location = () => {
|
const location = () => {
|
||||||
const directory = options.directory()
|
const directory = options.directory()
|
||||||
return directory ? { directory } : undefined
|
return directory ? { directory } : undefined
|
||||||
@@ -118,12 +115,15 @@ export function createProviderConnectionController(options: {
|
|||||||
}
|
}
|
||||||
const finish = async () => {
|
const finish = async () => {
|
||||||
cancelPolling()
|
cancelPolling()
|
||||||
const directory = options.directory()
|
const ref = location()
|
||||||
const key = directory ? pathKey(directory) : null
|
data.location.integration.invalidate(ref)
|
||||||
|
data.location.provider.invalidate(ref)
|
||||||
|
data.location.model.invalidate(ref)
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
queryClient.refetchQueries(serverSync.queryOptions.providers(key)).catch(() => undefined),
|
data.location.integration.sync(ref),
|
||||||
queryClient.refetchQueries(serverSync.queryOptions.integrations(key)).catch(() => undefined),
|
data.location.provider.sync(ref),
|
||||||
])
|
data.location.model.sync(ref),
|
||||||
|
]).catch(() => undefined)
|
||||||
if (polling.disposed) return
|
if (polling.disposed) return
|
||||||
options.onComplete()
|
options.onComplete()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,21 @@
|
|||||||
import { Match, Show, Switch, createMemo, type ComponentProps, type JSX } from "solid-js"
|
import { Show, createMemo, type ComponentProps, type JSX } from "solid-js"
|
||||||
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
|
import { ProgressCircle } from "@opencode-ai/ui/progress-circle"
|
||||||
import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2"
|
import { ProgressCircleV2 } from "@opencode-ai/ui/v2/progress-circle-v2"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
|
|
||||||
import { useFile } from "@/context/file"
|
import { useFile } from "@/context/file"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useSync } from "@/context/sync"
|
import { useData } from "@/context/server"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { getSessionContext } from "@/components/session/session-context-metrics"
|
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
import { createSessionTabs } from "@/pages/session/helpers"
|
import { createSessionTabs } from "@/pages/session/helpers"
|
||||||
import { useSettings } from "@/context/settings"
|
|
||||||
|
|
||||||
interface SessionContextUsageProps {
|
interface SessionContextUsageProps {
|
||||||
variant?: "button" | "indicator"
|
variant?: "button" | "indicator"
|
||||||
buttonAppearance?: "default" | "v2"
|
|
||||||
placement?: ComponentProps<typeof TooltipV2>["placement"]
|
placement?: ComponentProps<typeof TooltipV2>["placement"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,26 +39,24 @@ function openSessionContext(args: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SessionContextUsage(props: SessionContextUsageProps) {
|
export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||||
const sync = useSync()
|
const data = useData()
|
||||||
const file = useFile()
|
const file = useFile()
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const sdk = useSDK()
|
const sdk = useWorkspaceLocation()
|
||||||
const settings = useSettings()
|
|
||||||
const providers = useProviders(() => sdk().directory)
|
const providers = useProviders(() => sdk().directory)
|
||||||
const { params, tabs, view } = useSessionLayout()
|
const { params, tabs, view } = useSessionLayout()
|
||||||
const isDesktop = createMediaQuery("(min-width: 768px)")
|
const isDesktop = createMediaQuery("(min-width: 768px)")
|
||||||
|
|
||||||
const variant = createMemo(() => props.variant ?? "button")
|
const variant = createMemo(() => props.variant ?? "button")
|
||||||
const buttonAppearance = createMemo(() => props.buttonAppearance ?? "default")
|
|
||||||
const tabState = createSessionTabs({
|
const tabState = createSessionTabs({
|
||||||
tabs,
|
tabs,
|
||||||
pathFromTab: file.pathFromTab,
|
pathFromTab: file.pathFromTab,
|
||||||
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
|
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
|
||||||
fileBrowser: () => settings.general.newLayoutDesigns() && isDesktop() && !!params.id,
|
fileBrowser: () => isDesktop() && !!params.id,
|
||||||
})
|
})
|
||||||
const messages = createMemo(() => (params.id ? (sync().data.message[params.id] ?? []) : []))
|
const messages = createMemo(() => (params.id ? data.session.message.list(params.id) : []))
|
||||||
const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined))
|
const info = createMemo(() => (params.id ? data.session.get(params.id) : undefined))
|
||||||
|
|
||||||
const usd = createMemo(
|
const usd = createMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -72,7 +66,21 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const context = createMemo(() => getSessionContext(messages(), [...providers.all().values()]))
|
const context = createMemo(() => {
|
||||||
|
const message = messages().findLast((item) => item.type === "assistant" && !!item.tokens)
|
||||||
|
if (message?.type !== "assistant" || !message.tokens) return
|
||||||
|
const model = providers.all().get(message.model.providerID)?.models[message.model.id]
|
||||||
|
const total =
|
||||||
|
message.tokens.input +
|
||||||
|
message.tokens.output +
|
||||||
|
message.tokens.reasoning +
|
||||||
|
message.tokens.cache.read +
|
||||||
|
message.tokens.cache.write
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
usage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null,
|
||||||
|
}
|
||||||
|
})
|
||||||
const cost = createMemo(() => {
|
const cost = createMemo(() => {
|
||||||
return usd().format(info()?.cost ?? 0)
|
return usd().format(info()?.cost ?? 0)
|
||||||
})
|
})
|
||||||
@@ -138,9 +146,9 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
return (
|
return (
|
||||||
<Show when={params.id}>
|
<Show when={params.id}>
|
||||||
<TooltipV2 value={tooltipValue()} placement={props.placement ?? "top"} shift={-8}>
|
<TooltipV2 value={tooltipValue()} placement={props.placement ?? "top"} shift={-8}>
|
||||||
<Switch>
|
<Show
|
||||||
<Match when={variant() === "indicator"}>{circle()}</Match>
|
when={variant() === "indicator"}
|
||||||
<Match when={buttonAppearance() === "v2"}>
|
fallback={
|
||||||
<IconButtonV2
|
<IconButtonV2
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
@@ -149,19 +157,10 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
onClick={openContext}
|
onClick={openContext}
|
||||||
aria-label={language.t("context.usage.view")}
|
aria-label={language.t("context.usage.view")}
|
||||||
/>
|
/>
|
||||||
</Match>
|
}
|
||||||
<Match when={true}>
|
>
|
||||||
<Button
|
{circle()}
|
||||||
type="button"
|
</Show>
|
||||||
variant="ghost"
|
|
||||||
class="size-6"
|
|
||||||
onClick={openContext}
|
|
||||||
aria-label={language.t("context.usage.view")}
|
|
||||||
>
|
|
||||||
{circle()}
|
|
||||||
</Button>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</TooltipV2>
|
</TooltipV2>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { createSignal, For, Show, type ComponentProps, type JSX } from "solid-js
|
|||||||
import type { Project } from "@/types"
|
import type { Project } from "@/types"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useData } from "@/context/server"
|
||||||
import { useSettingsDialog } from "@/components/settings-dialog"
|
import { useSettingsDialog } from "@/components/settings-dialog"
|
||||||
import { pathKey } from "@/utils/path-key"
|
import { pathKey } from "@/utils/path-key"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
@@ -26,11 +26,11 @@ export function SessionWorkspaceMenu(props: {
|
|||||||
}) {
|
}) {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
const serverSync = useServerSync()
|
const data = useData()
|
||||||
const openWorkspaces = useSettingsDialog("workspaces")
|
const openWorkspaces = useSettingsDialog("workspaces")
|
||||||
const [store, setStore] = createStore({ selected: undefined as string | undefined })
|
const [store, setStore] = createStore({ selected: undefined as string | undefined })
|
||||||
const [directories, setDirectories] = createSignal(workspaceDirectories(props.project))
|
const [directories, setDirectories] = createSignal(workspaceDirectories(props.project))
|
||||||
const blocked = () => props.eligible === false || serverSync.session.data.session_working(props.sessionID)
|
const blocked = () => props.eligible === false || data.session.status(props.sessionID) === "running"
|
||||||
const currentWorkspace = () => directories().find((workspace) => containsDirectory(workspace, props.directory))
|
const currentWorkspace = () => directories().find((workspace) => containsDirectory(workspace, props.directory))
|
||||||
const workspaces = () =>
|
const workspaces = () =>
|
||||||
directories().filter((workspace) => pathKey(workspace) !== pathKey(currentWorkspace() ?? props.directory))
|
directories().filter((workspace) => pathKey(workspace) !== pathKey(currentWorkspace() ?? props.directory))
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export { SessionHeader } from "./session-header"
|
export { SessionHeader } from "./session-header"
|
||||||
export { SessionContextTab } from "./session-context-tab"
|
export { SessionContextTab } from "./session-context-tab"
|
||||||
export { SortableTab, FileVisual } from "./session-sortable-tab"
|
export { FileVisual } from "./session-sortable-tab"
|
||||||
export { SortableTabV2 } from "./session-sortable-tab-v2"
|
export { SortableTabV2 } from "./session-sortable-tab-v2"
|
||||||
export { SortableTerminalTab } from "./session-sortable-terminal-tab"
|
|
||||||
export { NewSessionView } from "./session-new-view"
|
export { NewSessionView } from "./session-new-view"
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js"
|
import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js"
|
||||||
import type { JSX } from "solid-js"
|
import type { JSX } from "solid-js"
|
||||||
import { useSync } from "@/context/sync"
|
import { useData } from "@/context/server"
|
||||||
import { checksum } from "@opencode-ai/core/util/encode"
|
import { checksum } from "@opencode-ai/core/util/encode"
|
||||||
import { findLast } from "@opencode-ai/core/util/array"
|
|
||||||
import { same } from "@/utils/same"
|
import { same } from "@/utils/same"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
@@ -11,25 +10,16 @@ import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
|
|||||||
import { File } from "@opencode-ai/session-ui/file"
|
import { File } from "@opencode-ai/session-ui/file"
|
||||||
import { Markdown } from "@opencode-ai/session-ui/markdown"
|
import { Markdown } from "@opencode-ai/session-ui/markdown"
|
||||||
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||||
import type { Message, Part, UserMessage } from "@/types"
|
import type { SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { downloadSessionExport, fetchSessionExport, sessionExportFilename } from "@/utils/session-export"
|
import { downloadSessionExport, fetchSessionExport, sessionExportFilename } from "@/utils/session-export"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
import { getSessionContext } from "./session-context-metrics"
|
|
||||||
import { estimateSessionContextBreakdown, type SessionContextBreakdownKey } from "./session-context-breakdown"
|
|
||||||
import { createSessionContextFormatter } from "./session-context-format"
|
import { createSessionContextFormatter } from "./session-context-format"
|
||||||
|
|
||||||
const BREAKDOWN_COLOR: Record<SessionContextBreakdownKey, string> = {
|
|
||||||
system: "var(--syntax-info)",
|
|
||||||
user: "var(--syntax-success)",
|
|
||||||
assistant: "var(--syntax-property)",
|
|
||||||
tool: "var(--syntax-warning)",
|
|
||||||
other: "var(--syntax-comment)",
|
|
||||||
}
|
|
||||||
|
|
||||||
function Stat(props: { label: string; value: JSX.Element }) {
|
function Stat(props: { label: string; value: JSX.Element }) {
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
@@ -39,12 +29,11 @@ function Stat(props: { label: string; value: JSX.Element }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RawMessageContent(props: { message: Message; getParts: (id: string) => Part[]; onRendered: () => void }) {
|
function RawMessageContent(props: { message: SessionMessageInfo; onRendered: () => void }) {
|
||||||
const file = createMemo(() => {
|
const file = createMemo(() => {
|
||||||
const parts = props.getParts(props.message.id)
|
const contents = JSON.stringify(props.message, null, 2)
|
||||||
const contents = JSON.stringify({ message: props.message, parts }, null, 2)
|
|
||||||
return {
|
return {
|
||||||
name: `${props.message.role}-${props.message.id}.json`,
|
name: `${props.message.type}-${props.message.id}.json`,
|
||||||
contents,
|
contents,
|
||||||
cacheKey: checksum(contents),
|
cacheKey: checksum(contents),
|
||||||
}
|
}
|
||||||
@@ -62,8 +51,7 @@ function RawMessageContent(props: { message: Message; getParts: (id: string) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RawMessage(props: {
|
function RawMessage(props: {
|
||||||
message: Message
|
message: SessionMessageInfo
|
||||||
getParts: (id: string) => Part[]
|
|
||||||
onRendered: () => void
|
onRendered: () => void
|
||||||
time: (value: number | undefined) => string
|
time: (value: number | undefined) => string
|
||||||
}) {
|
}) {
|
||||||
@@ -73,7 +61,7 @@ function RawMessage(props: {
|
|||||||
<Accordion.Trigger>
|
<Accordion.Trigger>
|
||||||
<div class="flex items-center justify-between gap-2 w-full">
|
<div class="flex items-center justify-between gap-2 w-full">
|
||||||
<div class="min-w-0 truncate">
|
<div class="min-w-0 truncate">
|
||||||
{props.message.role} <span class="text-text-base">• {props.message.id}</span>
|
{props.message.type} <span class="text-text-base">• {props.message.id}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="shrink-0 text-12-regular text-text-weak">{props.time(props.message.time.created)}</div>
|
<div class="shrink-0 text-12-regular text-text-weak">{props.time(props.message.time.created)}</div>
|
||||||
@@ -84,52 +72,35 @@ function RawMessage(props: {
|
|||||||
</StickyAccordionHeader>
|
</StickyAccordionHeader>
|
||||||
<Accordion.Content class="bg-background-base">
|
<Accordion.Content class="bg-background-base">
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<RawMessageContent message={props.message} getParts={props.getParts} onRendered={props.onRendered} />
|
<RawMessageContent message={props.message} onRendered={props.onRendered} />
|
||||||
</div>
|
</div>
|
||||||
</Accordion.Content>
|
</Accordion.Content>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyMessages: Message[] = []
|
const emptyMessages: SessionMessageInfo[] = []
|
||||||
const emptyUserMessages: UserMessage[] = []
|
|
||||||
|
|
||||||
export function SessionContextTab() {
|
export function SessionContextTab() {
|
||||||
const sync = useSync()
|
const data = useData()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const sdk = useSDK()
|
const sdk = useWorkspaceLocation()
|
||||||
|
const serverSDK = useServerSDK()
|
||||||
const providers = useProviders(() => sdk().directory)
|
const providers = useProviders(() => sdk().directory)
|
||||||
const { params, view } = useSessionLayout()
|
const { params, view } = useSessionLayout()
|
||||||
|
|
||||||
const info = createMemo(() => (params.id ? sync().session.get(params.id) : undefined))
|
const info = createMemo(() => (params.id ? data.session.get(params.id) : undefined))
|
||||||
|
|
||||||
const messages = createMemo(
|
const messages = createMemo(
|
||||||
() => {
|
() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return emptyMessages
|
if (!id) return emptyMessages
|
||||||
return (sync().data.message[id] ?? []) as Message[]
|
return data.session.message.list(id)
|
||||||
},
|
},
|
||||||
emptyMessages,
|
emptyMessages,
|
||||||
{ equals: same },
|
{ equals: same },
|
||||||
)
|
)
|
||||||
|
|
||||||
const userMessages = createMemo(
|
|
||||||
() => messages().filter((m) => m.role === "user") as UserMessage[],
|
|
||||||
emptyUserMessages,
|
|
||||||
{ equals: same },
|
|
||||||
)
|
|
||||||
|
|
||||||
const visibleUserMessages = createMemo(
|
|
||||||
() => {
|
|
||||||
const revert = info()?.revert?.messageID
|
|
||||||
if (!revert) return userMessages()
|
|
||||||
const boundary = userMessages().findIndex((message) => message.id === revert)
|
|
||||||
return boundary < 0 ? userMessages() : userMessages().slice(0, boundary)
|
|
||||||
},
|
|
||||||
emptyUserMessages,
|
|
||||||
{ equals: same },
|
|
||||||
)
|
|
||||||
|
|
||||||
const usd = createMemo(
|
const usd = createMemo(
|
||||||
() =>
|
() =>
|
||||||
new Intl.NumberFormat(language.intl(), {
|
new Intl.NumberFormat(language.intl(), {
|
||||||
@@ -138,7 +109,28 @@ export function SessionContextTab() {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const ctx = createMemo(() => getSessionContext(messages(), [...providers.all().values()]))
|
const ctx = createMemo(() => {
|
||||||
|
const message = messages().findLast((item) => item.type === "assistant" && !!item.tokens)
|
||||||
|
if (message?.type !== "assistant" || !message.tokens) return
|
||||||
|
const provider = providers.all().get(message.model.providerID)
|
||||||
|
const model = provider?.models[message.model.id]
|
||||||
|
const total =
|
||||||
|
message.tokens.input +
|
||||||
|
message.tokens.output +
|
||||||
|
message.tokens.reasoning +
|
||||||
|
message.tokens.cache.read +
|
||||||
|
message.tokens.cache.write
|
||||||
|
return {
|
||||||
|
message,
|
||||||
|
tokens: message.tokens,
|
||||||
|
providerLabel: provider?.name ?? message.model.providerID,
|
||||||
|
modelLabel: model?.name ?? message.model.id,
|
||||||
|
limit: model?.limit.context,
|
||||||
|
input: message.tokens.input,
|
||||||
|
total,
|
||||||
|
usage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null,
|
||||||
|
}
|
||||||
|
})
|
||||||
const formatter = createMemo(() => createSessionContextFormatter(language.intl()))
|
const formatter = createMemo(() => createSessionContextFormatter(language.intl()))
|
||||||
|
|
||||||
const cost = createMemo(() => {
|
const cost = createMemo(() => {
|
||||||
@@ -147,8 +139,8 @@ export function SessionContextTab() {
|
|||||||
|
|
||||||
const counts = createMemo(() => {
|
const counts = createMemo(() => {
|
||||||
const all = messages()
|
const all = messages()
|
||||||
const user = all.reduce((count, x) => count + (x.role === "user" ? 1 : 0), 0)
|
const user = all.reduce((count, message) => count + (message.type === "user" ? 1 : 0), 0)
|
||||||
const assistant = all.reduce((count, x) => count + (x.role === "assistant" ? 1 : 0), 0)
|
const assistant = all.reduce((count, message) => count + (message.type === "assistant" ? 1 : 0), 0)
|
||||||
return {
|
return {
|
||||||
all: all.length,
|
all: all.length,
|
||||||
user,
|
user,
|
||||||
@@ -157,8 +149,7 @@ export function SessionContextTab() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const systemPrompt = createMemo(() => {
|
const systemPrompt = createMemo(() => {
|
||||||
const msg = findLast(visibleUserMessages(), (m) => !!m.system)
|
const system = messages().findLast((message) => message.type === "system")?.text
|
||||||
const system = msg?.system
|
|
||||||
if (!system) return
|
if (!system) return
|
||||||
const trimmed = system.trim()
|
const trimmed = system.trim()
|
||||||
if (!trimmed) return
|
if (!trimmed) return
|
||||||
@@ -177,30 +168,6 @@ export function SessionContextTab() {
|
|||||||
return c.modelLabel
|
return c.modelLabel
|
||||||
})
|
})
|
||||||
|
|
||||||
const breakdown = createMemo(
|
|
||||||
on(
|
|
||||||
() => [ctx()?.message.id, ctx()?.input, messages().length, systemPrompt()],
|
|
||||||
() => {
|
|
||||||
const c = ctx()
|
|
||||||
if (!c?.input) return []
|
|
||||||
return estimateSessionContextBreakdown({
|
|
||||||
messages: messages(),
|
|
||||||
parts: sync().data.part as Record<string, Part[] | undefined>,
|
|
||||||
input: c.input,
|
|
||||||
systemPrompt: systemPrompt(),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const breakdownLabel = (key: SessionContextBreakdownKey) => {
|
|
||||||
if (key === "system") return language.t("context.breakdown.system")
|
|
||||||
if (key === "user") return language.t("context.breakdown.user")
|
|
||||||
if (key === "assistant") return language.t("context.breakdown.assistant")
|
|
||||||
if (key === "tool") return language.t("context.breakdown.tool")
|
|
||||||
return language.t("context.breakdown.other")
|
|
||||||
}
|
|
||||||
|
|
||||||
const stats = [
|
const stats = [
|
||||||
{ label: "context.stats.session", value: () => info()?.title ?? params.id ?? "—" },
|
{ label: "context.stats.session", value: () => info()?.title ?? params.id ?? "—" },
|
||||||
{ label: "context.stats.messages", value: () => counts().all.toLocaleString(language.intl()) },
|
{ label: "context.stats.messages", value: () => counts().all.toLocaleString(language.intl()) },
|
||||||
@@ -210,12 +177,12 @@ export function SessionContextTab() {
|
|||||||
{ label: "context.stats.totalTokens", value: () => formatter().number(ctx()?.total) },
|
{ label: "context.stats.totalTokens", value: () => formatter().number(ctx()?.total) },
|
||||||
{ label: "context.stats.usage", value: () => formatter().percent(ctx()?.usage) },
|
{ label: "context.stats.usage", value: () => formatter().percent(ctx()?.usage) },
|
||||||
{ label: "context.stats.inputTokens", value: () => formatter().number(ctx()?.input) },
|
{ label: "context.stats.inputTokens", value: () => formatter().number(ctx()?.input) },
|
||||||
{ label: "context.stats.outputTokens", value: () => formatter().number(ctx()?.message.tokens.output) },
|
{ label: "context.stats.outputTokens", value: () => formatter().number(ctx()?.tokens.output) },
|
||||||
{ label: "context.stats.reasoningTokens", value: () => formatter().number(ctx()?.message.tokens.reasoning) },
|
{ label: "context.stats.reasoningTokens", value: () => formatter().number(ctx()?.tokens.reasoning) },
|
||||||
{
|
{
|
||||||
label: "context.stats.cacheTokens",
|
label: "context.stats.cacheTokens",
|
||||||
value: () =>
|
value: () =>
|
||||||
`${formatter().number(ctx()?.message.tokens.cache.read)} / ${formatter().number(ctx()?.message.tokens.cache.write)}`,
|
`${formatter().number(ctx()?.tokens.cache.read)} / ${formatter().number(ctx()?.tokens.cache.write)}`,
|
||||||
},
|
},
|
||||||
{ label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) },
|
{ label: "context.stats.userMessages", value: () => counts().user.toLocaleString(language.intl()) },
|
||||||
{ label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) },
|
{ label: "context.stats.assistantMessages", value: () => counts().assistant.toLocaleString(language.intl()) },
|
||||||
@@ -230,7 +197,7 @@ export function SessionContextTab() {
|
|||||||
try {
|
try {
|
||||||
const data = await fetchSessionExport({
|
const data = await fetchSessionExport({
|
||||||
sessionID,
|
sessionID,
|
||||||
api: sdk().api,
|
api: serverSDK.api,
|
||||||
})
|
})
|
||||||
const filename = sessionExportFilename(data.info)
|
const filename = sessionExportFilename(data.info)
|
||||||
downloadSessionExport(filename, data)
|
downloadSessionExport(filename, data)
|
||||||
@@ -252,8 +219,6 @@ export function SessionContextTab() {
|
|||||||
let scroll: HTMLDivElement | undefined
|
let scroll: HTMLDivElement | undefined
|
||||||
let frame: number | undefined
|
let frame: number | undefined
|
||||||
let pending: { x: number; y: number } | undefined
|
let pending: { x: number; y: number } | undefined
|
||||||
const getParts = (id: string) => (sync().data.part[id] ?? []) as Part[]
|
|
||||||
|
|
||||||
const restoreScroll = () => {
|
const restoreScroll = () => {
|
||||||
const el = scroll
|
const el = scroll
|
||||||
if (!el) return
|
if (!el) return
|
||||||
@@ -314,37 +279,6 @@ export function SessionContextTab() {
|
|||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={breakdown().length > 0}>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<div class="text-12-regular text-text-weak">{language.t("context.breakdown.title")}</div>
|
|
||||||
<div class="h-2 w-full rounded-full bg-surface-base overflow-hidden flex">
|
|
||||||
<For each={breakdown()}>
|
|
||||||
{(segment) => (
|
|
||||||
<div
|
|
||||||
class="h-full"
|
|
||||||
style={{
|
|
||||||
width: `${segment.width}%`,
|
|
||||||
"background-color": BREAKDOWN_COLOR[segment.key],
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap gap-x-3 gap-y-1">
|
|
||||||
<For each={breakdown()}>
|
|
||||||
{(segment) => (
|
|
||||||
<div class="flex items-center gap-1 text-11-regular text-text-weak">
|
|
||||||
<div class="size-2 rounded-sm" style={{ "background-color": BREAKDOWN_COLOR[segment.key] }} />
|
|
||||||
<div>{breakdownLabel(segment.key)}</div>
|
|
||||||
<div class="text-text-weaker">{segment.percent.toLocaleString(language.intl())}%</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
<div class="hidden text-11-regular text-text-weaker">{language.t("context.breakdown.note")}</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={systemPrompt()}>
|
<Show when={systemPrompt()}>
|
||||||
{(prompt) => (
|
{(prompt) => (
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@@ -372,7 +306,7 @@ export function SessionContextTab() {
|
|||||||
<Accordion multiple>
|
<Accordion multiple>
|
||||||
<For each={messages()}>
|
<For each={messages()}>
|
||||||
{(message) => (
|
{(message) => (
|
||||||
<RawMessage message={message} getParts={getParts} onRendered={restoreScroll} time={formatter().time} />
|
<RawMessage message={message} onRendered={restoreScroll} time={formatter().time} />
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Show, createMemo } from "solid-js"
|
import { Show, createMemo } from "solid-js"
|
||||||
import { DateTime } from "luxon"
|
import { DateTime } from "luxon"
|
||||||
import { useSync } from "@/context/sync"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { useSDK } from "@/context/sdk"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { useData } from "@/context/server"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Mark } from "@opencode-ai/ui/logo"
|
import { Mark } from "@opencode-ai/ui/logo"
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
||||||
@@ -16,28 +16,32 @@ interface NewSessionViewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function NewSessionView(props: NewSessionViewProps) {
|
export function NewSessionView(props: NewSessionViewProps) {
|
||||||
const sync = useSync()
|
const sdk = useWorkspaceLocation()
|
||||||
const sdk = useSDK()
|
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const data = useData()
|
||||||
|
|
||||||
const sandboxes = createMemo(() => sync().project?.sandboxes ?? [])
|
const project = createMemo(() => {
|
||||||
|
const projectID = data.location.info({ directory: sdk().directory })?.project.id
|
||||||
|
return projectID ? data.project.get(projectID) : undefined
|
||||||
|
})
|
||||||
|
const sandboxes = createMemo(() => project()?.sandboxes ?? [])
|
||||||
const options = createMemo(() => [MAIN_WORKTREE, ...sandboxes(), CREATE_WORKTREE])
|
const options = createMemo(() => [MAIN_WORKTREE, ...sandboxes(), CREATE_WORKTREE])
|
||||||
const current = createMemo(() => {
|
const current = createMemo(() => {
|
||||||
const selection = props.worktree
|
const selection = props.worktree
|
||||||
if (options().includes(selection)) return selection
|
if (options().includes(selection)) return selection
|
||||||
return MAIN_WORKTREE
|
return MAIN_WORKTREE
|
||||||
})
|
})
|
||||||
const projectRoot = createMemo(() => sync().project?.worktree ?? sdk().directory)
|
const projectRoot = createMemo(() => project()?.canonical ?? sdk().directory)
|
||||||
const isWorktree = createMemo(() => {
|
const isWorktree = createMemo(() => {
|
||||||
const project = sync().project
|
const current = project()
|
||||||
if (!project) return false
|
if (!current) return false
|
||||||
return sdk().directory !== project.worktree
|
return sdk().directory !== current.canonical
|
||||||
})
|
})
|
||||||
|
|
||||||
const label = (value: string) => {
|
const label = (value: string) => {
|
||||||
if (value === MAIN_WORKTREE) {
|
if (value === MAIN_WORKTREE) {
|
||||||
if (isWorktree()) return language.t("session.new.worktree.main")
|
if (isWorktree()) return language.t("session.new.worktree.main")
|
||||||
const branch = sync().data.vcs?.branch
|
const branch = data.location.vcs.info({ directory: sdk().directory })?.branch.current
|
||||||
if (branch) return language.t("session.new.worktree.mainWithBranch", { branch })
|
if (branch) return language.t("session.new.worktree.mainWithBranch", { branch })
|
||||||
return language.t("session.new.worktree.main")
|
return language.t("session.new.worktree.main")
|
||||||
}
|
}
|
||||||
@@ -69,7 +73,7 @@ export function NewSessionView(props: NewSessionViewProps) {
|
|||||||
{label(current())}
|
{label(current())}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Show when={sync().project}>
|
<Show when={project()}>
|
||||||
{(project) => (
|
{(project) => (
|
||||||
<div class="flex items-start justify-center gap-3 min-h-5">
|
<div class="flex items-start justify-center gap-3 min-h-5">
|
||||||
<div class="text-12-medium text-text-weak leading-5 min-w-0 max-w-160 break-words text-center">
|
<div class="text-12-medium text-text-weak leading-5 min-w-0 max-w-160 break-words text-center">
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import { createMemo, Show } from "solid-js"
|
import { Show } from "solid-js"
|
||||||
import type { JSX } from "solid-js"
|
import type { JSX } from "solid-js"
|
||||||
import { createSortable } from "@thisbeyond/solid-dnd"
|
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
|
||||||
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
|
||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/core/util/path"
|
||||||
import { useFile } from "@/context/file"
|
|
||||||
import { useLanguage } from "@/context/language"
|
|
||||||
import { useCommand } from "@/context/command"
|
|
||||||
|
|
||||||
export function FileVisual(props: { path: string; active?: boolean; temporary?: boolean }): JSX.Element {
|
export function FileVisual(props: { path: string; active?: boolean; temporary?: boolean }): JSX.Element {
|
||||||
return (
|
return (
|
||||||
@@ -28,51 +21,3 @@ export function FileVisual(props: { path: string; active?: boolean; temporary?:
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SortableTab(props: {
|
|
||||||
tab: string
|
|
||||||
temporary?: boolean
|
|
||||||
onTabClose: (tab: string) => void
|
|
||||||
onTabDoubleClick?: (tab: string) => void
|
|
||||||
}): JSX.Element {
|
|
||||||
const file = useFile()
|
|
||||||
const language = useLanguage()
|
|
||||||
const command = useCommand()
|
|
||||||
const sortable = createSortable(props.tab)
|
|
||||||
const path = createMemo(() => file.pathFromTab(props.tab))
|
|
||||||
const content = createMemo(() => {
|
|
||||||
const value = path()
|
|
||||||
if (!value) return
|
|
||||||
return <FileVisual path={value} temporary={props.temporary} />
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<div use:sortable class="h-full flex items-center" classList={{ "opacity-0": sortable.isActiveDraggable }}>
|
|
||||||
<div class="relative">
|
|
||||||
<Tabs.Trigger
|
|
||||||
value={props.tab}
|
|
||||||
closeButton={
|
|
||||||
<TooltipKeybind
|
|
||||||
title={language.t("common.closeTab")}
|
|
||||||
keybind={command.keybind("tab.close")}
|
|
||||||
placement="bottom"
|
|
||||||
gutter={10}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
icon="close-small"
|
|
||||||
variant="ghost"
|
|
||||||
class="h-5 w-5"
|
|
||||||
onClick={() => props.onTabClose(props.tab)}
|
|
||||||
aria-label={language.t("common.closeTab")}
|
|
||||||
/>
|
|
||||||
</TooltipKeybind>
|
|
||||||
}
|
|
||||||
hideCloseButton
|
|
||||||
onMiddleClick={() => props.onTabClose(props.tab)}
|
|
||||||
onDblClick={() => props.onTabDoubleClick?.(props.tab)}
|
|
||||||
>
|
|
||||||
<Show when={content()}>{(value) => value()}</Show>
|
|
||||||
</Tabs.Trigger>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,20 +4,13 @@ import { createStore } from "solid-js/store"
|
|||||||
import { useSortable } from "@dnd-kit/solid/sortable"
|
import { useSortable } from "@dnd-kit/solid/sortable"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
||||||
import { isDefaultTitle as isDefaultTerminalTitle } from "@/context/terminal-title"
|
import { isDefaultTitle as isDefaultTerminalTitle } from "@/context/terminal-title"
|
||||||
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { focusTerminalById } from "@/pages/session/helpers"
|
import { focusTerminalById } from "@/pages/session/helpers"
|
||||||
|
|
||||||
export function SortableTerminalTabV2(props: {
|
export function SortableTerminalTabV2(props: { terminal: LocalPTY; index: number; onClose?: () => void }): JSX.Element {
|
||||||
terminal: LocalPTY
|
|
||||||
index: number
|
|
||||||
newLayout: boolean
|
|
||||||
onClose?: () => void
|
|
||||||
}): JSX.Element {
|
|
||||||
const terminal = useTerminal()
|
const terminal = useTerminal()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const sortable = useSortable({
|
const sortable = useSortable({
|
||||||
@@ -31,8 +24,6 @@ export function SortableTerminalTabV2(props: {
|
|||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
editing: false,
|
editing: false,
|
||||||
title: props.terminal.title,
|
title: props.terminal.title,
|
||||||
menuOpen: false,
|
|
||||||
menuPosition: { x: 0, y: 0 },
|
|
||||||
blurEnabled: false,
|
blurEnabled: false,
|
||||||
})
|
})
|
||||||
let input: HTMLInputElement | undefined
|
let input: HTMLInputElement | undefined
|
||||||
@@ -106,12 +97,6 @@ export function SortableTerminalTabV2(props: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const menu = (e: MouseEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
setStore("menuPosition", { x: e.clientX, y: e.clientY })
|
|
||||||
setStore("menuOpen", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!store.editing) return
|
if (!store.editing) return
|
||||||
if (!input) return
|
if (!input) return
|
||||||
@@ -130,155 +115,75 @@ export function SortableTerminalTabV2(props: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div ref={sortable.ref} class="h-full flex items-center outline-none focus:outline-none focus-visible:outline-none">
|
||||||
ref={sortable.ref}
|
<MenuV2.Context>
|
||||||
class="outline-none focus:outline-none focus-visible:outline-none"
|
<MenuV2.Context.Trigger class="relative" as="div">
|
||||||
classList={{
|
<Tabs.Trigger
|
||||||
"h-full flex items-center": props.newLayout,
|
value={props.terminal.id}
|
||||||
"h-full": !props.newLayout,
|
onMouseDown={(e) => {
|
||||||
}}
|
// Switch on mousedown to shave the press-release delay off tab switches.
|
||||||
>
|
if (e.button !== 0) return
|
||||||
<Show
|
if (store.editing) return
|
||||||
when={props.newLayout}
|
focus()
|
||||||
fallback={
|
}}
|
||||||
<div class="relative h-full">
|
onClick={(e) => {
|
||||||
<Tabs.Trigger
|
// Mouse navigation already happened on mousedown; detail 0 means keyboard activation.
|
||||||
value={props.terminal.id}
|
if (e.detail > 0) return
|
||||||
onClick={focus}
|
focus()
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
}}
|
||||||
onContextMenu={menu}
|
closeButton={
|
||||||
class="!shadow-none"
|
<IconButton
|
||||||
classes={{
|
icon="close-small"
|
||||||
button: "border-0 outline-none focus:outline-none focus-visible:outline-none !shadow-none !ring-0",
|
variant="ghost"
|
||||||
}}
|
class="h-5 w-5"
|
||||||
closeButton={
|
onClick={(e) => {
|
||||||
<IconButton
|
e.stopPropagation()
|
||||||
icon="close"
|
close()
|
||||||
variant="ghost"
|
}}
|
||||||
onClick={(e) => {
|
aria-label={language.t("terminal.close")}
|
||||||
e.stopPropagation()
|
/>
|
||||||
close()
|
}
|
||||||
}}
|
hideCloseButton
|
||||||
aria-label={language.t("terminal.close")}
|
onMiddleClick={close}
|
||||||
/>
|
>
|
||||||
}
|
<span
|
||||||
|
class="truncate"
|
||||||
|
data-slot="terminal-tab-title"
|
||||||
|
onDblClick={edit}
|
||||||
|
classList={{ invisible: store.editing }}
|
||||||
>
|
>
|
||||||
<span onDblClick={edit} classList={{ invisible: store.editing }}>
|
{label()}
|
||||||
{label()}
|
</span>
|
||||||
</span>
|
</Tabs.Trigger>
|
||||||
</Tabs.Trigger>
|
<Show when={store.editing}>
|
||||||
<Show when={store.editing}>
|
<div class="absolute inset-0 flex items-center bg-v2-background-bg-layer-01 z-10 pointer-events-auto rounded-[6px] shadow-[inset_0_0_0_0.5px_var(--v2-border-border-muted)] px-2">
|
||||||
<div class="absolute inset-0 flex items-center px-3 bg-muted z-10 pointer-events-auto">
|
<input
|
||||||
<input
|
ref={input}
|
||||||
ref={input}
|
type="text"
|
||||||
type="text"
|
value={store.title}
|
||||||
value={store.title}
|
onInput={(e) => setStore("title", e.currentTarget.value)}
|
||||||
onInput={(e) => setStore("title", e.currentTarget.value)}
|
onBlur={save}
|
||||||
onBlur={save}
|
onKeyDown={keydown}
|
||||||
onKeyDown={keydown}
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
class="bg-transparent border-none outline-none min-w-0 flex-1 p-0 text-[13px] leading-4 tracking-[-0.04px] text-v2-text-text-base [font-weight:440] [font-variation-settings:'slnt'_0] [font-variant-numeric:tabular-nums]"
|
||||||
class="bg-transparent border-none outline-none text-sm min-w-0 flex-1"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</Show>
|
||||||
</Show>
|
</MenuV2.Context.Trigger>
|
||||||
<DropdownMenu open={store.menuOpen} onOpenChange={(open) => setStore("menuOpen", open)}>
|
<MenuV2.Context.Portal>
|
||||||
<DropdownMenu.Portal>
|
<MenuV2.Context.Content
|
||||||
<DropdownMenu.Content
|
onCloseAutoFocus={(e) => {
|
||||||
class="fixed"
|
if (!editRequested) return
|
||||||
style={{
|
e.preventDefault()
|
||||||
left: `${store.menuPosition.x}px`,
|
editRequested = false
|
||||||
top: `${store.menuPosition.y}px`,
|
requestAnimationFrame(() => edit())
|
||||||
}}
|
}}
|
||||||
onCloseAutoFocus={(e) => {
|
>
|
||||||
if (!editRequested) return
|
<MenuV2.Item onSelect={() => (editRequested = true)}>{language.t("common.rename")}</MenuV2.Item>
|
||||||
e.preventDefault()
|
<MenuV2.Item onSelect={close}>{language.t("common.close")}</MenuV2.Item>
|
||||||
editRequested = false
|
</MenuV2.Context.Content>
|
||||||
requestAnimationFrame(() => edit())
|
</MenuV2.Context.Portal>
|
||||||
}}
|
</MenuV2.Context>
|
||||||
>
|
|
||||||
<DropdownMenu.Item onSelect={() => (editRequested = true)}>
|
|
||||||
<Icon name="edit" class="w-4 h-4 mr-2" />
|
|
||||||
{language.t("common.rename")}
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item onSelect={close}>
|
|
||||||
<Icon name="close" class="w-4 h-4 mr-2" />
|
|
||||||
{language.t("common.close")}
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
</DropdownMenu.Content>
|
|
||||||
</DropdownMenu.Portal>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<MenuV2.Context>
|
|
||||||
<MenuV2.Context.Trigger class="relative" as="div">
|
|
||||||
<Tabs.Trigger
|
|
||||||
value={props.terminal.id}
|
|
||||||
onMouseDown={(e) => {
|
|
||||||
// Switch on mousedown to shave the press-release delay off tab switches.
|
|
||||||
if (e.button !== 0) return
|
|
||||||
if (store.editing) return
|
|
||||||
focus()
|
|
||||||
}}
|
|
||||||
onClick={(e) => {
|
|
||||||
// Mouse navigation already happened on mousedown; detail 0 means keyboard activation.
|
|
||||||
if (e.detail > 0) return
|
|
||||||
focus()
|
|
||||||
}}
|
|
||||||
closeButton={
|
|
||||||
<IconButton
|
|
||||||
icon="close-small"
|
|
||||||
variant="ghost"
|
|
||||||
class="h-5 w-5"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
close()
|
|
||||||
}}
|
|
||||||
aria-label={language.t("terminal.close")}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
hideCloseButton
|
|
||||||
onMiddleClick={close}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="truncate"
|
|
||||||
data-slot="terminal-tab-title"
|
|
||||||
onDblClick={edit}
|
|
||||||
classList={{ invisible: store.editing }}
|
|
||||||
>
|
|
||||||
{label()}
|
|
||||||
</span>
|
|
||||||
</Tabs.Trigger>
|
|
||||||
<Show when={store.editing}>
|
|
||||||
<div class="absolute inset-0 flex items-center bg-v2-background-bg-layer-01 z-10 pointer-events-auto rounded-[6px] shadow-[inset_0_0_0_0.5px_var(--v2-border-border-muted)] px-2">
|
|
||||||
<input
|
|
||||||
ref={input}
|
|
||||||
type="text"
|
|
||||||
value={store.title}
|
|
||||||
onInput={(e) => setStore("title", e.currentTarget.value)}
|
|
||||||
onBlur={save}
|
|
||||||
onKeyDown={keydown}
|
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
class="bg-transparent border-none outline-none min-w-0 flex-1 p-0 text-[13px] leading-4 tracking-[-0.04px] text-v2-text-text-base [font-weight:440] [font-variation-settings:'slnt'_0] [font-variant-numeric:tabular-nums]"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</MenuV2.Context.Trigger>
|
|
||||||
<MenuV2.Context.Portal>
|
|
||||||
<MenuV2.Context.Content
|
|
||||||
onCloseAutoFocus={(e) => {
|
|
||||||
if (!editRequested) return
|
|
||||||
e.preventDefault()
|
|
||||||
editRequested = false
|
|
||||||
requestAnimationFrame(() => edit())
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MenuV2.Item onSelect={() => (editRequested = true)}>{language.t("common.rename")}</MenuV2.Item>
|
|
||||||
<MenuV2.Item onSelect={close}>{language.t("common.close")}</MenuV2.Item>
|
|
||||||
</MenuV2.Context.Content>
|
|
||||||
</MenuV2.Context.Portal>
|
|
||||||
</MenuV2.Context>
|
|
||||||
</Show>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,193 +0,0 @@
|
|||||||
import type { JSX } from "solid-js"
|
|
||||||
import { Show, createEffect, onCleanup } from "solid-js"
|
|
||||||
import { createStore } from "solid-js/store"
|
|
||||||
import { createSortable } from "@thisbeyond/solid-dnd"
|
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
import { isDefaultTitle as isDefaultTerminalTitle } from "@/context/terminal-title"
|
|
||||||
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
|
||||||
import { useLanguage } from "@/context/language"
|
|
||||||
import { focusTerminalById } from "@/pages/session/helpers"
|
|
||||||
|
|
||||||
export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => void }): JSX.Element {
|
|
||||||
const terminal = useTerminal()
|
|
||||||
const language = useLanguage()
|
|
||||||
const sortable = createSortable(props.terminal.id)
|
|
||||||
const [store, setStore] = createStore({
|
|
||||||
editing: false,
|
|
||||||
title: props.terminal.title,
|
|
||||||
menuOpen: false,
|
|
||||||
menuPosition: { x: 0, y: 0 },
|
|
||||||
blurEnabled: false,
|
|
||||||
})
|
|
||||||
let input: HTMLInputElement | undefined
|
|
||||||
let blurFrame: number | undefined
|
|
||||||
let editRequested = false
|
|
||||||
|
|
||||||
const isDefaultTitle = () => {
|
|
||||||
const number = props.terminal.titleNumber
|
|
||||||
if (!Number.isFinite(number) || number <= 0) return false
|
|
||||||
return isDefaultTerminalTitle(props.terminal.title, number)
|
|
||||||
}
|
|
||||||
|
|
||||||
const label = () => {
|
|
||||||
language.locale()
|
|
||||||
if (props.terminal.title && !isDefaultTitle()) return props.terminal.title
|
|
||||||
|
|
||||||
const number = props.terminal.titleNumber
|
|
||||||
if (Number.isFinite(number) && number > 0) return language.t("terminal.title.numbered", { number })
|
|
||||||
if (props.terminal.title) return props.terminal.title
|
|
||||||
return language.t("terminal.title")
|
|
||||||
}
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
const count = terminal.all().length
|
|
||||||
void terminal.close(props.terminal.id)
|
|
||||||
if (count === 1) {
|
|
||||||
props.onClose?.()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const focus = () => {
|
|
||||||
if (store.editing) return
|
|
||||||
if (document.activeElement instanceof HTMLElement) document.activeElement.blur()
|
|
||||||
focusTerminalById(props.terminal.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const edit = (e?: Event) => {
|
|
||||||
if (e) {
|
|
||||||
e.stopPropagation()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
setStore("blurEnabled", false)
|
|
||||||
setStore("title", props.terminal.title)
|
|
||||||
setStore("editing", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = () => {
|
|
||||||
if (!store.blurEnabled) return
|
|
||||||
|
|
||||||
const value = store.title.trim()
|
|
||||||
if (value && value !== props.terminal.title) {
|
|
||||||
terminal.update({ id: props.terminal.id, title: value })
|
|
||||||
}
|
|
||||||
setStore("editing", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const keydown = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
e.preventDefault()
|
|
||||||
save()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
e.preventDefault()
|
|
||||||
setStore("editing", false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const menu = (e: MouseEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
setStore("menuPosition", { x: e.clientX, y: e.clientY })
|
|
||||||
setStore("menuOpen", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (!store.editing) return
|
|
||||||
if (!input) return
|
|
||||||
input.focus()
|
|
||||||
input.select()
|
|
||||||
if (blurFrame !== undefined) cancelAnimationFrame(blurFrame)
|
|
||||||
blurFrame = requestAnimationFrame(() => {
|
|
||||||
blurFrame = undefined
|
|
||||||
setStore("blurEnabled", true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
if (blurFrame === undefined) return
|
|
||||||
cancelAnimationFrame(blurFrame)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
use:sortable
|
|
||||||
class="outline-none focus:outline-none focus-visible:outline-none"
|
|
||||||
classList={{
|
|
||||||
"h-full": true,
|
|
||||||
"opacity-0": sortable.isActiveDraggable,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="relative h-full">
|
|
||||||
<Tabs.Trigger
|
|
||||||
value={props.terminal.id}
|
|
||||||
onClick={focus}
|
|
||||||
onMouseDown={(e) => e.preventDefault()}
|
|
||||||
onContextMenu={menu}
|
|
||||||
class="!shadow-none"
|
|
||||||
classes={{
|
|
||||||
button: "border-0 outline-none focus:outline-none focus-visible:outline-none !shadow-none !ring-0",
|
|
||||||
}}
|
|
||||||
closeButton={
|
|
||||||
<IconButton
|
|
||||||
icon="close"
|
|
||||||
variant="ghost"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
close()
|
|
||||||
}}
|
|
||||||
aria-label={language.t("terminal.close")}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span onDblClick={edit} classList={{ invisible: store.editing }}>
|
|
||||||
{label()}
|
|
||||||
</span>
|
|
||||||
</Tabs.Trigger>
|
|
||||||
<Show when={store.editing}>
|
|
||||||
<div class="absolute inset-0 flex items-center px-3 bg-muted z-10 pointer-events-auto">
|
|
||||||
<input
|
|
||||||
ref={input}
|
|
||||||
type="text"
|
|
||||||
value={store.title}
|
|
||||||
onInput={(e) => setStore("title", e.currentTarget.value)}
|
|
||||||
onBlur={save}
|
|
||||||
onKeyDown={keydown}
|
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
class="bg-transparent border-none outline-none text-sm min-w-0 flex-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<DropdownMenu open={store.menuOpen} onOpenChange={(open) => setStore("menuOpen", open)}>
|
|
||||||
<DropdownMenu.Portal>
|
|
||||||
<DropdownMenu.Content
|
|
||||||
class="fixed"
|
|
||||||
style={{
|
|
||||||
left: `${store.menuPosition.x}px`,
|
|
||||||
top: `${store.menuPosition.y}px`,
|
|
||||||
}}
|
|
||||||
onCloseAutoFocus={(e) => {
|
|
||||||
if (!editRequested) return
|
|
||||||
e.preventDefault()
|
|
||||||
editRequested = false
|
|
||||||
requestAnimationFrame(() => edit())
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DropdownMenu.Item onSelect={() => (editRequested = true)}>
|
|
||||||
<Icon name="edit" class="w-4 h-4 mr-2" />
|
|
||||||
{language.t("common.rename")}
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<DropdownMenu.Item onSelect={close}>
|
|
||||||
<Icon name="close" class="w-4 h-4 mr-2" />
|
|
||||||
{language.t("common.close")}
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
</DropdownMenu.Content>
|
|
||||||
</DropdownMenu.Portal>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -63,7 +63,7 @@ export const DialogSettings: Component<{
|
|||||||
const draft = tabs.store.find((item) => item.type === "draft" && item.draftID === route.draftID)
|
const draft = tabs.store.find((item) => item.type === "draft" && item.draftID === route.draftID)
|
||||||
return draft?.type === "draft" ? draft.directory : undefined
|
return draft?.type === "draft" ? draft.directory : undefined
|
||||||
}
|
}
|
||||||
if (route.type === "session") return serverCtx()?.sync.session.get(route.sessionId)?.location.directory
|
if (route.type === "session") return serverCtx()?.data.session.get(route.sessionId)?.location.directory
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Component, For, createMemo, createResource } from "solid-js"
|
import { Component, For, createEffect, createMemo, createResource } from "solid-js"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
import { Switch } from "@opencode-ai/ui/v2/switch-v2"
|
||||||
import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
|
import { TabsV2 } from "@opencode-ai/ui/v2/tabs-v2"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { useData } from "@/context/server"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useMcpToggle } from "@/context/mcp"
|
||||||
import { ExternalLink } from "../external-link"
|
import { ExternalLink } from "../external-link"
|
||||||
import { InlineServerSelect } from "./parts/server-select"
|
import { InlineServerSelect } from "./parts/server-select"
|
||||||
import "./settings-v2.css"
|
import "./settings-v2.css"
|
||||||
@@ -21,35 +22,35 @@ interface PluginRowItem {
|
|||||||
export const SettingsExtensionsV2: Component = () => {
|
export const SettingsExtensionsV2: Component = () => {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const serverSdk = useServerSDK()
|
const serverSdk = useServerSDK()
|
||||||
const serverSync = useServerSync()
|
const data = useData()
|
||||||
|
const [mcpList, { refetch: refetchMcp }] = createResource(
|
||||||
|
() => serverSdk.connection.status() === "connected",
|
||||||
|
() => serverSdk.api.mcp.list().then((result) => result.data),
|
||||||
|
)
|
||||||
|
const toggleMcp = useMcpToggle(() => undefined, refetchMcp)
|
||||||
const mcps = createMemo<McpRowItem[]>(() => {
|
const mcps = createMemo<McpRowItem[]>(() => {
|
||||||
const configMcp = serverSync.data.config.mcp ?? {}
|
return (mcpList.latest ?? []).map((server) => ({
|
||||||
return Object.entries(configMcp).map(([name, config]) => ({
|
name: server.name,
|
||||||
name,
|
enabled: server.status.status === "connected",
|
||||||
enabled: typeof config !== "object" || config === null || !("enabled" in config) || config.enabled !== false,
|
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleMcpToggle = (item: McpRowItem, checked: boolean) => {
|
const handleMcpToggle = (item: McpRowItem, checked: boolean) => {
|
||||||
const before = serverSync.data.config.mcp ?? {}
|
if (item.enabled === checked || toggleMcp.isPending) return
|
||||||
const config = before[item.name]
|
toggleMcp.mutate(item.name)
|
||||||
if (typeof config !== "object" || config === null) return
|
|
||||||
const next = { ...before, [item.name]: { ...config, enabled: checked } }
|
|
||||||
serverSync.set("config", "mcp", next)
|
|
||||||
void serverSync.updateConfig({ mcp: next }).catch(() => serverSync.set("config", "mcp", before))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugins = createMemo<PluginRowItem[]>(() => {
|
const [pluginList] = createResource(
|
||||||
const raw = serverSync.data.config.plugin ?? []
|
() => serverSdk.connection.status() === "connected",
|
||||||
return raw.map((item) => {
|
() => serverSdk.api.plugin.list().then((result) => result.data),
|
||||||
const name = typeof item === "string" ? item : item[0]
|
)
|
||||||
return { name }
|
const plugins = createMemo<PluginRowItem[]>(() => (pluginList.latest ?? []).map((item) => ({ name: item.id })))
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const [skills] = createResource(serverSdk, (sdk) => sdk.api.skill.list().then((result) => result.data), {
|
createEffect(() => {
|
||||||
initialValue: [],
|
if (serverSdk.connection.status() !== "connected") return
|
||||||
|
void data.location.skill.sync().catch(() => undefined)
|
||||||
})
|
})
|
||||||
|
const skills = () => data.location.skill.list() ?? []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { createMemo, createResource, onMount, type Accessor } from "solid-js"
|
|||||||
import type { ColorScheme } from "@opencode-ai/ui/theme/context"
|
import type { ColorScheme } from "@opencode-ai/ui/theme/context"
|
||||||
import { useTheme } from "@opencode-ai/ui/theme/context"
|
import { useTheme } from "@opencode-ai/ui/theme/context"
|
||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
|
||||||
import {
|
import {
|
||||||
monoDefault,
|
monoDefault,
|
||||||
monoFontFamily,
|
monoFontFamily,
|
||||||
@@ -34,7 +33,7 @@ export function createPermissionScopeController(
|
|||||||
const s = server()
|
const s = server()
|
||||||
const id = sessionID()
|
const id = sessionID()
|
||||||
if (!s || !id) return undefined
|
if (!s || !id) return undefined
|
||||||
return serverCtx()?.sync.session.lineage.peek(id)?.session.location.directory
|
return serverCtx()?.data.session.get(id)?.location.directory
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { useIntegrations } from "@/hooks/use-integrations"
|
|||||||
import { createMemo, type Component, For, Show } from "solid-js"
|
import { createMemo, type Component, For, Show } from "solid-js"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
|
||||||
import { DialogConnectProvider, useProviderConnectController } from "../dialog-connect-provider"
|
import { DialogConnectProvider, useProviderConnectController } from "../dialog-connect-provider"
|
||||||
import { DialogCustomProvider } from "../dialog-custom-provider"
|
import { DialogCustomProvider } from "../dialog-custom-provider"
|
||||||
import { SettingsServerScope } from "../settings-server-picker"
|
import { SettingsServerScope } from "../settings-server-picker"
|
||||||
@@ -39,7 +38,6 @@ export const SettingsProvidersV2: Component<{
|
|||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const serverSdk = useServerSDK()
|
const serverSdk = useServerSDK()
|
||||||
const serverSync = useServerSync()
|
|
||||||
const providers = useProviders(() => props.directory)
|
const providers = useProviders(() => props.directory)
|
||||||
const integrations = useIntegrations(() => props.directory)
|
const integrations = useIntegrations(() => props.directory)
|
||||||
const providerConnect = useProviderConnectController({ onBack: props.onBack })
|
const providerConnect = useProviderConnectController({ onBack: props.onBack })
|
||||||
@@ -55,15 +53,12 @@ export const SettingsProvidersV2: Component<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
const connected = createMemo(() => {
|
const connected = createMemo(() => {
|
||||||
return providers.connected().filter(
|
return providers
|
||||||
(provider) =>
|
.connected()
|
||||||
provider.id !== "opencode" ||
|
.filter(
|
||||||
Object.values(provider.models).some((model) => {
|
(provider) =>
|
||||||
if (typeof model !== "object" || model === null || !("cost" in model)) return false
|
provider.id !== "opencode" || Object.values(provider.models).some((model) => model.cost.input > 0),
|
||||||
const cost = model.cost
|
)
|
||||||
return typeof cost === "object" && cost !== null && "input" in cost
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const popular = createMemo(() => {
|
const popular = createMemo(() => {
|
||||||
@@ -94,10 +89,7 @@ export const SettingsProvidersV2: Component<{
|
|||||||
const current = source(item)
|
const current = source(item)
|
||||||
if (current === "env") return language.t("settings.providers.tag.environment")
|
if (current === "env") return language.t("settings.providers.tag.environment")
|
||||||
if (current === "api") return language.t("provider.connect.method.apiKey")
|
if (current === "api") return language.t("provider.connect.method.apiKey")
|
||||||
if (current === "config") {
|
if (current === "config") return language.t("settings.providers.tag.config")
|
||||||
if (isConfigCustom(item.id)) return language.t("settings.providers.tag.custom")
|
|
||||||
return language.t("settings.providers.tag.config")
|
|
||||||
}
|
|
||||||
if (current === "custom") return language.t("settings.providers.tag.custom")
|
if (current === "custom") return language.t("settings.providers.tag.custom")
|
||||||
return language.t("settings.providers.tag.other")
|
return language.t("settings.providers.tag.other")
|
||||||
}
|
}
|
||||||
@@ -105,19 +97,12 @@ export const SettingsProvidersV2: Component<{
|
|||||||
const canDisconnect = (item: ProviderItem) => {
|
const canDisconnect = (item: ProviderItem) => {
|
||||||
const current = integration(item.id)
|
const current = integration(item.id)
|
||||||
if (current) return current.connections.some((connection) => connection.type === "credential")
|
if (current) return current.connections.some((connection) => connection.type === "credential")
|
||||||
return source(item) !== "env" && !isConfigCustom(item.id)
|
const currentSource = source(item)
|
||||||
|
return currentSource !== "env" && currentSource !== "config"
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = (id: string) => PROVIDER_NOTES.find((item) => item.match(id))?.key
|
const note = (id: string) => PROVIDER_NOTES.find((item) => item.match(id))?.key
|
||||||
|
|
||||||
const isConfigCustom = (providerID: string) => {
|
|
||||||
const provider = serverSync.data.config.provider?.[providerID]
|
|
||||||
if (!provider) return false
|
|
||||||
if (provider.npm !== "@ai-sdk/openai-compatible") return false
|
|
||||||
if (!provider.models || Object.keys(provider.models).length === 0) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const disconnect = async (providerID: string, name: string) => {
|
const disconnect = async (providerID: string, name: string) => {
|
||||||
const location = props.directory ? { directory: props.directory } : undefined
|
const location = props.directory ? { directory: props.directory } : undefined
|
||||||
await serverSdk.api.integration
|
await serverSdk.api.integration
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Component } from "solid-js"
|
import type { Component } from "solid-js"
|
||||||
import { For, Show, createMemo } from "solid-js"
|
import { For, Show, createMemo } from "solid-js"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import type { SessionInfo } from "@opencode-ai/client/promise"
|
import type { SessionInfo } from "@opencode-ai/client/promise"
|
||||||
import { useQuery } from "@tanstack/solid-query"
|
import { useQuery } from "@tanstack/solid-query"
|
||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||||
@@ -13,9 +13,10 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|||||||
import { getFilename } from "@opencode-ai/core/util/path"
|
import { getFilename } from "@opencode-ai/core/util/path"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useServerSync } from "@/context/server-sync"
|
import { useData } from "@/context/server"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { getRelativeTime } from "@/utils/time"
|
import { getRelativeTime } from "@/utils/time"
|
||||||
|
import { sessionLabel } from "@/utils/session-title"
|
||||||
import { pathKey } from "@/utils/path-key"
|
import { pathKey } from "@/utils/path-key"
|
||||||
import { SettingsListV2 } from "./parts/list"
|
import { SettingsListV2 } from "./parts/list"
|
||||||
import { InlineServerSelect } from "./parts/server-select"
|
import { InlineServerSelect } from "./parts/server-select"
|
||||||
@@ -37,6 +38,7 @@ import {
|
|||||||
} from "@/utils/workspace"
|
} from "@/utils/workspace"
|
||||||
import { listAllSessions } from "@/utils/session"
|
import { listAllSessions } from "@/utils/session"
|
||||||
import type { ServerScope } from "@/utils/server-scope"
|
import type { ServerScope } from "@/utils/server-scope"
|
||||||
|
import { normalizeProjectInfo } from "@/context/global-sync/utils"
|
||||||
import "./settings-v2.css"
|
import "./settings-v2.css"
|
||||||
|
|
||||||
type Workspace = {
|
type Workspace = {
|
||||||
@@ -48,7 +50,7 @@ export const SettingsWorkspacesV2: Component<{ activeDirectory?: string }> = (pr
|
|||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
const serverSync = useServerSync()
|
const data = useData()
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
@@ -56,9 +58,22 @@ export const SettingsWorkspacesV2: Component<{ activeDirectory?: string }> = (pr
|
|||||||
transaction: undefined as "confirm" | "running" | undefined,
|
transaction: undefined as "confirm" | "running" | undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const workspaces = createMemo(() => workspaceInventory(serverSync.data.project))
|
const projectQuery = useQuery(() => ({
|
||||||
|
queryKey: [serverSDK.scope, "settings-workspace-projects"] as const,
|
||||||
|
queryFn: async () =>
|
||||||
|
Promise.all(
|
||||||
|
(await serverSDK.api.project.list()).map(async (project) => {
|
||||||
|
const worktrees = await serverSDK.api.worktree
|
||||||
|
.list({ projectID: project.id })
|
||||||
|
.catch(() => [{ directory: project.canonical }, ...project.sandboxes.map((directory) => ({ directory }))])
|
||||||
|
return normalizeProjectInfo({ ...project, worktrees })
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
refetchOnMount: "always",
|
||||||
|
}))
|
||||||
|
const workspaces = createMemo(() => workspaceInventory(projectQuery.data ?? []))
|
||||||
const projects = createMemo(() =>
|
const projects = createMemo(() =>
|
||||||
serverSync.data.project.filter((project) => managedWorkspaceDirectories(project).length > 0),
|
(projectQuery.data ?? []).filter((project) => managedWorkspaceDirectories(project).length > 0),
|
||||||
)
|
)
|
||||||
const projectName = (project: Project) => project.name || getFilename(project.worktree)
|
const projectName = (project: Project) => project.name || getFilename(project.worktree)
|
||||||
const projectOptions = createMemo(() => [
|
const projectOptions = createMemo(() => [
|
||||||
@@ -71,18 +86,21 @@ export const SettingsWorkspacesV2: Component<{ activeDirectory?: string }> = (pr
|
|||||||
const filtered = createMemo(() => filterWorkspaceInventory(workspaces(), selectedProject()))
|
const filtered = createMemo(() => filterWorkspaceInventory(workspaces(), selectedProject()))
|
||||||
const captureDeleteContext = () => {
|
const captureDeleteContext = () => {
|
||||||
const sdk = serverSDK
|
const sdk = serverSDK
|
||||||
return { sdk, sync: serverSync, server: ServerConnection.key(sdk.server), activeDirectory: props.activeDirectory }
|
return {
|
||||||
|
sdk,
|
||||||
|
data,
|
||||||
|
server: ServerConnection.key(sdk.server),
|
||||||
|
activeDirectory: props.activeDirectory,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const loadSessions = async (context = captureDeleteContext()) => {
|
const loadSessions = async (context = captureDeleteContext()) => {
|
||||||
const fetched = await listAllSessions(context.sdk.api.session, { order: "desc" })
|
const fetched = await listAllSessions(context.sdk.api.session, { order: "desc" })
|
||||||
return mergeWorkspaceSessionInventory(
|
fetched.forEach(context.data.session.remember)
|
||||||
fetched,
|
return mergeWorkspaceSessionInventory(fetched, context.data.session.list())
|
||||||
Object.values(context.sync.session.data.info).filter((session): session is SessionInfo => !!session),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
const sessionQuery = useQuery(() => ({
|
const sessionQuery = useQuery(() => ({
|
||||||
queryKey: [serverSDK.scope, null, "settings-workspace-sessions"] as const,
|
queryKey: [serverSDK.scope, null, "settings-workspace-sessions"] as const,
|
||||||
queryFn: () => loadSessions(),
|
queryFn: () => loadSessions().then(() => Date.now()),
|
||||||
refetchOnMount: "always",
|
refetchOnMount: "always",
|
||||||
}))
|
}))
|
||||||
const sessionsByWorkspace = createMemo(
|
const sessionsByWorkspace = createMemo(
|
||||||
@@ -90,7 +108,7 @@ export const SettingsWorkspacesV2: Component<{ activeDirectory?: string }> = (pr
|
|||||||
new Map(
|
new Map(
|
||||||
workspaces().map((workspace) => [
|
workspaces().map((workspace) => [
|
||||||
pathKey(workspace.directory),
|
pathKey(workspace.directory),
|
||||||
sessionQuery.isSuccess ? sessionsForWorkspace(sessionQuery.data ?? [], workspace.directory) : [],
|
sessionQuery.isSuccess ? sessionsForWorkspace(data.session.list(), workspace.directory) : [],
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -182,19 +200,7 @@ export const SettingsWorkspacesV2: Component<{ activeDirectory?: string }> = (pr
|
|||||||
platform,
|
platform,
|
||||||
context.sdk.scope,
|
context.sdk.scope,
|
||||||
)
|
)
|
||||||
context.sync.set(
|
await projectQuery.refetch()
|
||||||
"project",
|
|
||||||
produce((draft) => {
|
|
||||||
const project = draft.find((item) => item.id === workspace.project.id)
|
|
||||||
if (!project) return
|
|
||||||
project.sandboxes = (project.sandboxes ?? []).filter(
|
|
||||||
(directory) => pathKey(directory) !== pathKey(workspace.directory),
|
|
||||||
)
|
|
||||||
project.worktrees = project.worktrees.filter(
|
|
||||||
(worktree) => pathKey(worktree.directory) !== pathKey(workspace.directory),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let inspectionID = 0
|
let inspectionID = 0
|
||||||
@@ -382,7 +388,7 @@ export const SettingsWorkspacesV2: Component<{ activeDirectory?: string }> = (pr
|
|||||||
<For each={linked()}>
|
<For each={linked()}>
|
||||||
{(session) => (
|
{(session) => (
|
||||||
<div class="settings-v2-workspaces-session">
|
<div class="settings-v2-workspaces-session">
|
||||||
<span>{session.title}</span>
|
<span>{sessionLabel(session)}</span>
|
||||||
<Show when={sessionTime(session)}>
|
<Show when={sessionTime(session)}>
|
||||||
{(time) => <span class="settings-v2-workspaces-session-time">{time()}</span>}
|
{(time) => <span class="settings-v2-workspaces-session-time">{time()}</span>}
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
import { Switch } from "@opencode-ai/ui/switch"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { showToast } from "@/utils/toast"
|
import { createMemo, createResource, For, type JSXElement, Show } from "solid-js"
|
||||||
import { createEffect, createMemo, createResource, For, type JSXElement, onCleanup, Show } from "solid-js"
|
|
||||||
import { createStore } from "solid-js/store"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
|
||||||
import { ServerConnection, useServers } from "@/context/servers"
|
|
||||||
import { useSync } from "@/context/sync"
|
|
||||||
import { type ServerHealth } from "@/utils/server-health"
|
|
||||||
import { useGlobal } from "@/context/global"
|
|
||||||
import { useMcpToggle } from "@/context/mcp"
|
import { useMcpToggle } from "@/context/mcp"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
import { useServer } from "@/context/server"
|
import { useData } from "@/context/server"
|
||||||
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
|
|
||||||
const pluginEmptyMessage = (value: string, file: string): JSXElement => {
|
const pluginEmptyMessage = (value: string, file: string): JSXElement => {
|
||||||
const parts = value.split(file)
|
const parts = value.split(file)
|
||||||
@@ -25,110 +19,22 @@ const pluginEmptyMessage = (value: string, file: string): JSXElement => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const listServersByHealth = (
|
|
||||||
list: ServerConnection.Any[],
|
|
||||||
active: ServerConnection.Key | undefined,
|
|
||||||
status: Record<ServerConnection.Key, ServerHealth | undefined>,
|
|
||||||
) => {
|
|
||||||
if (!list.length) return list
|
|
||||||
const order = new Map(list.map((url, index) => [url, index] as const))
|
|
||||||
const rank = (value?: ServerHealth) => {
|
|
||||||
if (value?.healthy === true) return 0
|
|
||||||
if (value?.healthy === false) return 2
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.slice().sort((a, b) => {
|
|
||||||
if (ServerConnection.key(a) === active) return -1
|
|
||||||
if (ServerConnection.key(b) === active) return 1
|
|
||||||
const diff = rank(status[ServerConnection.key(a)]) - rank(status[ServerConnection.key(b)])
|
|
||||||
if (diff !== 0) return diff
|
|
||||||
return (order.get(a) ?? 0) - (order.get(b) ?? 0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const useDefaultServerKey = (
|
|
||||||
get: (() => string | Promise<string | null | undefined> | null | undefined) | undefined,
|
|
||||||
) => {
|
|
||||||
const [state, setState] = createStore({
|
|
||||||
key: undefined as ServerConnection.Key | undefined,
|
|
||||||
tick: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
state.tick
|
|
||||||
let dead = false
|
|
||||||
const result = get?.()
|
|
||||||
if (!result) {
|
|
||||||
setState("key", undefined)
|
|
||||||
onCleanup(() => {
|
|
||||||
dead = true
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result instanceof Promise) {
|
|
||||||
void result.then((next) => {
|
|
||||||
if (dead) return
|
|
||||||
setState("key", next ?? undefined)
|
|
||||||
})
|
|
||||||
onCleanup(() => {
|
|
||||||
dead = true
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setState("key", ServerConnection.Key.make(result))
|
|
||||||
onCleanup(() => {
|
|
||||||
dead = true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: () => {
|
|
||||||
return state.key
|
|
||||||
},
|
|
||||||
refresh: () => setState("tick", (value) => value + 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerStatusItem = {
|
|
||||||
key: ServerConnection.Key
|
|
||||||
conn: ServerConnection.Any
|
|
||||||
health?: ServerHealth
|
|
||||||
blocked: boolean
|
|
||||||
active: boolean
|
|
||||||
onSelect: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function StatusPopoverBody(props: { shown: boolean }) {
|
export function StatusPopoverBody(props: { shown: boolean }) {
|
||||||
const sync = useSync()
|
const data = useData()
|
||||||
const sdk = useSDK()
|
const sdk = useWorkspaceLocation()
|
||||||
|
const serverSDK = useServerSDK()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
||||||
const fail = (err: unknown) => {
|
const toggleMcp = useMcpToggle(() => sdk().directory)
|
||||||
showToast({
|
const mcp = () => data.location.mcp.server.list({ directory: sdk().directory }) ?? []
|
||||||
variant: "error",
|
const mcpNames = createMemo(() => mcp().map((server) => server.name).sort((a, b) => a.localeCompare(b)))
|
||||||
title: language.t("common.requestFailed"),
|
const mcpStatus = (name: string) => mcp().find((server) => server.name === name)?.status.status
|
||||||
description: err instanceof Error ? err.message : String(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let dialogRun = 0
|
|
||||||
onCleanup(() => {
|
|
||||||
dialogRun += 1
|
|
||||||
})
|
|
||||||
const toggleMcp = useMcpToggle()
|
|
||||||
const mcpNames = createMemo(() => Object.keys(sync().data.mcp ?? {}).sort((a, b) => a.localeCompare(b)))
|
|
||||||
const mcpStatus = (name: string) => sync().data.mcp?.[name]?.status
|
|
||||||
const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length)
|
const mcpConnected = createMemo(() => mcpNames().filter((name) => mcpStatus(name) === "connected").length)
|
||||||
const lspItems = createMemo(() => sync().data.lsp ?? [])
|
|
||||||
const lspCount = createMemo(() => lspItems().length)
|
|
||||||
const [pluginList] = createResource(
|
const [pluginList] = createResource(
|
||||||
() => (props.shown ? sdk().directory : undefined),
|
() => (props.shown ? sdk().directory : undefined),
|
||||||
(directory) =>
|
(directory) =>
|
||||||
sdk()
|
serverSDK.api.plugin
|
||||||
.api.plugin.list({ location: { directory } })
|
.list({ location: { directory } })
|
||||||
.then((result) => result.data),
|
.then((result) => result.data),
|
||||||
)
|
)
|
||||||
const plugins = createMemo(() => (pluginList.latest ?? []).map((item) => item.id))
|
const plugins = createMemo(() => (pluginList.latest ?? []).map((item) => item.id))
|
||||||
@@ -150,10 +56,7 @@ export function StatusPopoverBody(props: { shown: boolean }) {
|
|||||||
{mcpConnected() > 0 ? `${mcpConnected()} ` : ""}
|
{mcpConnected() > 0 ? `${mcpConnected()} ` : ""}
|
||||||
{language.t("status.popover.tab.mcp")}
|
{language.t("status.popover.tab.mcp")}
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger value="lsp" data-slot="tab" class="text-12-regular">
|
{/* TODO: Restore LSP status when V2 exposes it. */}
|
||||||
{lspCount() > 0 ? `${lspCount()} ` : ""}
|
|
||||||
{language.t("status.popover.tab.lsp")}
|
|
||||||
</Tabs.Trigger>
|
|
||||||
<Show when={true}>
|
<Show when={true}>
|
||||||
<Tabs.Trigger value="plugins" data-slot="tab" class="text-12-regular">
|
<Tabs.Trigger value="plugins" data-slot="tab" class="text-12-regular">
|
||||||
{pluginCount() > 0 ? `${pluginCount()} ` : ""}
|
{pluginCount() > 0 ? `${pluginCount()} ` : ""}
|
||||||
@@ -223,34 +126,6 @@ export function StatusPopoverBody(props: { shown: boolean }) {
|
|||||||
</div>
|
</div>
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
|
|
||||||
<Tabs.Content value="lsp">
|
|
||||||
<div class="flex flex-col px-2 pb-2">
|
|
||||||
<div class="flex flex-col p-3 bg-background-base rounded-sm min-h-14">
|
|
||||||
<Show
|
|
||||||
when={lspItems().length > 0}
|
|
||||||
fallback={
|
|
||||||
<div class="text-14-regular text-text-base text-center my-auto">{language.t("dialog.lsp.empty")}</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<For each={lspItems()}>
|
|
||||||
{(item) => (
|
|
||||||
<div class="flex items-center gap-2 w-full px-2 py-1">
|
|
||||||
<div
|
|
||||||
classList={{
|
|
||||||
"size-1.5 rounded-full shrink-0": true,
|
|
||||||
"bg-icon-success-base": item.status === "connected",
|
|
||||||
"bg-icon-critical-base": item.status === "error",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span class="text-14-regular text-text-base truncate">{item.name || item.id}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tabs.Content>
|
|
||||||
|
|
||||||
<Show when={true}>
|
<Show when={true}>
|
||||||
<Tabs.Content value="plugins">
|
<Tabs.Content value="plugins">
|
||||||
<div class="flex flex-col px-2 pb-2">
|
<div class="flex flex-col px-2 pb-2">
|
||||||
|
|||||||
@@ -5,16 +5,14 @@ import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
|||||||
import { Popover } from "@opencode-ai/ui/popover"
|
import { Popover } from "@opencode-ai/ui/popover"
|
||||||
import { Suspense, createMemo, createSignal, lazy, Show, type JSX } from "solid-js"
|
import { Suspense, createMemo, createSignal, lazy, Show, type JSX } from "solid-js"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { ServerConnection, useServers } from "@/context/servers"
|
|
||||||
import { useServerSDK } from "@/context/server-sdk"
|
|
||||||
import { useSync } from "@/context/sync"
|
|
||||||
import { useGlobal } from "@/context/global"
|
import { useGlobal } from "@/context/global"
|
||||||
import {
|
import {
|
||||||
hasNonBlockingServiceIssue,
|
hasNonBlockingServiceIssue,
|
||||||
hasServiceNeedingAttention,
|
hasServiceNeedingAttention,
|
||||||
serverStatusDotClass,
|
serverStatusDotClass,
|
||||||
} from "./status-popover-indicator"
|
} from "./status-popover-indicator"
|
||||||
import { useServer } from "@/context/server"
|
import { useData, useServer } from "@/context/server"
|
||||||
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
|
|
||||||
const Body = lazy(() => import("./status-popover-body").then((x) => ({ default: x.StatusPopoverBody })))
|
const Body = lazy(() => import("./status-popover-body").then((x) => ({ default: x.StatusPopoverBody })))
|
||||||
|
|
||||||
@@ -22,19 +20,21 @@ export function StatusPopover() {
|
|||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const sync = useSync()
|
const data = useData()
|
||||||
|
const sdk = useWorkspaceLocation()
|
||||||
const [shown, setShown] = createSignal(false)
|
const [shown, setShown] = createSignal(false)
|
||||||
const serverHealth = () => global.servers.health[server.key]?.healthy
|
const serverHealth = () => global.servers.health[server.key]?.healthy
|
||||||
const ready = createMemo(() => serverHealth() === false || (sync().data.mcp_ready && sync().data.lsp_ready))
|
const mcp = () => data.location.mcp.server.list({ directory: sdk().directory })
|
||||||
|
const ready = createMemo(() => serverHealth() === false || mcp() !== undefined)
|
||||||
const attention = createMemo(() =>
|
const attention = createMemo(() =>
|
||||||
hasServiceNeedingAttention({
|
hasServiceNeedingAttention({
|
||||||
mcp: Object.values(sync().data.mcp ?? {}).map((item) => item.status),
|
mcp: (mcp() ?? []).map((item) => item.status.status),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const issue = createMemo(() =>
|
const issue = createMemo(() =>
|
||||||
hasNonBlockingServiceIssue({
|
hasNonBlockingServiceIssue({
|
||||||
mcp: Object.values(sync().data.mcp ?? {}).map((item) => item.status),
|
mcp: (mcp() ?? []).map((item) => item.status.status),
|
||||||
lsp: (sync().data.lsp ?? []).map((item) => item.status),
|
lsp: [],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,19 +86,21 @@ export function StatusPopoverV2() {
|
|||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const sync = useSync()
|
const data = useData()
|
||||||
|
const sdk = useWorkspaceLocation()
|
||||||
const [shown, setShown] = createSignal(false)
|
const [shown, setShown] = createSignal(false)
|
||||||
const serverHealth = () => global.servers.health[server.key]?.healthy
|
const serverHealth = () => global.servers.health[server.key]?.healthy
|
||||||
const ready = createMemo(() => serverHealth() === false || (sync().data.mcp_ready && sync().data.lsp_ready))
|
const mcp = () => data.location.mcp.server.list({ directory: sdk().directory })
|
||||||
|
const ready = createMemo(() => serverHealth() === false || mcp() !== undefined)
|
||||||
const attention = createMemo(() =>
|
const attention = createMemo(() =>
|
||||||
hasServiceNeedingAttention({
|
hasServiceNeedingAttention({
|
||||||
mcp: Object.values(sync().data.mcp ?? {}).map((item) => item.status),
|
mcp: (mcp() ?? []).map((item) => item.status.status),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const issue = createMemo(() =>
|
const issue = createMemo(() =>
|
||||||
hasNonBlockingServiceIssue({
|
hasNonBlockingServiceIssue({
|
||||||
mcp: Object.values(sync().data.mcp ?? {}).map((item) => item.status),
|
mcp: (mcp() ?? []).map((item) => item.status.status),
|
||||||
lsp: (sync().data.lsp ?? []).map((item) => item.status),
|
lsp: [],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const state = createMemo<StatusPopoverState>(() => ({
|
const state = createMemo<StatusPopoverState>(() => ({
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import { SerializeAddon } from "@/addons/serialize"
|
|||||||
import { matchKeybind, parseKeybind } from "@/context/command"
|
import { matchKeybind, parseKeybind } from "@/context/command"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useWorkspaceLocation } from "@/context/location"
|
||||||
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { terminalFontFamily, useSettings } from "@/context/settings"
|
import { terminalFontFamily, useSettings } from "@/context/settings"
|
||||||
import type { LocalPTY } from "@/context/terminal"
|
import type { LocalPTY } from "@/context/terminal"
|
||||||
import { disposeIfDisposable, getHoveredLinkText, setOptionIfSupported } from "@/utils/runtime-adapters"
|
import { disposeIfDisposable, getHoveredLinkText, setOptionIfSupported } from "@/utils/runtime-adapters"
|
||||||
@@ -171,13 +172,14 @@ const persistTerminal = (input: {
|
|||||||
|
|
||||||
export const Terminal = (props: TerminalProps) => {
|
export const Terminal = (props: TerminalProps) => {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const sdk = useSDK()
|
const sdk = useWorkspaceLocation()
|
||||||
|
const serverSDK = useServerSDK()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
// Intentional mount-time capture: the imperative xterm/WebSocket lifecycle needs stable values, and Terminal remounts when the SDK scope changes.
|
// Intentional mount-time capture: the imperative xterm/WebSocket lifecycle needs stable values, and Terminal remounts when the SDK scope changes.
|
||||||
const directory = sdk().directory
|
const directory = sdk().directory
|
||||||
const url = sdk().url
|
const url = serverSDK.url
|
||||||
let container!: HTMLDivElement
|
let container!: HTMLDivElement
|
||||||
const [local, others] = splitProps(props, [
|
const [local, others] = splitProps(props, [
|
||||||
"pty",
|
"pty",
|
||||||
@@ -235,8 +237,8 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pushSize = async (cols: number, rows: number) => {
|
const pushSize = async (cols: number, rows: number) => {
|
||||||
return sdk()
|
return serverSDK.api.pty
|
||||||
.api.pty.update({
|
.update({
|
||||||
ptyID: id,
|
ptyID: id,
|
||||||
location: { directory },
|
location: { directory },
|
||||||
size: { cols, rows },
|
size: { cols, rows },
|
||||||
@@ -255,10 +257,8 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
if (!variant?.seeds && !variant?.palette) return fallback
|
if (!variant?.seeds && !variant?.palette) return fallback
|
||||||
const resolved = resolveThemeVariant(variant, mode === "dark")
|
const resolved = resolveThemeVariant(variant, mode === "dark")
|
||||||
const text = resolved["text-stronger"] ?? fallback.foreground
|
const text = resolved["text-stronger"] ?? fallback.foreground
|
||||||
const background = settings.general.newLayoutDesigns()
|
const background =
|
||||||
? (resolveV2Token(resolveThemeVariantV2(variant, mode === "dark"), "v2-background-bg-base") ??
|
resolveV2Token(resolveThemeVariantV2(variant, mode === "dark"), "v2-background-bg-base") ?? fallback.background
|
||||||
fallback.background)
|
|
||||||
: (resolved["background-stronger"] ?? fallback.background)
|
|
||||||
const alpha = mode === "dark" ? 0.25 : 0.2
|
const alpha = mode === "dark" ? 0.25 : 0.2
|
||||||
const base = text.startsWith("#") ? (text as HexColor) : (fallback.foreground as HexColor)
|
const base = text.startsWith("#") ? (text as HexColor) : (fallback.foreground as HexColor)
|
||||||
const selectionBackground = withAlpha(base, alpha)
|
const selectionBackground = withAlpha(base, alpha)
|
||||||
@@ -521,8 +521,8 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const gone = async () => {
|
const gone = async () => {
|
||||||
return sdk()
|
return serverSDK.api.pty
|
||||||
.api.pty.get({ ptyID: id, location: { directory } })
|
.get({ ptyID: id, location: { directory } })
|
||||||
.then((result) => result.data.status === "exited")
|
.then((result) => result.data.status === "exited")
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err && typeof err === "object" && "_tag" in err && err._tag === "PtyNotFoundError") return true
|
if (err && typeof err === "object" && "_tag" in err && err._tag === "PtyNotFoundError") return true
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createEffect, createMemo, createResource, createRoot, For, onCleanup, onMount, Show } from "solid-js"
|
import { createEffect, createMemo, createResource, For, onCleanup, onMount, Show } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||||
import { DragDropProvider, PointerSensor } from "@dnd-kit/solid"
|
import { DragDropProvider, PointerSensor } from "@dnd-kit/solid"
|
||||||
@@ -84,32 +84,35 @@ function SessionTabEntry(props: {
|
|||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const sdk = createMemo(() => props.serverCtx?.sdk ?? null)
|
const sdk = createMemo(() => props.serverCtx?.sdk ?? null)
|
||||||
const cachedSession = createMemo(() => props.serverCtx?.sync.session.peek(props.tab.sessionId))
|
const cachedSession = createMemo(() => props.serverCtx?.data.session.get(props.tab.sessionId))
|
||||||
const persisted = createMemo(() => tabs.info[props.id])
|
const persisted = createMemo(() => tabs.info[props.id])
|
||||||
const [loadedSession] = createResource(
|
const [loadedSession] = createResource(
|
||||||
() => {
|
() => {
|
||||||
const ctx = props.serverCtx
|
const ctx = props.serverCtx
|
||||||
return ctx ? { id: props.tab.sessionId, ctx } : null
|
return ctx ? { id: props.tab.sessionId, ctx } : null
|
||||||
},
|
},
|
||||||
({ id, ctx }) => ctx.sync.session.resolve(id).catch(() => undefined),
|
({ id, ctx }) =>
|
||||||
|
ctx.data.session
|
||||||
|
.sync(id)
|
||||||
|
.then(() => ctx.data.session.get(id))
|
||||||
|
.catch(() => undefined),
|
||||||
)
|
)
|
||||||
const session = createMemo(() => cachedSession() ?? loadedSession())
|
const session = createMemo(() => cachedSession() ?? loadedSession())
|
||||||
const missingSession = createMemo(() => !!props.serverCtx && !loadedSession.loading && !session())
|
const missingSession = createMemo(() => !!props.serverCtx && !loadedSession.loading && !session())
|
||||||
const visible = createMemo(() => !!session() || missingSession() || !!persisted()?.title)
|
const visible = createMemo(() => !!session() || missingSession() || !!persisted()?.title)
|
||||||
let prefetched = false
|
|
||||||
|
|
||||||
const rename = async (title: string) => {
|
const rename = async (title: string) => {
|
||||||
const value = session()
|
const value = session()
|
||||||
const ctx = props.serverCtx
|
const ctx = props.serverCtx
|
||||||
if (!value || !ctx) return
|
if (!value || !ctx) return
|
||||||
|
|
||||||
ctx.sync.session.remember({ ...value, title })
|
ctx.data.session.remember({ ...value, title })
|
||||||
try {
|
try {
|
||||||
await ctx.sdk.api.session.rename({ sessionID: value.id, title })
|
await ctx.sdk.api.session.rename({ sessionID: value.id, title })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const current = session()
|
const current = session()
|
||||||
const currentCtx = props.serverCtx
|
const currentCtx = props.serverCtx
|
||||||
if (current && currentCtx) currentCtx.sync.session.remember({ ...current, title: value.title })
|
if (current && currentCtx) currentCtx.data.session.remember({ ...current, title: value.title })
|
||||||
showToast({
|
showToast({
|
||||||
title: language.t("common.requestFailed"),
|
title: language.t("common.requestFailed"),
|
||||||
description: err instanceof Error ? err.message : undefined,
|
description: err instanceof Error ? err.message : undefined,
|
||||||
@@ -122,19 +125,19 @@ function SessionTabEntry(props: {
|
|||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const ctx = props.serverCtx
|
const ctx = props.serverCtx
|
||||||
const value = session()
|
const value = session()
|
||||||
if (!ctx || !value || prefetched) return
|
if (!ctx || !value || props.active || ctx.sdk.connection.status() !== "connected") return
|
||||||
prefetched = true
|
const timer = window.setTimeout(
|
||||||
createRoot((dispose) => {
|
() =>
|
||||||
try {
|
void Promise.allSettled([
|
||||||
void ctx.sync
|
ctx.data.session.sync(value.id, { children: true }),
|
||||||
.ensureDirSyncContext(value.location.directory)
|
ctx.data.session.pending.sync(value.id),
|
||||||
.session.sync(value.id)
|
ctx.data.session.message.sync(value.id),
|
||||||
.catch(() => {})
|
ctx.data.session.permission.sync(value.id),
|
||||||
.finally(dispose)
|
ctx.data.session.form.sync(value.id),
|
||||||
} catch {
|
]),
|
||||||
dispose()
|
300 + props.index * 50,
|
||||||
}
|
)
|
||||||
})
|
onCleanup(() => window.clearTimeout(timer))
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
|||||||
@@ -11,11 +11,7 @@ import {
|
|||||||
untrack,
|
untrack,
|
||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
import { useLocation, useNavigate } from "@solidjs/router"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
||||||
@@ -32,14 +28,13 @@ import { TitlebarTabStrip } from "@/components/titlebar-tab-strip"
|
|||||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
import { readSessionTabsRemovedDetail, SESSION_TABS_REMOVED_EVENT } from "@/components/titlebar-session-events"
|
import { readSessionTabsRemovedDetail, SESSION_TABS_REMOVED_EVENT } from "@/components/titlebar-session-events"
|
||||||
import { useGlobal, useServerCtx } from "@/context/global"
|
import { useGlobal } from "@/context/global"
|
||||||
import { ServerConnection, useServers } from "@/context/servers"
|
import { ServerConnection } from "@/context/servers"
|
||||||
import { tabKey, useTabs } from "@/context/tabs"
|
import { tabKey, useTabs } from "@/context/tabs"
|
||||||
import type { PromptSession } from "@/context/prompt"
|
import type { PromptSession } from "@/context/prompt"
|
||||||
import "./titlebar.css"
|
import "./titlebar.css"
|
||||||
import { newTabTooltipKeybind } from "./command-tooltip-keybind"
|
import { newTabTooltipKeybind } from "./command-tooltip-keybind"
|
||||||
|
|
||||||
const legacyTitlebarHeight = 40
|
|
||||||
const v2TitlebarHeight = 36
|
const v2TitlebarHeight = 36
|
||||||
const minTitlebarZoom = 0.25
|
const minTitlebarZoom = 0.25
|
||||||
const windowsControlsBaseWidth = 138 // 3 native Windows caption buttons at 46px each.
|
const windowsControlsBaseWidth = 138 // 3 native Windows caption buttons at 46px each.
|
||||||
@@ -65,12 +60,10 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
|
|||||||
const command = useCommand()
|
const command = useCommand()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const servers = useServers()
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const useV2Titlebar = createMemo(() => settings.general.newLayoutDesigns())
|
|
||||||
const mobile = createMediaQuery("(max-width: 767px)")
|
const mobile = createMediaQuery("(max-width: 767px)")
|
||||||
const bottom = createMemo(() => useV2Titlebar() && mobile() && settings.general.mobileTitlebarPosition() === "bottom")
|
const bottom = createMemo(() => mobile() && settings.general.mobileTitlebarPosition() === "bottom")
|
||||||
|
|
||||||
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
|
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
|
||||||
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
|
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
|
||||||
@@ -79,9 +72,8 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
|
|||||||
const zoom = () => platform.webviewZoom?.() ?? 1
|
const zoom = () => platform.webviewZoom?.() ?? 1
|
||||||
const titlebarZoom = () => (windows() ? Math.max(zoom(), minTitlebarZoom) : zoom())
|
const titlebarZoom = () => (windows() ? Math.max(zoom(), minTitlebarZoom) : zoom())
|
||||||
const minHeight = () => {
|
const minHeight = () => {
|
||||||
const height = useV2Titlebar() ? v2TitlebarHeight : legacyTitlebarHeight
|
if (mac()) return `${v2TitlebarHeight / zoom()}px`
|
||||||
if (mac()) return `${height / zoom()}px`
|
if (windows()) return `${v2TitlebarHeight / Math.min(titlebarZoom(), 1)}px`
|
||||||
if (windows()) return `${height / Math.min(titlebarZoom(), 1)}px`
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const windowsControlsWidth = () => `${windowsControlsBaseWidth / Math.max(titlebarZoom(), 1)}px`
|
const windowsControlsWidth = () => `${windowsControlsBaseWidth / Math.max(titlebarZoom(), 1)}px`
|
||||||
@@ -153,11 +145,9 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
data-slot={useV2Titlebar() ? "titlebar-v2" : undefined}
|
data-slot="titlebar-v2"
|
||||||
classList={{
|
classList={{
|
||||||
"shrink-0 relative flex flex-row": true,
|
"shrink-0 relative flex flex-row h-9 bg-v2-background-bg-deep overflow-visible": true,
|
||||||
"h-9 bg-v2-background-bg-deep overflow-visible": useV2Titlebar(),
|
|
||||||
"h-10 bg-background-base overflow-hidden": !useV2Titlebar(),
|
|
||||||
"order-last": bottom(),
|
"order-last": bottom(),
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
@@ -264,13 +254,14 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
|
|||||||
}
|
}
|
||||||
case "home": {
|
case "home": {
|
||||||
const selection = layout.home.selection()
|
const selection = layout.home.selection()
|
||||||
const conn = global.servers.list().find((item) => ServerConnection.key(item) === selection.server)
|
const conn =
|
||||||
const project = conn
|
global.servers.list().find((item) => ServerConnection.key(item) === selection.server) ??
|
||||||
? global
|
global.servers.list()[0]
|
||||||
.ensureServerCtx(conn)
|
const projects = conn ? global.ensureServerCtx(conn).projects : undefined
|
||||||
.projects.list()
|
const project =
|
||||||
.find((item) => item.worktree === selection.directory)
|
projects?.list().find((item) => item.worktree === selection.directory) ??
|
||||||
: undefined
|
projects?.list().find((item) => item.worktree === projects.last()) ??
|
||||||
|
projects?.list()[0]
|
||||||
if (conn && project) {
|
if (conn && project) {
|
||||||
tabs.newDraft({ server: ServerConnection.key(conn), directory: project.worktree }, "")
|
tabs.newDraft({ server: ServerConnection.key(conn), directory: project.worktree }, "")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { ServerScope } from "@/utils/server-scope"
|
|||||||
import { createScopedCache } from "@/utils/scoped-cache"
|
import { createScopedCache } from "@/utils/scoped-cache"
|
||||||
import { uuid } from "@/utils/uuid"
|
import { uuid } from "@/utils/uuid"
|
||||||
import type { SelectedLineRange } from "@/context/file"
|
import type { SelectedLineRange } from "@/context/file"
|
||||||
import { useSDK } from "./sdk"
|
import { useWorkspaceLocation } from "./location"
|
||||||
|
|
||||||
export type LineComment = {
|
export type LineComment = {
|
||||||
id: string
|
id: string
|
||||||
@@ -212,7 +212,7 @@ export const { use: useComments, provider: CommentsProvider } = createSimpleCont
|
|||||||
gate: false,
|
gate: false,
|
||||||
init: () => {
|
init: () => {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const sdk = useSDK()
|
const sdk = useWorkspaceLocation()
|
||||||
const serverSDK = useServerSDK()
|
const serverSDK = useServerSDK()
|
||||||
const cache = createScopedCache(
|
const cache = createScopedCache(
|
||||||
(key) => {
|
(key) => {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user