diff --git a/.github/TEAM_MEMBERS b/.github/TEAM_MEMBERS new file mode 100644 index 0000000000..22c9a923d3 --- /dev/null +++ b/.github/TEAM_MEMBERS @@ -0,0 +1,15 @@ +adamdotdevin +Brendonovich +fwang +Hona +iamdavidhill +jayair +jlongster +kitlangton +kommander +MrMushrooooom +nexxeln +R44VC0RP +rekram1-node +RhysSullivan +thdxr diff --git a/.github/actions/setup-bun/action.yml b/.github/actions/setup-bun/action.yml index 8cf87c5d8e..20d53e81e8 100644 --- a/.github/actions/setup-bun/action.yml +++ b/.github/actions/setup-bun/action.yml @@ -3,12 +3,13 @@ description: "Setup Bun with caching and install dependencies" runs: using: "composite" steps: - - name: Mount Bun Cache - if: ${{ runner.os == 'Linux' }} - uses: useblacksmith/stickydisk@v1 + - name: Cache Bun dependencies + uses: actions/cache@v4 with: - key: ${{ github.repository }}-bun-cache-${{ runner.os }} - path: ~/.bun + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }} + restore-keys: | + ${{ runner.os }}-bun- - name: Setup Bun uses: oven-sh/setup-bun@v2 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8cf030eceb..393bf90518 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,29 @@ +### Issue for this PR + +Closes # + +### Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Refactor / code improvement +- [ ] Documentation + ### What does this PR do? -Please provide a description of the issue (if there is one), the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR. +Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR. **If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!** ### How did you verify your code works? + +### Screenshots / recordings + +_If this is a UI change, please include a screenshot or recording._ + +### Checklist + +- [ ] I have tested my changes locally +- [ ] I have not included unrelated changes in this PR + +_If you do not follow this template your PR will be automatically rejected._ diff --git a/.github/workflows/duplicate-issues.yml b/.github/workflows/duplicate-issues.yml index 87e655fe4b..6c1943fe7b 100644 --- a/.github/workflows/duplicate-issues.yml +++ b/.github/workflows/duplicate-issues.yml @@ -2,10 +2,11 @@ name: duplicate-issues on: issues: - types: [opened] + types: [opened, edited] jobs: check-duplicates: + if: github.event.action == 'opened' runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: read @@ -34,7 +35,7 @@ jobs: "webfetch": "deny" } run: | - opencode run -m opencode/claude-haiku-4-5 "A new issue has been created: + opencode run -m opencode/claude-sonnet-4-6 "A new issue has been created: Issue number: ${{ github.event.issue.number }} @@ -115,3 +116,62 @@ jobs: If you believe this was flagged incorrectly, please let a maintainer know. Remember: post at most ONE comment combining all findings. If everything is fine, post nothing." + + recheck-compliance: + if: github.event.action == 'edited' && contains(github.event.issue.labels.*.name, 'needs:compliance') + runs-on: blacksmith-4vcpu-ubuntu-2404 + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: ./.github/actions/setup-bun + + - name: Install opencode + run: curl -fsSL https://opencode.ai/install | bash + + - name: Recheck compliance + env: + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENCODE_PERMISSION: | + { + "bash": { + "*": "deny", + "gh issue*": "allow" + }, + "webfetch": "deny" + } + run: | + opencode run -m opencode/claude-sonnet-4-6 "Issue #${{ github.event.issue.number }} was previously flagged as non-compliant and has been edited. + + Lookup this issue with gh issue view ${{ github.event.issue.number }}. + + Re-check whether the issue now follows our contributing guidelines and issue templates. + + This project has three issue templates that every issue MUST use one of: + + 1. Bug Report - requires a Description field with real content + 2. Feature Request - requires a verification checkbox and description, title should start with [FEATURE]: + 3. Question - requires the Question field with real content + + Additionally check: + - No AI-generated walls of text (long, AI-generated descriptions are not acceptable) + - The issue has real content, not just template placeholder text left unchanged + - Bug reports should include some context about how to reproduce + - Feature requests should explain the problem or need + - We want to push for having the user provide system description & information + + Do NOT be nitpicky about optional fields. Only flag real problems like: no template used, required fields empty or placeholder text only, obviously AI-generated walls of text, or completely empty/nonsensical content. + + If the issue is NOW compliant: + 1. Remove the needs:compliance label: gh issue edit ${{ github.event.issue.number }} --remove-label needs:compliance + 2. Find and delete the previous compliance comment (the one containing ) using: gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments --jq '.[] | select(.body | contains(\"\")) | .id' then delete it with: gh api -X DELETE repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments/{id} + 3. Post a short comment thanking them for updating the issue. + + If the issue is STILL not compliant: + Post a comment explaining what still needs to be fixed. Keep the needs:compliance label." diff --git a/.github/workflows/pr-management.yml b/.github/workflows/pr-management.yml index 0082724154..35bd7ae36f 100644 --- a/.github/workflows/pr-management.yml +++ b/.github/workflows/pr-management.yml @@ -6,17 +6,6 @@ on: jobs: check-duplicates: - if: | - github.event.pull_request.user.login != 'actions-user' && - github.event.pull_request.user.login != 'opencode' && - github.event.pull_request.user.login != 'rekram1-node' && - github.event.pull_request.user.login != 'thdxr' && - github.event.pull_request.user.login != 'kommander' && - github.event.pull_request.user.login != 'jayair' && - github.event.pull_request.user.login != 'fwang' && - github.event.pull_request.user.login != 'adamdotdevin' && - github.event.pull_request.user.login != 'iamdavidhill' && - github.event.pull_request.user.login != 'opencode-agent[bot]' runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: read @@ -27,16 +16,31 @@ jobs: with: fetch-depth: 1 + - name: Check team membership + id: team-check + run: | + LOGIN="${{ github.event.pull_request.user.login }}" + if [ "$LOGIN" = "opencode-agent[bot]" ] || grep -qxF "$LOGIN" .github/TEAM_MEMBERS; then + echo "is_team=true" >> "$GITHUB_OUTPUT" + echo "Skipping: $LOGIN is a team member or bot" + else + echo "is_team=false" >> "$GITHUB_OUTPUT" + fi + - name: Setup Bun + if: steps.team-check.outputs.is_team != 'true' uses: ./.github/actions/setup-bun - name: Install dependencies + if: steps.team-check.outputs.is_team != 'true' run: bun install - name: Install opencode + if: steps.team-check.outputs.is_team != 'true' run: curl -fsSL https://opencode.ai/install | bash - name: Build prompt + if: steps.team-check.outputs.is_team != 'true' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} @@ -53,6 +57,7 @@ jobs: } > pr_info.txt - name: Check for duplicate PRs + if: steps.team-check.outputs.is_team != 'true' env: OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-standards.yml b/.github/workflows/pr-standards.yml index 397f794a1c..a7e9eed3a8 100644 --- a/.github/workflows/pr-standards.yml +++ b/.github/workflows/pr-standards.yml @@ -6,19 +6,9 @@ on: jobs: check-standards: - if: | - github.event.pull_request.user.login != 'actions-user' && - github.event.pull_request.user.login != 'opencode' && - github.event.pull_request.user.login != 'rekram1-node' && - github.event.pull_request.user.login != 'thdxr' && - github.event.pull_request.user.login != 'kommander' && - github.event.pull_request.user.login != 'jayair' && - github.event.pull_request.user.login != 'fwang' && - github.event.pull_request.user.login != 'adamdotdevin' && - github.event.pull_request.user.login != 'iamdavidhill' && - github.event.pull_request.user.login != 'opencode-agent[bot]' runs-on: ubuntu-latest permissions: + contents: read pull-requests: write steps: - name: Check PR standards @@ -26,6 +16,22 @@ jobs: with: script: | const pr = context.payload.pull_request; + const login = pr.user.login; + + // Check if author is a team member or bot + if (login === 'opencode-agent[bot]') return; + const { data: file } = await github.rest.repos.getContent({ + owner: context.repo.owner, + repo: context.repo.repo, + path: '.github/TEAM_MEMBERS', + ref: 'dev' + }); + const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean); + if (members.includes(login)) { + console.log(`Skipping: ${login} is a team member`); + return; + } + const title = pr.title; async function addLabel(label) { @@ -137,3 +143,193 @@ jobs: await removeLabel('needs:issue'); console.log('PR meets all standards'); + + check-compliance: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Check PR template compliance + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + const login = pr.user.login; + + // Check if author is a team member or bot + if (login === 'opencode-agent[bot]') return; + const { data: file } = await github.rest.repos.getContent({ + owner: context.repo.owner, + repo: context.repo.repo, + path: '.github/TEAM_MEMBERS', + ref: 'dev' + }); + const members = Buffer.from(file.content, 'base64').toString().split('\n').map(l => l.trim()).filter(Boolean); + if (members.includes(login)) { + console.log(`Skipping: ${login} is a team member`); + return; + } + + const body = pr.body || ''; + const title = pr.title; + const isDocsOrRefactor = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title); + + const issues = []; + + // Check: template sections exist + const hasWhatSection = /### What does this PR do\?/.test(body); + const hasTypeSection = /### Type of change/.test(body); + const hasVerifySection = /### How did you verify your code works\?/.test(body); + const hasChecklistSection = /### Checklist/.test(body); + const hasIssueSection = /### Issue for this PR/.test(body); + + if (!hasWhatSection || !hasTypeSection || !hasVerifySection || !hasChecklistSection || !hasIssueSection) { + issues.push('PR description is missing required template sections. Please use the [PR template](../blob/dev/.github/pull_request_template.md).'); + } + + // Check: "What does this PR do?" has real content (not just placeholder text) + if (hasWhatSection) { + const whatMatch = body.match(/### What does this PR do\?\s*\n([\s\S]*?)(?=###|$)/); + const whatContent = whatMatch ? whatMatch[1].trim() : ''; + const placeholder = 'Please provide a description of the issue'; + const onlyPlaceholder = whatContent.includes(placeholder) && whatContent.replace(placeholder, '').replace(/[*\s]/g, '').length < 20; + if (!whatContent || onlyPlaceholder) { + issues.push('"What does this PR do?" section is empty or only contains placeholder text. Please describe your changes.'); + } + } + + // Check: at least one "Type of change" checkbox is checked + if (hasTypeSection) { + const typeMatch = body.match(/### Type of change\s*\n([\s\S]*?)(?=###|$)/); + const typeContent = typeMatch ? typeMatch[1] : ''; + const hasCheckedBox = /- \[x\]/i.test(typeContent); + if (!hasCheckedBox) { + issues.push('No "Type of change" checkbox is checked. Please select at least one.'); + } + } + + // Check: issue reference (skip for docs/refactor) + if (!isDocsOrRefactor && hasIssueSection) { + const issueMatch = body.match(/### Issue for this PR\s*\n([\s\S]*?)(?=###|$)/); + const issueContent = issueMatch ? issueMatch[1].trim() : ''; + const hasIssueRef = /(closes|fixes|resolves)\s+#\d+/i.test(issueContent) || /#\d+/.test(issueContent); + if (!hasIssueRef) { + issues.push('No issue referenced. Please add `Closes #` linking to the relevant issue.'); + } + } + + // Check: "How did you verify" has content + if (hasVerifySection) { + const verifyMatch = body.match(/### How did you verify your code works\?\s*\n([\s\S]*?)(?=###|$)/); + const verifyContent = verifyMatch ? verifyMatch[1].trim() : ''; + if (!verifyContent) { + issues.push('"How did you verify your code works?" section is empty. Please explain how you tested.'); + } + } + + // Check: checklist boxes are checked + if (hasChecklistSection) { + const checklistMatch = body.match(/### Checklist\s*\n([\s\S]*?)(?=###|$)/); + const checklistContent = checklistMatch ? checklistMatch[1] : ''; + const unchecked = (checklistContent.match(/- \[ \]/g) || []).length; + const checked = (checklistContent.match(/- \[x\]/gi) || []).length; + if (checked < 2) { + issues.push('Not all checklist items are checked. Please confirm you have tested locally and have not included unrelated changes.'); + } + } + + // Helper functions + async function addLabel(label) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + labels: [label] + }); + } + + async function removeLabel(label) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + name: label + }); + } catch (e) {} + } + + const hasComplianceLabel = pr.labels.some(l => l.name === 'needs:compliance'); + + if (issues.length > 0) { + // Non-compliant + if (!hasComplianceLabel) { + await addLabel('needs:compliance'); + } + + const marker = ''; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number + }); + const existing = comments.find(c => c.body.includes(marker)); + + const body_text = `${marker} + This PR doesn't fully meet our [contributing guidelines](../blob/dev/CONTRIBUTING.md) and [PR template](../blob/dev/.github/pull_request_template.md). + + **What needs to be fixed:** + ${issues.map(i => `- ${i}`).join('\n')} + + Please edit this PR description to address the above within **2 hours**, or it will be automatically closed. + + If you believe this was flagged incorrectly, please let a maintainer know.`; + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body: body_text + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: body_text + }); + } + + console.log(`PR #${pr.number} is non-compliant: ${issues.join(', ')}`); + } else if (hasComplianceLabel) { + // Was non-compliant, now fixed + await removeLabel('needs:compliance'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number + }); + const marker = ''; + const existing = comments.find(c => c.body.includes(marker)); + if (existing) { + await github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id + }); + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: 'Thanks for updating your PR! It now meets our contributing guidelines. :+1:' + }); + + console.log(`PR #${pr.number} is now compliant, label removed`); + } else { + console.log(`PR #${pr.number} is compliant`); + } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4bec009ef4..2ae3fc6f2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,11 @@ If you are unsure if a PR would be accepted, feel free to ask a maintainer or lo Want to take on an issue? Leave a comment and a maintainer may assign it to you unless it is something we are already working on. +## Adding New Providers + +New providers shouldn't require many if ANY code changes, but if you want to add support for a new provider first make a PR to: +https://github.com/anomalyco/models.dev + ## Developing OpenCode - Requirements: Bun 1.3+ diff --git a/bun.lock b/bun.lock index b2bd70b8e2..bd340ea6e5 100644 --- a/bun.lock +++ b/bun.lock @@ -18,7 +18,7 @@ "husky": "9.1.7", "prettier": "3.6.2", "semver": "^7.6.0", - "sst": "3.17.23", + "sst": "3.18.10", "turbo": "2.5.6", }, }, @@ -289,7 +289,7 @@ "@ai-sdk/vercel": "1.0.33", "@ai-sdk/xai": "2.0.51", "@clack/prompts": "1.0.0-alpha.1", - "@gitlab/gitlab-ai-provider": "3.5.1", + "@gitlab/gitlab-ai-provider": "3.6.0", "@gitlab/opencode-gitlab-auth": "1.3.3", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", @@ -992,7 +992,7 @@ "@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="], - "@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.5.1", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-I8+EGdUeKmGJSjAdFobHtqpxM9Fm00w0j7NJbtln/D/XQ1SKEGoZIuqJko4v0pV2mkhGUIs7qezljH/2kbXovA=="], + "@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-8LmcIQ86xkMtC7L4P1/QYVEC+yKMTRerfPeniaaQGalnzXKtX6iMHLjLPOL9Rxp55lOXi6ed0WrFuJzZx+fNRg=="], "@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="], @@ -1612,7 +1612,7 @@ "@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="], - "@smithy/core": ["@smithy/core@3.23.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Yq4UPVoQICM9zHnByLmG8632t2M0+yap4T7ANVw482J0W7HW0pOuxwVmeOwzJqX2Q89fkXz0Vybz55Wj2Xzrsg=="], + "@smithy/core": ["@smithy/core@3.23.2", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-HaaH4VbGie4t0+9nY3tNBRSxVTr96wzIqexUa6C2qx3MPePAuz7lIxPxYtt1Wc//SPfJLNoZJzfdt0B6ksj2jA=="], "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="], @@ -1642,9 +1642,9 @@ "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="], - "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.14", "", { "dependencies": { "@smithy/core": "^3.23.0", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FUFNE5KVeaY6U/GL0nzAAHkaCHzXLZcY1EhtQnsAqhD8Du13oPKtMB9/0WK4/LK6a/T5OZ24wPoSShff5iI6Ag=="], + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.16", "", { "dependencies": { "@smithy/core": "^3.23.2", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-L5GICFCSsNhbJ5JSKeWFGFy16Q2OhoBizb3X2DrxaJwXSEujVvjG9Jt386dpQn2t7jINglQl0b4K/Su69BdbMA=="], - "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.31", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-RXBzLpMkIrxBPe4C8OmEOHvS8aH9RUuCOH++Acb5jZDEblxDjyg6un72X9IcbrGTJoiUwmI7hLypNfuDACypbg=="], + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.33", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-jLqZOdJhtIL4lnA9hXnAG6GgnJlo1sD3FqsTxm9wSfjviqgWesY/TMBVnT84yr4O0Vfe0jWoXlfFbzsBVph3WA=="], "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="], @@ -1668,7 +1668,7 @@ "@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="], - "@smithy/smithy-client": ["@smithy/smithy-client@4.11.3", "", { "dependencies": { "@smithy/core": "^3.23.0", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-Q7kY5sDau8OoE6Y9zJoRGgje8P4/UY0WzH8R2ok0PDh+iJ+ZnEKowhjEqYafVcubkbYxQVaqwm3iufktzhprGg=="], + "@smithy/smithy-client": ["@smithy/smithy-client@4.11.5", "", { "dependencies": { "@smithy/core": "^3.23.2", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-xixwBRqoeP2IUgcAl3U9dvJXc+qJum4lzo3maaJxifsZxKUYLfVfCXvhT4/jD01sRrHg5zjd1cw2Zmjr4/SuKQ=="], "@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="], @@ -1684,9 +1684,9 @@ "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], - "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.30", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cMni0uVU27zxOiU8TuC8pQLC1pYeZ/xEMxvchSK/ILwleRd1ugobOcIRr5vXtcRqKd4aBLWlpeBoDPJJ91LQng=="], + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.32", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-092sjYfFMQ/iaPH798LY/OJFBcYu0sSK34Oy9vdixhsU36zlZu8OcYjF3TD4e2ARupyK7xaxPXl+T0VIJTEkkg=="], - "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.33", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-LEb2aq5F4oZUSzWBG7S53d4UytZSkOEJPXcBq/xbG2/TmK9EW5naUZ8lKu1BEyWMzdHIzEVN16M3k8oxDq+DJA=="], + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.35", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-miz/ggz87M8VuM29y7jJZMYkn7+IErM5p5UgKIf8OtqVs/h2bXr1Bt3uTsREsI/4nK8a0PQERbAPsVPVNIsG7Q=="], "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="], @@ -1988,7 +1988,7 @@ "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20251207.1", "", { "os": "win32", "cpu": "x64" }, "sha512-5l51HlXjX7lXwo65DEl1IaCFLjmkMtL6K3NrSEamPNeNTtTQwZRa3pQ9V65dCglnnCQ0M3+VF1RqzC7FU0iDKg=="], - "@typescript/vfs": ["@typescript/vfs@1.6.2", "", { "dependencies": { "debug": "^4.1.1" }, "peerDependencies": { "typescript": "*" } }, "sha512-hoBwJwcbKHmvd2QVebiytN1aELvpk9B74B4L1mFm/XT1Q/VOYAWl2vQ9AWRFtQq8zmz6enTpfTV8WRc4ATjW/g=="], + "@typescript/vfs": ["@typescript/vfs@1.6.4", "", { "dependencies": { "debug": "^4.4.3" }, "peerDependencies": { "typescript": "*" } }, "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ=="], "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.3", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA=="], @@ -2054,7 +2054,7 @@ "ai-gateway-provider": ["ai-gateway-provider@2.3.1", "", { "dependencies": { "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.19", "ai": "^5.0.116" }, "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^3.0.71", "@ai-sdk/anthropic": "^2.0.56", "@ai-sdk/azure": "^2.0.90", "@ai-sdk/cerebras": "^1.0.33", "@ai-sdk/cohere": "^2.0.21", "@ai-sdk/deepgram": "^1.0.21", "@ai-sdk/deepseek": "^1.0.32", "@ai-sdk/elevenlabs": "^1.0.21", "@ai-sdk/fireworks": "^1.0.30", "@ai-sdk/google": "^2.0.51", "@ai-sdk/google-vertex": "3.0.90", "@ai-sdk/groq": "^2.0.33", "@ai-sdk/mistral": "^2.0.26", "@ai-sdk/openai": "^2.0.88", "@ai-sdk/perplexity": "^2.0.22", "@ai-sdk/xai": "^2.0.42", "@openrouter/ai-sdk-provider": "^1.5.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^1.0.29" } }, "sha512-PqI6TVNEDNwr7kOhy7XUGnA8XJB1SpeA9aLqGjr0CyWkKgH+y+ofPm8MZGZ74DOwVejDF+POZq0Qs9jKEKUeYg=="], - "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], @@ -2136,11 +2136,11 @@ "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], - "b4a": ["b4a@1.7.4", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-u20zJLDaSWpxaZ+zaAkEIB2dZZ1o+DF4T/MRbmsvGp9nletHOyiai19OzX1fF8xUBYsO1bPXxODvcd0978pnug=="], + "b4a": ["b4a@1.7.5", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-iEsKNwDh1wiWTps1/hdkNdmBgDlDVZP5U57ZVOlt+dNFqpc/lpPouCIxZw+DYBgc4P9NDfIZMPNR4CHNhzwLIA=="], "babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="], - "babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.3", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w=="], + "babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.5", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-8TFKemVLDYezqqv4mWz+PhRrkryTzivTGu0twyLrOkVZ0P63COx2Y04eVsUjFlwSOXui1z3P3Pn209dokWnirg=="], "babel-plugin-module-resolver": ["babel-plugin-module-resolver@5.0.2", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="], @@ -2216,13 +2216,13 @@ "bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="], - "bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eDgLN9teKTfmvrCqgwwmWNsNszxYs7IZdCqk0S1DCarvMhr4wcajoSBlA/nQA0/owwLduPTS8xxCnQp4/N/gDg=="], + "bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qM7W5IaFpWYGPDcNiQ8DOng3noQ97gxpH2MFH1mGsdKwI0T4oy++egSh5Z7s6AQx8WKgc9GzAsTUM4KZkFdacw=="], - "bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-X+PjwJUWenUmdQBP8EtdItMyieQ6Nlpn+BH518oaouDiSnWj5+b0Y7DNDZJq7Ezom4EaxmqL/uGYZK3aCQ7CXg=="], + "bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-oVoIsme27pcXB68YxnQSAgdNGCa4A3PGWYIBUewOh9VnJaoik4JenGb5Yy+svGE+ETFhQXV9nhHqgMPsDRrO6A=="], - "bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.4", "", { "os": "linux", "cpu": "x64" }, "sha512-zMLs2YIGB+/jxrYFXaFhVKX/GBt05UTF45lc9srcHc9JXGjEj+12CIo1CHLTAWatXMTqt0Jsu6ukWEoWVT/ayA=="], + "bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-+SYt09k+xDEl/GfcU7L1zdNgm7IlvAFKV5Xl/auBwuprKG5UwXNhjRlRAWfhTMCUZWN+NDf8E+ZQx0cQi9K2/g=="], - "bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.4", "", { "os": "win32", "cpu": "x64" }, "sha512-Z5yAK28xrcm8Wb5k7TZ8FJKpOI/r+aVCRdlHYAqI2SDJFN3nD4mJs900X6kNVmG/xFzb5yOuKVYWGg+6ZXWbyA=="], + "bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-zvnUl4EAsQbKsmZVu+lEJcH8axQ7MiCfqg2OmnHd6uw1THABmHaX0GbpKiHshdgadNN2Nf+4zDyTJB5YMcAdrA=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], @@ -2242,7 +2242,7 @@ "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001769", "", {}, "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg=="], + "caniuse-lite": ["caniuse-lite@1.0.30001770", "", {}, "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -2476,7 +2476,7 @@ "enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="], - "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="], @@ -2712,7 +2712,7 @@ "h3": ["h3@2.0.1-rc.4", "", { "dependencies": { "rou3": "^0.7.8", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-vZq8pEUp6THsXKXrUXX44eOqfChic2wVQ1GlSzQCBr7DeFBkfIZAo2WyNND4GSv54TAa0E4LYIK73WSPdgKUgw=="], - "happy-dom": ["happy-dom@20.6.1", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^6.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-+0vhESXXhFwkdjZnJ5DlmJIfUYGgIEEjzIjB+aKJbFuqlvvKyOi+XkI1fYbgYR9QCxG5T08koxsQ6HrQfa5gCQ=="], + "happy-dom": ["happy-dom@20.6.2", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-Xk/Y0cuq9ngN/my8uvK4gKoyDl6sBKkIl8A/hJ0IabZVH7E5SJLHNE7uKRPVmSrQbhJaLIHTEcvTct4GgNtsRA=="], "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], @@ -2924,7 +2924,7 @@ "is-whitespace": ["is-whitespace@0.3.0", "", {}, "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg=="], - "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], "is64bit": ["is64bit@2.0.0", "", { "dependencies": { "system-architecture": "^0.1.0" } }, "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw=="], @@ -3502,7 +3502,7 @@ "punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="], - "qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], + "qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="], "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], @@ -3764,23 +3764,23 @@ "srvx": ["srvx@0.9.8", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-RZaxTKJEE/14HYn8COLuUOJAt0U55N9l1Xf6jj+T0GoA01EUH1Xz5JtSUOI+EHn+AEgPCVn7gk6jHJffrr06fQ=="], - "sst": ["sst@3.17.23", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.23", "sst-darwin-x64": "3.17.23", "sst-linux-arm64": "3.17.23", "sst-linux-x64": "3.17.23", "sst-linux-x86": "3.17.23", "sst-win32-arm64": "3.17.23", "sst-win32-x64": "3.17.23", "sst-win32-x86": "3.17.23" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-TwKgUgDnZdc1Swe+bvCNeyO4dQnYz5cTodMpYj3jlXZdK9/KNz0PVxT1f0u5E76i1pmilXrUBL/f7iiMPw4RDg=="], + "sst": ["sst@3.18.10", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.18.10", "sst-darwin-x64": "3.18.10", "sst-linux-arm64": "3.18.10", "sst-linux-x64": "3.18.10", "sst-linux-x86": "3.18.10", "sst-win32-arm64": "3.18.10", "sst-win32-x64": "3.18.10", "sst-win32-x86": "3.18.10" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-SY+ldeJ9K5E9q+DhjXA3e2W3BEOzBwkE3IyLSD71uA3/5nRhUAST31iOWEpW36LbIvSQ9uOVDFcebztoLJ8s7w=="], - "sst-darwin-arm64": ["sst-darwin-arm64@3.17.23", "", { "os": "darwin", "cpu": "arm64" }, "sha512-R6kvmF+rUideOoU7KBs2SdvrIupoE+b+Dor/eq9Uo4Dojj7KvYDZI/EDm8sSCbbcx/opiWeyNqKtlnLEdCxE6g=="], + "sst-darwin-arm64": ["sst-darwin-arm64@3.18.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3MwIpMZhhdZKDqLp9ZQNlwkWix5+q+N0PWstuTomYwgZOxCCe6u9IIsoIszSk+GAJJN/jvGZyLiXKeV4iiQvw=="], - "sst-darwin-x64": ["sst-darwin-x64@3.17.23", "", { "os": "darwin", "cpu": "x64" }, "sha512-WW4P1S35iYCifQXxD+sE3wuzcN+LHLpuKMaNoaBqEcWGZnH3IPaDJ7rpLF0arkDAo/z3jZmWWzOCkr0JuqJ8vQ=="], + "sst-darwin-x64": ["sst-darwin-x64@3.18.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-nQ0jMKkPOa+kj6Ygz8+kYhBua/vgNTLkd+4r8NSmk7v+Zs78lKnx3T//kEzS0yik6Q6QwGfokwrTcA1Jii2xSw=="], - "sst-linux-arm64": ["sst-linux-arm64@3.17.23", "", { "os": "linux", "cpu": "arm64" }, "sha512-TjtNqgIh7RlAWgPLFCAt0mXvIB+J7WjmRvIRrAdX0mXsndOiBJ/DMOgXSLVsIWHCfPj8MIEot/hWpnJgXgIeag=="], + "sst-linux-arm64": ["sst-linux-arm64@3.18.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-mj9VNj3SvLS+HaXx2PhCX0aTA7CwJNoM6JhRc0s/zCilqchcvqDjbhpYBJO4brEPv6aOaaa7T3WvIQqtYauK4Q=="], - "sst-linux-x64": ["sst-linux-x64@3.17.23", "", { "os": "linux", "cpu": "x64" }, "sha512-qdqJiEbYfCjZlI3F/TA6eoIU7JXVkEEI/UMILNf2JWhky0KQdCW2Xyz+wb6c0msVJCWdUM/uj+1DaiP2eXvghw=="], + "sst-linux-x64": ["sst-linux-x64@3.18.10", "", { "os": "linux", "cpu": "x64" }, "sha512-7iy1Eq2eqnT9Ag/8OVgC04vRjV7AAQyf/BvzLc+6Sz+GvRiKA8VEuPnbXNYQF+NIvEqsawfcd7MknSTtImpsvQ=="], - "sst-linux-x86": ["sst-linux-x86@3.17.23", "", { "os": "linux", "cpu": "none" }, "sha512-aGmUujIvoNlmAABEGsOgfY1rxD9koC6hN8bnTLbDI+oI/u/zjHYh50jsbL0p3TlaHpwF/lxP3xFSuT6IKp+KgA=="], + "sst-linux-x86": ["sst-linux-x86@3.18.10", "", { "os": "linux", "cpu": "none" }, "sha512-77qZSuPZeQ5bdRCiq1pQEdY8EcGNHboKrx4P2yFid2FBDKJsXxOXtIxJdloyx+ljBn0+nxl/g040QBmXxdc9tA=="], - "sst-win32-arm64": ["sst-win32-arm64@3.17.23", "", { "os": "win32", "cpu": "arm64" }, "sha512-ZxdkGqYDrrZGz98rijDCN+m5yuCcwD6Bc9/6hubLsvdpNlVorUqzpg801Ec97xSK0nIC9g6pNiRyxAcsQQstUg=="], + "sst-win32-arm64": ["sst-win32-arm64@3.18.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-aY+FhMxvYs8crlrKALpLn/kKmud8YQj6LkMHsrOAAIJhfNyxhCja2vrYQaY+bcqdsS5W2LMVcS2hyaMqKXZKcg=="], - "sst-win32-x64": ["sst-win32-x64@3.17.23", "", { "os": "win32", "cpu": "x64" }, "sha512-yc9cor4MS49Ccy2tQCF1tf6M81yLeSGzGL+gjhUxpVKo2pN3bxl3w70eyU/mTXSEeyAmG9zEfbt6FNu4sy5cUA=="], + "sst-win32-x64": ["sst-win32-x64@3.18.10", "", { "os": "win32", "cpu": "x64" }, "sha512-rY+yJXOpG+P5xXnaQRpCvBK2zwwLhjzpYidGkp6F+cGgiVdh2Wre/CIQNRaVHr20ncj8lLe/RsHWa9QCNM48jg=="], - "sst-win32-x86": ["sst-win32-x86@3.17.23", "", { "os": "win32", "cpu": "none" }, "sha512-DIp3s54IpNAfdYjSRt6McvkbEPQDMxUu6RUeRAd2C+FcTJgTloon/ghAPQBaDgu2VoVgymjcJARO/XyfKcCLOQ=="], + "sst-win32-x86": ["sst-win32-x86@3.18.10", "", { "os": "win32", "cpu": "none" }, "sha512-pq8SmV0pIjBFMY6DraUZ4akyTxHnfjIKCRbBLdMxFUZK8TzA1NK2YdjRt1AwrgXRYGRyctrz/mt4WyO0SMOVQQ=="], "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], @@ -3846,19 +3846,19 @@ "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], - "tar": ["tar@7.5.7", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ=="], + "tar": ["tar@7.5.9", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg=="], "tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="], "tarn": ["tarn@3.0.2", "", {}, "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="], - "tedious": ["tedious@19.2.1", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.5", "@types/node": ">=18", "bl": "^6.1.4", "iconv-lite": "^0.7.0", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-pk1Q16Yl62iocuQB+RWbg6rFUFkIyzqOFQ6NfysCltRvQqKwfurgj8v/f2X+CKvDhSL4IJ0cCOfCHDg9PWEEYA=="], + "tedious": ["tedious@18.6.2", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.1", "@types/node": ">=18", "bl": "^6.0.11", "iconv-lite": "^0.6.3", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-g7jC56o3MzLkE3lHkaFe2ZdOVFBahq5bsB60/M4NYUbocw/MCrS89IOEQUFr+ba6pb8ZHczZ/VqCyYeYq0xBAg=="], "terracotta": ["terracotta@1.1.0", "", { "dependencies": { "solid-use": "^0.9.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-kfQciWUBUBgYkXu7gh3CK3FAJng/iqZslAaY08C+k1Hdx17aVEpcFFb/WPaysxAfcupNH3y53s/pc53xxZauww=="], "terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="], - "text-decoder": ["text-decoder@1.2.4", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-mzlffA3tBNhziEHPK5L5InZg1d/ElNIpJhnhbDRNUtem/edZcJ5zg5FgwKKKOyklxk+6Jt+TrSu83musmvrDlg=="], + "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="], "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], @@ -3966,7 +3966,7 @@ "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], - "undici": ["undici@7.21.0", "", {}, "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg=="], + "undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="], "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], @@ -4132,7 +4132,7 @@ "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="], - "xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], + "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], @@ -4282,8 +4282,6 @@ "@azure/core-http/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], - "@azure/core-http/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], - "@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], "@azure/msal-node/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], @@ -4304,7 +4302,7 @@ "@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], - "@gitlab/gitlab-ai-provider/openai": ["openai@6.21.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-26dQFi76dB8IiN/WKGQOV+yKKTTlRCxQjoi2WLt0kMcH8pvxVyvfdBDkld5GTl7W1qvBpwVOtFcsqktj3fBRpA=="], + "@gitlab/gitlab-ai-provider/openai": ["openai@6.22.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw=="], "@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -4506,7 +4504,9 @@ "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.62", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-I3RhaOEMnWlWnrvjNBOYvUb19Dwf2nw01IruZrVJRDi688886e11wnd5DxrBZLd2V29Gizo3vpOPnnExsA+wTA=="], + "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.63", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-zXlUPCkumnvp8lWS9VFcen/MLF6CL/t1zAKDhpobYj9y/nmylQrKtRvn3RwH871Wd3dF3KYEUXd6M2c6dfCKOA=="], + + "ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@2.0.53", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ccCxr5mrd3AC2CjLq4e1ST7+UiN5T2Pdmgi0XdWM3QohmNBwUQ/RBG7BvL+cB/ex/j6y64tkMmpYz9zBw/SEFQ=="], "ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@3.0.90", "", { "dependencies": { "@ai-sdk/anthropic": "2.0.56", "@ai-sdk/google": "2.0.46", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-C9MLe1KZGg1ZbupV2osygHtL5qngyCDA6ATatunyfTbIe8TXKG8HGni/3O6ifbnI5qxTidIn150Ox7eIFZVMYg=="], @@ -4514,6 +4514,8 @@ "ai-gateway-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@1.0.33", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@ai-sdk/provider-utils": "3.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2KMcR2xAul3u5dGZD7gONgbIki3Hg7Ey+sFu7gsiJ4U2iRU0GDV3ccNq79dTuAEXPDFcOWCUpW8A8jXc0kxJxQ=="], + "ai-gateway-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="], + "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -4534,6 +4536,8 @@ "aws-sdk/events": ["events@1.1.1", "", {}, "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw=="], + "aws-sdk/xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], + "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], "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=="], @@ -4544,6 +4548,10 @@ "body-parser/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + "body-parser/qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], + + "buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "bun-webgpu/@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="], "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], @@ -4578,6 +4586,8 @@ "express/path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="], + "express/qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], + "fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -4586,7 +4596,7 @@ "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - "glob/minimatch": ["minimatch@10.2.0", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w=="], + "glob/minimatch": ["minimatch@10.2.1", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A=="], "globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -4624,8 +4634,6 @@ "mssql/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], - "mssql/tedious": ["tedious@18.6.2", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.1", "@types/node": ">=18", "bl": "^6.0.11", "iconv-lite": "^0.6.3", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-g7jC56o3MzLkE3lHkaFe2ZdOVFBahq5bsB60/M4NYUbocw/MCrS89IOEQUFr+ba6pb8ZHczZ/VqCyYeYq0xBAg=="], - "nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="], "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], @@ -4654,10 +4662,10 @@ "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], - "parse-bmfont-xml/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="], - "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], "pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="], @@ -4714,6 +4722,8 @@ "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "token-types/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], @@ -4730,6 +4740,8 @@ "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=="], + "unstorage/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], + "utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], "vitest/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], @@ -4750,6 +4762,8 @@ "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "xml2js/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + "yaml-language-server/lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], "yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="], @@ -5010,8 +5024,6 @@ "accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - "ai-gateway-provider/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="], - "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@2.0.56", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ=="], "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@2.0.46", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8PK6u4sGE/kXebd7ZkTp+0aya4kNqzoqpS5m7cHY2NfTK6fhPc6GNvE+MZIZIoHQTp5ed86wGBdeBPpFaaUtyg=="], @@ -5020,7 +5032,7 @@ "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="], - "ai-gateway-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="], + "ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="], "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -5040,6 +5052,8 @@ "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=="], + "aws-sdk/xml2js/sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + "babel-plugin-module-resolver/glob/minimatch": ["minimatch@8.0.4", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA=="], "babel-plugin-module-resolver/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="], @@ -5074,8 +5088,6 @@ "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - "mssql/tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - "opencode/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-veuMwTLxsgh31Jjn0SnBABnM1f7ebHhRWcV2ZuY3hP3iJDCZ8VXBaYqcHXoOQDqUXTCas08sKQcHyWK+zl882Q=="], "opencontrol/@modelcontextprotocol/sdk/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=="], diff --git a/github/sst-env.d.ts b/github/sst-env.d.ts index f742a12004..3b8cffd4fd 100644 --- a/github/sst-env.d.ts +++ b/github/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/infra/console.ts b/infra/console.ts index 9089055821..3f3c2b8d93 100644 --- a/infra/console.ts +++ b/infra/console.ts @@ -214,9 +214,9 @@ new sst.cloudflare.x.SolidStart("Console", { }, transform: { server: { + placement: { region: "aws:us-east-1" }, transform: { worker: { - placement: { mode: "smart" }, tailConsumers: [{ service: logProcessor.nodes.worker.scriptName }], }, }, diff --git a/nix/hashes.json b/nix/hashes.json index 3fa1455fc0..d0e314a74d 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-C3WIEER2XgzO85wk2sp3BzQ6dknW026zslD8nKZjo2U=", - "aarch64-linux": "sha256-+tTJHZMZ/+8fAjI/1fUTuca8J2MZfB+5vhBoZ7jgqcE=", - "aarch64-darwin": "sha256-vS82puFGBBToxyIBa8Zi0KLKdJYr64T6HZL2rL32mH8=", - "x86_64-darwin": "sha256-Tr8JMTCxV6WVt3dXV7iq3PNCm2Cn+RXAbU9+o7pKKV0=" + "x86_64-linux": "sha256-7y6gQyIxyrdp2DaG/0oOEpuL+1n9oa8arUn1CuDiDhA=", + "aarch64-linux": "sha256-7dnHO2WqQZ9A8cG3EC8p7408YR9n2F5C6DG5rNWHqNY=", + "aarch64-darwin": "sha256-jxjhnVfE61RVOHaWvDO4mGLk6guQ8jHeXv/pbu5nbaE=", + "x86_64-darwin": "sha256-22yM4FEtVxGWRug6H0rKog86Q/cYE3QsADrRbLeJKVQ=" } } diff --git a/package.json b/package.json index e0008d1028..f1ba10269c 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "husky": "9.1.7", "prettier": "3.6.2", "semver": "^7.6.0", - "sst": "3.17.23", + "sst": "3.18.10", "turbo": "2.5.6" }, "dependencies": { diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 1ca085a428..599711a9b3 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -403,15 +403,10 @@ export const PromptInput: Component = (props) => { const [composing, setComposing] = createSignal(false) const isImeComposing = (event: KeyboardEvent) => event.isComposing || composing() || event.keyCode === 229 - createEffect(() => { - if (!isFocused()) closePopover() - }) - - // Safety: reset composing state on focus change to prevent stuck state - // This handles edge cases where compositionend event may not fire - createEffect(() => { - if (!isFocused()) setComposing(false) - }) + const handleBlur = () => { + closePopover() + setComposing(false) + } const agentList = createMemo(() => sync.data.agent @@ -1118,6 +1113,7 @@ export const PromptInput: Component = (props) => { onPaste={handlePaste} onCompositionStart={() => setComposing(true)} onCompositionEnd={() => setComposing(false)} + onBlur={handleBlur} onKeyDown={handleKeyDown} classList={{ "select-text": true, @@ -1367,7 +1363,8 @@ export const PromptInput: Component = (props) => { diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 912e449cfe..ae8fc200f2 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -257,27 +257,12 @@ export function SessionHeader() { ] as const }) - const checksReady = createMemo(() => { - if (platform.platform !== "desktop") return true - if (!platform.checkAppExists) return true - const list = apps() - return list.every((app) => exists[app.id] !== undefined) - }) - const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp })) const [menu, setMenu] = createStore({ open: false }) const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal()) const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0]) - createEffect(() => { - if (platform.platform !== "desktop") return - if (!checksReady()) return - const value = prefs.app - if (options().some((o) => o.id === value)) return - setPrefs("app", options()[0]?.id ?? "finder") - }) - const openDir = (app: OpenApp) => { const directory = projectDirectory() if (!directory) return @@ -319,9 +304,11 @@ export function SessionHeader() { {(mount) => ( - + )} @@ -398,7 +385,7 @@ export function SessionHeader() { {language.t("session.header.openIn")} { if (!OPEN_APPS.includes(value as OpenApp)) return setPrefs("app", value as OpenApp) @@ -464,7 +451,7 @@ export function SessionHeader() { triggerProps={{ variant: "ghost", class: - "rounded-md h-[24px] px-3 border border-border-base bg-surface-panel shadow-none data-[expanded]:bg-surface-raised-base-active", + "rounded-md h-[24px] px-3 border border-border-weak-base bg-surface-panel shadow-none data-[expanded]:bg-surface-base-active", classList: { "rounded-r-none": share.shareUrl() !== undefined }, style: { scale: 1 }, }} @@ -537,7 +524,7 @@ export function SessionHeader() { share.copyLink((error) => showRequestError(language, error))} disabled={share.state.unshare} aria-label={ diff --git a/packages/app/src/components/status-popover.tsx b/packages/app/src/components/status-popover.tsx index 006b15780a..a846385e79 100644 --- a/packages/app/src/components/status-popover.tsx +++ b/packages/app/src/components/status-popover.tsx @@ -203,7 +203,7 @@ export function StatusPopover() { triggerProps={{ variant: "ghost", class: - "rounded-md h-[24px] pr-3 pl-0.5 gap-2 border border-border-weak-base bg-surface-panel shadow-none data-[expanded]:bg-surface-raised-base-hover", + "rounded-md h-[24px] pr-3 pl-0.5 gap-2 border border-border-weak-base bg-surface-panel shadow-none data-[expanded]:bg-surface-base-active", style: { scale: 1 }, }} trigger={ diff --git a/packages/app/src/context/global-sync.test.ts b/packages/app/src/context/global-sync.test.ts index 396b412318..7956057fd0 100644 --- a/packages/app/src/context/global-sync.test.ts +++ b/packages/app/src/context/global-sync.test.ts @@ -30,7 +30,6 @@ describe("pickDirectoriesToEvict", () => { describe("loadRootSessionsWithFallback", () => { test("uses limited roots query when supported", async () => { const calls: Array<{ directory: string; roots: true; limit?: number }> = [] - let fallback = 0 const result = await loadRootSessionsWithFallback({ directory: "dir", @@ -39,20 +38,15 @@ describe("loadRootSessionsWithFallback", () => { calls.push(query) return { data: [] } }, - onFallback: () => { - fallback += 1 - }, }) expect(result.data).toEqual([]) expect(result.limited).toBe(true) expect(calls).toEqual([{ directory: "dir", roots: true, limit: 10 }]) - expect(fallback).toBe(0) }) test("falls back to full roots query on limited-query failure", async () => { const calls: Array<{ directory: string; roots: true; limit?: number }> = [] - let fallback = 0 const result = await loadRootSessionsWithFallback({ directory: "dir", @@ -62,9 +56,6 @@ describe("loadRootSessionsWithFallback", () => { if (query.limit) throw new Error("unsupported") return { data: [] } }, - onFallback: () => { - fallback += 1 - }, }) expect(result.data).toEqual([]) @@ -73,7 +64,6 @@ describe("loadRootSessionsWithFallback", () => { { directory: "dir", roots: true, limit: 25 }, { directory: "dir", roots: true }, ]) - expect(fallback).toBe(1) }) }) diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 4f3664d8e5..7e242130f1 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -57,14 +57,6 @@ function errorMessage(error: unknown) { return "Unknown error" } -function setDevStats(value: { - activeDirectoryStores: number - evictions: number - loadSessionsFullFetchFallback: number -}) { - ;(globalThis as { __OPENCODE_GLOBAL_SYNC_STATS?: typeof value }).__OPENCODE_GLOBAL_SYNC_STATS = value -} - function createGlobalSync() { const globalSDK = useGlobalSDK() const platform = usePlatform() @@ -72,11 +64,6 @@ function createGlobalSync() { const owner = getOwner() if (!owner) throw new Error("GlobalSync must be created within owner") - const stats = { - evictions: 0, - loadSessionsFallback: 0, - } - const sdkCache = new Map() const booting = new Map>() const sessionLoads = new Map>() @@ -112,15 +99,6 @@ function createGlobalSync() { setGlobalStore("session_todo", sessionID, reconcile(todos, { key: "id" })) } - const updateStats = (activeDirectoryStores: number) => { - if (!import.meta.env.DEV) return - setDevStats({ - activeDirectoryStores, - evictions: stats.evictions, - loadSessionsFullFetchFallback: stats.loadSessionsFallback, - }) - } - const paused = () => untrack(() => globalStore.reload) !== undefined const queue = createRefreshQueue({ @@ -131,11 +109,6 @@ function createGlobalSync() { const children = createChildStoreManager({ owner, - markStats: updateStats, - incrementEvictions: () => { - stats.evictions += 1 - updateStats(Object.keys(children.children).length) - }, isBooting: (directory) => booting.has(directory), isLoadingSessions: (directory) => sessionLoads.has(directory), onBootstrap: (directory) => { @@ -207,10 +180,6 @@ function createGlobalSync() { directory, limit, list: (query) => globalSDK.client.session.list(query), - onFallback: () => { - stats.loadSessionsFallback += 1 - updateStats(Object.keys(children.children).length) - }, }) .then((x) => { const nonArchived = (x.data ?? []) diff --git a/packages/app/src/context/global-sync/child-store.test.ts b/packages/app/src/context/global-sync/child-store.test.ts index 500f0fc70a..cec76ff87e 100644 --- a/packages/app/src/context/global-sync/child-store.test.ts +++ b/packages/app/src/context/global-sync/child-store.test.ts @@ -17,8 +17,6 @@ describe("createChildStoreManager", () => { const manager = createChildStoreManager({ owner, - markStats() {}, - incrementEvictions() {}, isBooting: () => false, isLoadingSessions: () => false, onBootstrap() {}, diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index af08c3bd43..2fe5b78303 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -17,8 +17,6 @@ import { canDisposeDirectory, pickDirectoriesToEvict } from "./eviction" export function createChildStoreManager(input: { owner: Owner - markStats: (activeDirectoryStores: number) => void - incrementEvictions: () => void isBooting: (directory: string) => boolean isLoadingSessions: (directory: string) => boolean onBootstrap: (directory: string) => void @@ -102,7 +100,6 @@ export function createChildStoreManager(input: { } delete children[directory] input.onDispose(directory) - input.markStats(Object.keys(children).length) return true } @@ -120,7 +117,6 @@ export function createChildStoreManager(input: { if (list.length === 0) return for (const directory of list) { if (!disposeDirectory(directory)) continue - input.incrementEvictions() } } @@ -200,7 +196,6 @@ export function createChildStoreManager(input: { }) runWithOwner(input.owner, init) - input.markStats(Object.keys(children).length) } mark(directory) const childStore = children[directory] diff --git a/packages/app/src/context/global-sync/session-load.ts b/packages/app/src/context/global-sync/session-load.ts index 443aa84502..3693dcb460 100644 --- a/packages/app/src/context/global-sync/session-load.ts +++ b/packages/app/src/context/global-sync/session-load.ts @@ -9,7 +9,6 @@ export async function loadRootSessionsWithFallback(input: RootLoadArgs) { limited: true, } as const } catch { - input.onFallback() const result = await input.list({ directory: input.directory, roots: true }) return { data: result.data, diff --git a/packages/app/src/context/global-sync/types.ts b/packages/app/src/context/global-sync/types.ts index ade0b973a2..c61dc337d8 100644 --- a/packages/app/src/context/global-sync/types.ts +++ b/packages/app/src/context/global-sync/types.ts @@ -119,7 +119,6 @@ export type RootLoadArgs = { directory: string limit: number list: (query: { directory: string; roots: true; limit?: number }) => Promise<{ data?: Session[] }> - onFallback: () => void } export type RootLoadResult = { diff --git a/packages/app/src/context/language.tsx b/packages/app/src/context/language.tsx index b21ec6d3cc..905305d3af 100644 --- a/packages/app/src/context/language.tsx +++ b/packages/app/src/context/language.tsx @@ -174,6 +174,10 @@ function detectLocale(): Locale { return "en" } +function normalizeLocale(value: string): Locale { + return LOCALES.includes(value as Locale) ? (value as Locale) : "en" +} + export const { use: useLanguage, provider: LanguageProvider } = createSimpleContext({ name: "Language", init: () => { @@ -184,15 +188,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont }), ) - const locale = createMemo(() => - LOCALES.includes(store.locale as Locale) ? (store.locale as Locale) : "en", - ) - - createEffect(() => { - const current = locale() - if (store.locale === current) return - setStore("locale", current) - }) + const locale = createMemo(() => normalizeLocale(store.locale)) const dict = createMemo(() => DICT[locale()]) @@ -213,7 +209,7 @@ export const { use: useLanguage, provider: LanguageProvider } = createSimpleCont label, t, setLocale(next: Locale) { - setStore("locale", next) + setStore("locale", normalizeLocale(next)) }, } }, diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx index 182f7507f8..336f8aa98c 100644 --- a/packages/app/src/context/server.tsx +++ b/packages/app/src/context/server.tsx @@ -21,11 +21,12 @@ export function serverDisplayName(conn?: ServerConnection.Any) { return conn.http.url.replace(/^https?:\/\//, "").replace(/\/+$/, "") } -function projectsKey(url: string) { - if (!url) return "" - const host = url.replace(/^https?:\/\//, "").split(":")[0] +function projectsKey(key: ServerConnection.Key) { + if (!key) return "" + if (key === "sidecar") return "local" + const host = key.replace(/^https?:\/\//, "").split(":")[0] if (host === "localhost" || host === "127.0.0.1") return "local" - return url + return key } export namespace ServerConnection { @@ -187,10 +188,13 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( const origin = createMemo(() => projectsKey(state.active)) const projectsList = createMemo(() => store.projects[origin()] ?? []) - const isLocal = createMemo(() => origin() === "local") const current: Accessor = createMemo( () => allServers().find((s) => ServerConnection.key(s) === state.active) ?? allServers()[0], ) + const isLocal = createMemo(() => { + const c = current() + return c?.type === "sidecar" && c.variant === "base" + }) return { ready: isReady, diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 470b54a6a9..69a3a86cb2 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -63,8 +63,8 @@ export const dict = { "command.agent.cycle.reverse.description": "التبديل إلى الوكيل السابق", "command.model.variant.cycle": "تغيير جهد التفكير", "command.model.variant.cycle.description": "التبديل إلى مستوى الجهد التالي", - "command.prompt.mode.shell": "التبديل إلى وضع Shell", - "command.prompt.mode.normal": "التبديل إلى وضع Prompt", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "قبول التعديلات تلقائيًا", "command.permissions.autoaccept.disable": "إيقاف قبول التعديلات تلقائيًا", "command.workspace.toggle": "تبديل مساحات العمل", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 53b456ad48..1c37317a37 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -63,8 +63,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Mudar para o agente anterior", "command.model.variant.cycle": "Alternar nível de raciocínio", "command.model.variant.cycle.description": "Mudar para o próximo nível de esforço", - "command.prompt.mode.shell": "Alternar para o modo Shell", - "command.prompt.mode.normal": "Alternar para o modo Prompt", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "Aceitar edições automaticamente", "command.permissions.autoaccept.disable": "Parar de aceitar edições automaticamente", "command.workspace.toggle": "Alternar espaços de trabalho", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 065372c40f..59bab1eb8b 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -69,8 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Prebaci na prethodnog agenta", "command.model.variant.cycle": "Promijeni nivo razmišljanja", "command.model.variant.cycle.description": "Prebaci na sljedeći nivo", - "command.prompt.mode.shell": "Prebaci na Shell način", - "command.prompt.mode.normal": "Prebaci na Prompt način", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "Automatski prihvataj izmjene", "command.permissions.autoaccept.disable": "Zaustavi automatsko prihvatanje izmjena", "command.workspace.toggle": "Prikaži/sakrij radne prostore", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 886548c845..ce33ceec31 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -69,8 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Skift til forrige agent", "command.model.variant.cycle": "Skift tænkeindsats", "command.model.variant.cycle.description": "Skift til næste indsatsniveau", - "command.prompt.mode.shell": "Skift til shell-tilstand", - "command.prompt.mode.normal": "Skift til prompt-tilstand", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "Accepter ændringer automatisk", "command.permissions.autoaccept.disable": "Stop automatisk accept af ændringer", "command.workspace.toggle": "Skift arbejdsområder", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index 6e83811efb..cf3416be2d 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -67,8 +67,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Zum vorherigen Agenten wechseln", "command.model.variant.cycle": "Denkaufwand wechseln", "command.model.variant.cycle.description": "Zum nächsten Aufwandslevel wechseln", - "command.prompt.mode.shell": "In den Shell-Modus wechseln", - "command.prompt.mode.normal": "In den Prompt-Modus wechseln", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "Änderungen automatisch akzeptieren", "command.permissions.autoaccept.disable": "Automatische Annahme von Änderungen stoppen", "command.workspace.toggle": "Arbeitsbereiche umschalten", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 6fcec384f1..8837dcbad0 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -69,8 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Switch to the previous agent", "command.model.variant.cycle": "Cycle thinking effort", "command.model.variant.cycle.description": "Switch to the next effort level", - "command.prompt.mode.shell": "Switch to shell mode", - "command.prompt.mode.normal": "Switch to prompt mode", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "Auto-accept edits", "command.permissions.autoaccept.disable": "Stop auto-accepting edits", "command.workspace.toggle": "Toggle workspaces", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 05fc817093..d741bb138b 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -69,8 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Cambiar al agente anterior", "command.model.variant.cycle": "Alternar esfuerzo de pensamiento", "command.model.variant.cycle.description": "Cambiar al siguiente nivel de esfuerzo", - "command.prompt.mode.shell": "Cambiar al modo Shell", - "command.prompt.mode.normal": "Cambiar al modo Prompt", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "Aceptar ediciones automáticamente", "command.permissions.autoaccept.disable": "Dejar de aceptar ediciones automáticamente", "command.workspace.toggle": "Alternar espacios de trabajo", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 811bb1968b..686539df4d 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -63,8 +63,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Passer à l'agent précédent", "command.model.variant.cycle": "Changer l'effort de réflexion", "command.model.variant.cycle.description": "Passer au niveau d'effort suivant", - "command.prompt.mode.shell": "Passer en mode Shell", - "command.prompt.mode.normal": "Passer en mode Prompt", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "Accepter automatiquement les modifications", "command.permissions.autoaccept.disable": "Arrêter l'acceptation automatique des modifications", "command.workspace.toggle": "Basculer les espaces de travail", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 6bc25c250f..24898403ec 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -63,8 +63,8 @@ export const dict = { "command.agent.cycle.reverse.description": "前のエージェントに切り替え", "command.model.variant.cycle": "思考レベルの切り替え", "command.model.variant.cycle.description": "次の思考レベルに切り替え", - "command.prompt.mode.shell": "シェルモードに切り替える", - "command.prompt.mode.normal": "プロンプトモードに切り替える", + "command.prompt.mode.shell": "シェル", + "command.prompt.mode.normal": "プロンプト", "command.permissions.autoaccept.enable": "編集を自動承認", "command.permissions.autoaccept.disable": "編集の自動承認を停止", "command.workspace.toggle": "ワークスペースを切り替え", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index c3a197941e..72a46ca7e6 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -67,8 +67,8 @@ export const dict = { "command.agent.cycle.reverse.description": "이전 에이전트로 전환", "command.model.variant.cycle": "생각 수준 순환", "command.model.variant.cycle.description": "다음 생각 수준으로 전환", - "command.prompt.mode.shell": "셸 모드로 전환", - "command.prompt.mode.normal": "프롬프트 모드로 전환", + "command.prompt.mode.shell": "셸", + "command.prompt.mode.normal": "프롬프트", "command.permissions.autoaccept.enable": "편집 자동 수락", "command.permissions.autoaccept.disable": "편집 자동 수락 중지", "command.workspace.toggle": "작업 공간 전환", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 9a193cba0a..c099fe61f9 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -72,8 +72,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Bytt til forrige agent", "command.model.variant.cycle": "Bytt tenkeinnsats", "command.model.variant.cycle.description": "Bytt til neste innsatsnivå", - "command.prompt.mode.shell": "Bytt til Shell-modus", - "command.prompt.mode.normal": "Bytt til Prompt-modus", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "Godta endringer automatisk", "command.permissions.autoaccept.disable": "Slutt å godta endringer automatisk", "command.workspace.toggle": "Veksle arbeidsområder", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 70b7ae2097..67c9dda2ac 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -63,8 +63,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Przełącz na poprzedniego agenta", "command.model.variant.cycle": "Przełącz wysiłek myślowy", "command.model.variant.cycle.description": "Przełącz na następny poziom wysiłku", - "command.prompt.mode.shell": "Przełącz na tryb terminala", - "command.prompt.mode.normal": "Przełącz na tryb Prompt", + "command.prompt.mode.shell": "Terminal", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "Automatyczne akceptowanie edycji", "command.permissions.autoaccept.disable": "Zatrzymaj automatyczne akceptowanie edycji", "command.workspace.toggle": "Przełącz przestrzenie robocze", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 1b1a12b0af..57ef82fd66 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -69,8 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Переключиться к предыдущему агенту", "command.model.variant.cycle": "Цикл режимов мышления", "command.model.variant.cycle.description": "Переключиться к следующему уровню усилий", - "command.prompt.mode.shell": "Переключиться в режим оболочки", - "command.prompt.mode.normal": "Переключиться в режим промпта", + "command.prompt.mode.shell": "Оболочка", + "command.prompt.mode.normal": "Промпт", "command.permissions.autoaccept.enable": "Авто-принятие изменений", "command.permissions.autoaccept.disable": "Прекратить авто-принятие изменений", "command.workspace.toggle": "Переключить рабочие пространства", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index fbd237c082..e67db04651 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -69,8 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "สลับไปยังเอเจนต์ก่อนหน้า", "command.model.variant.cycle": "เปลี่ยนความพยายามในการคิด", "command.model.variant.cycle.description": "สลับไปยังระดับความพยายามถัดไป", - "command.prompt.mode.shell": "สลับไปยังโหมดเชลล์", - "command.prompt.mode.normal": "สลับไปยังโหมดพรอมต์", + "command.prompt.mode.shell": "เชลล์", + "command.prompt.mode.normal": "พรอมต์", "command.permissions.autoaccept.enable": "ยอมรับการแก้ไขโดยอัตโนมัติ", "command.permissions.autoaccept.disable": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ", "command.workspace.toggle": "สลับพื้นที่ทำงาน", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 281cbaac11..42740fa771 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -93,8 +93,8 @@ export const dict = { "command.model.variant.cycle": "切换思考强度", "command.model.variant.cycle.description": "切换到下一个强度等级", - "command.prompt.mode.shell": "切换到 Shell 模式", - "command.prompt.mode.normal": "切换到 Prompt 模式", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "自动接受编辑", "command.permissions.autoaccept.disable": "停止自动接受编辑", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 5278756a27..f47fdede8f 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -73,8 +73,8 @@ export const dict = { "command.agent.cycle.reverse.description": "切換到上一個代理程式", "command.model.variant.cycle": "循環思考強度", "command.model.variant.cycle.description": "切換到下一個強度等級", - "command.prompt.mode.shell": "切換到 Shell 模式", - "command.prompt.mode.normal": "切換到 Prompt 模式", + "command.prompt.mode.shell": "Shell", + "command.prompt.mode.normal": "Prompt", "command.permissions.autoaccept.enable": "自動接受編輯", "command.permissions.autoaccept.disable": "停止自動接受編輯", "command.workspace.toggle": "切換工作區", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index cecb526171..29ba142e51 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -51,7 +51,6 @@ import { DialogSelectServer } from "@/components/dialog-select-server" import { DialogSettings } from "@/components/dialog-settings" import { useCommand, type CommandOption } from "@/context/command" import { ConstrainDragXAxis } from "@/utils/solid-dnd" -import { navStart } from "@/utils/perf" import { DialogSelectDirectory } from "@/components/dialog-select-directory" import { DialogEditProject } from "@/components/dialog-edit-project" import { Titlebar } from "@/components/titlebar" @@ -178,7 +177,12 @@ export default function Layout(props: ParentProps) { const sidebarHovering = createMemo(() => !layout.sidebar.opened() && state.hoverProject !== undefined) const sidebarExpanded = createMemo(() => layout.sidebar.opened() || sidebarHovering()) - const clearHoverProjectSoon = () => queueMicrotask(() => setState("hoverProject", undefined)) + const setHoverProject = (value: string | undefined) => { + setState("hoverProject", value) + if (value !== undefined) return + aim.reset() + } + const clearHoverProjectSoon = () => queueMicrotask(() => setHoverProject(undefined)) const setHoverSession = (id: string | undefined) => setState("hoverSession", id) const hoverProjectData = createMemo(() => { @@ -189,13 +193,7 @@ export default function Layout(props: ParentProps) { createEffect(() => { if (!layout.sidebar.opened()) return - aim.reset() - setState("hoverProject", undefined) - }) - - createEffect(() => { - if (state.hoverProject !== undefined) return - aim.reset() + setHoverProject(undefined) }) const autoselecting = createMemo(() => { @@ -226,7 +224,7 @@ export default function Layout(props: ParentProps) { const clearSidebarHoverState = () => { if (layout.sidebar.opened()) return setState("hoverSession", undefined) - setState("hoverProject", undefined) + setHoverProject(undefined) } const navigateWithSidebarReset = (href: string) => { @@ -826,14 +824,6 @@ export default function Layout(props: ParentProps) { if (next) prefetchSession(next) } - if (import.meta.env.DEV) { - navStart({ - dir: base64Encode(session.directory), - from: params.id, - to: session.id, - trigger: offset > 0 ? "alt+arrowdown" : "alt+arrowup", - }) - } navigateToSession(session) queueMicrotask(() => scrollToSession(session.id, `${session.directory}:${session.id}`)) } @@ -869,15 +859,6 @@ export default function Layout(props: ParentProps) { if (next) prefetchSession(next) } - if (import.meta.env.DEV) { - navStart({ - dir: base64Encode(session.directory), - from: params.id, - to: session.id, - trigger: offset > 0 ? "shift+alt+arrowdown" : "shift+alt+arrowup", - }) - } - navigateToSession(session) queueMicrotask(() => scrollToSession(session.id, `${session.directory}:${session.id}`)) return @@ -1508,7 +1489,7 @@ export default function Layout(props: ParentProps) { function handleDragStart(event: unknown) { const id = getDraggableId(event) if (!id) return - setState("hoverProject", undefined) + setHoverProject(undefined) setStore("activeProject", id) } @@ -1942,7 +1923,7 @@ export default function Layout(props: ParentProps) { if (navLeave.current !== undefined) clearTimeout(navLeave.current) navLeave.current = window.setTimeout(() => { navLeave.current = undefined - setState("hoverProject", undefined) + setHoverProject(undefined) setState("hoverSession", undefined) }, 300) }} diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 7d950b3466..21ba4e7d7b 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1,47 +1,29 @@ -import { For, onCleanup, Show, Match, Switch, createMemo, createEffect, on } from "solid-js" +import { onCleanup, Show, Match, Switch, createMemo, createEffect, on, onMount } from "solid-js" import { createMediaQuery } from "@solid-primitives/media" import { createResizeObserver } from "@solid-primitives/resize-observer" import { useLocal } from "@/context/local" import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file" -import { createStore, produce } from "solid-js/store" -import { IconButton } from "@opencode-ai/ui/icon-button" -import { Button } from "@opencode-ai/ui/button" -import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" +import { createStore } from "solid-js/store" import { ResizeHandle } from "@opencode-ai/ui/resize-handle" -import { Tabs } from "@opencode-ai/ui/tabs" import { Select } from "@opencode-ai/ui/select" import { createAutoScroll } from "@opencode-ai/ui/hooks" import { Mark } from "@opencode-ai/ui/logo" import { useSync } from "@/context/sync" -import { useTerminal } from "@/context/terminal" import { useLayout } from "@/context/layout" import { checksum, base64Encode } from "@opencode-ai/util/encode" -import { findLast } from "@opencode-ai/util/array" import { useDialog } from "@opencode-ai/ui/context/dialog" -import { DialogSelectFile } from "@/components/dialog-select-file" -import FileTree from "@/components/file-tree" -import { useCommand } from "@/context/command" import { useLanguage } from "@/context/language" import { useNavigate, useParams } from "@solidjs/router" import { UserMessage } from "@opencode-ai/sdk/v2" import { useSDK } from "@/context/sdk" import { usePrompt } from "@/context/prompt" import { useComments } from "@/context/comments" -import { usePermission } from "@/context/permission" -import { showToast } from "@opencode-ai/ui/toast" import { SessionHeader, NewSessionView } from "@/components/session" -import { navMark, navParams } from "@/utils/perf" import { same } from "@/utils/same" import { createOpenReviewFile } from "@/pages/session/helpers" import { createScrollSpy } from "@/pages/session/scroll-spy" -import { createFileTabListSync } from "@/pages/session/file-tab-scroll" -import { - SessionReviewTab, - StickyAddButton, - type DiffStyle, - type SessionReviewTabProps, -} from "@/pages/session/review-tab" +import { SessionReviewTab, type DiffStyle, type SessionReviewTabProps } from "@/pages/session/review-tab" import { TerminalPanel } from "@/pages/session/terminal-panel" import { MessageTimeline } from "@/pages/session/message-timeline" import { useSessionCommands } from "@/pages/session/use-session-commands" @@ -55,16 +37,13 @@ export default function Page() { const local = useLocal() const file = useFile() const sync = useSync() - const terminal = useTerminal() const dialog = useDialog() - const command = useCommand() const language = useLanguage() const params = useParams() const navigate = useNavigate() const sdk = useSDK() const prompt = usePrompt() const comments = useComments() - const permission = usePermission() const [ui, setUi] = createStore({ pendingMessage: undefined as string | undefined, @@ -123,46 +102,6 @@ export default function Page() { ), ) - if (import.meta.env.DEV) { - createEffect( - on( - () => [params.dir, params.id] as const, - ([dir, id], prev) => { - if (!id) return - navParams({ dir, from: prev?.[1], to: id }) - }, - ), - ) - - createEffect(() => { - const id = params.id - if (!id) return - if (!prompt.ready()) return - navMark({ dir: params.dir, to: id, name: "storage:prompt-ready" }) - }) - - createEffect(() => { - const id = params.id - if (!id) return - if (!terminal.ready()) return - navMark({ dir: params.dir, to: id, name: "storage:terminal-ready" }) - }) - - createEffect(() => { - const id = params.id - if (!id) return - if (!file.ready()) return - navMark({ dir: params.dir, to: id, name: "storage:file-view-ready" }) - }) - - createEffect(() => { - const id = params.id - if (!id) return - if (sync.data.message[id] === undefined) return - navMark({ dir: params.dir, to: id, name: "session:data-ready" }) - }) - } - const isDesktop = createMediaQuery("(min-width: 768px)") const desktopReviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened()) const desktopFileTreeOpen = createMemo(() => isDesktop() && layout.fileTree.opened()) @@ -195,16 +134,6 @@ export default function Page() { if (!view().reviewPanel.opened()) view().reviewPanel.open() } - const openTab = (value: string) => { - const next = normalizeTab(value) - tabs().open(next) - - const path = file.pathFromTab(next) - if (!path) return - file.load(path) - openReviewPanel() - } - createEffect(() => { const active = tabs().active() if (!active) return @@ -343,33 +272,6 @@ export default function Page() { scrollToMessage(msgs[targetIndex], "auto") } - const kinds = createMemo(() => { - const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => { - if (!a) return b - if (a === b) return a - return "mix" as const - } - - const normalize = (p: string) => p.replaceAll("\\\\", "/").replace(/\/+$/, "") - - const out = new Map() - for (const diff of diffs()) { - const file = normalize(diff.file) - const kind = diff.status === "added" ? "add" : diff.status === "deleted" ? "del" : "mix" - - out.set(file, kind) - - const parts = file.split("/") - for (const [idx] of parts.slice(0, -1).entries()) { - const dir = parts.slice(0, idx + 1).join("/") - if (!dir) continue - out.set(dir, merge(out.get(dir), kind)) - } - } - return out - }) - const emptyDiffFiles: string[] = [] - const diffFiles = createMemo(() => diffs().map((d) => d.file), emptyDiffFiles, { equals: same }) const diffsReady = createMemo(() => { const id = params.id if (!id) return true @@ -377,7 +279,6 @@ export default function Page() { return sync.data.session_diff[id] !== undefined }) - const idle = { type: "idle" as const } let inputRef!: HTMLDivElement let promptDock: HTMLDivElement | undefined let dockHeight = 0 @@ -419,8 +320,6 @@ export default function Page() { ), ) - const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle) - createEffect( on( sessionKey, @@ -453,11 +352,6 @@ export default function Page() { return lines.slice(0, 2).join("\n") } - const addSelectionToContext = (path: string, selection: FileSelection) => { - const preview = selectionPreview(path, selection) - prompt.context.add({ type: "file", path, selection, preview }) - } - const addCommentToContext = (input: { file: string selection: SelectedLineRange @@ -549,29 +443,8 @@ export default function Page() { const focusInput = () => inputRef?.focus() useSessionCommands({ - command, - dialog, - file, - language, - local, - permission, - prompt, - sdk, - sync, - terminal, - layout, - params, - navigate, - tabs, - view, - info, - status, - userMessages, - visibleUserMessages, - showAllFiles, navigateMessageByOffset, setActiveMessage, - addSelectionToContext, focusInput, }) @@ -709,11 +582,6 @@ export default function Page() { ), ) - const setFileTreeTabValue = (value: string) => { - if (value !== "changes" && value !== "all") return - setFileTreeTab(value) - } - const reviewDiffId = (path: string) => { const sum = checksum(path) if (!sum) return @@ -809,12 +677,6 @@ export default function Page() { return "empty" }) - const activeFileTab = createMemo(() => { - const active = activeTab() - if (!openedTabs().includes(active)) return - return active - }) - createEffect(() => { if (!layout.ready()) return if (tabs().active()) return @@ -1119,7 +981,7 @@ export default function Page() { consumePendingMessage: layout.pendingMessage.consume, }) - createEffect(() => { + onMount(() => { document.addEventListener("keydown", handleKeyDown) }) @@ -1202,11 +1064,6 @@ export default function Page() { anchor={anchor} onRegisterMessage={scrollSpy.register} onUnregisterMessage={scrollSpy.unregister} - onFirstTurnMount={() => { - const id = params.id - if (!id) return - navMark({ dir: params.dir, to: id, name: "session:first-turn-mounted" }) - }} lastUserMessageID={lastUserMessage()?.id} /> diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx index 9e3a543114..ebc1f59227 100644 --- a/packages/app/src/pages/session/file-tabs.tsx +++ b/packages/app/src/pages/session/file-tabs.tsx @@ -168,6 +168,13 @@ export function FileTabContent(props: { tab: string }) { draftTop: undefined as number | undefined, }) + const setCommenting = (range: SelectedLineRange | null) => { + setNote("commenting", range) + scheduleComments() + if (!range) return + setNote("draft", "") + } + const getRoot = () => { const el = wrap if (!el) return @@ -260,13 +267,6 @@ export function FileTabContent(props: { tab: string }) { scheduleComments() }) - createEffect(() => { - const range = note.commenting - scheduleComments() - if (!range) return - setNote("draft", "") - }) - createEffect(() => { const focus = comments.focus() const p = path() @@ -278,7 +278,7 @@ export function FileTabContent(props: { tab: string }) { if (!target) return setNote("openedComment", target.id) - setNote("commenting", null) + setCommenting(null) file.setSelectedLines(p, target.selection) requestAnimationFrame(() => comments.clearFocus()) }) @@ -438,16 +438,16 @@ export function FileTabContent(props: { tab: string }) { const p = path() if (!p) return file.setSelectedLines(p, range) - if (!range) setNote("commenting", null) + if (!range) setCommenting(null) }} onLineSelectionEnd={(range: SelectedLineRange | null) => { if (!range) { - setNote("commenting", null) + setCommenting(null) return } setNote("openedComment", null) - setNote("commenting", range) + setCommenting(range) }} overflow="scroll" class="select-text" @@ -468,7 +468,7 @@ export function FileTabContent(props: { tab: string }) { onClick={() => { const p = path() if (!p) return - setNote("commenting", null) + setCommenting(null) setNote("openedComment", (current) => (current === comment.id ? null : comment.id)) file.setSelectedLines(p, comment.selection) }} @@ -483,12 +483,12 @@ export function FileTabContent(props: { tab: string }) { value={note.draft} selection={formatCommentLabel(range())} onInput={(value) => setNote("draft", value)} - onCancel={() => setNote("commenting", null)} + onCancel={() => setCommenting(null)} onSubmit={(value) => { const p = path() if (!p) return addCommentToContext({ file: p, selection: range(), comment: value, origin: "file" }) - setNote("commenting", null) + setCommenting(null) }} onPopoverFocusOut={(e: FocusEvent) => { const current = e.currentTarget as HTMLDivElement @@ -497,7 +497,7 @@ export function FileTabContent(props: { tab: string }) { setTimeout(() => { if (!document.activeElement || !current.contains(document.activeElement)) { - setNote("commenting", null) + setCommenting(null) } }, 0) }} diff --git a/packages/app/src/pages/session/helpers.test.ts b/packages/app/src/pages/session/helpers.test.ts index 8b9746507e..7d357e6572 100644 --- a/packages/app/src/pages/session/helpers.test.ts +++ b/packages/app/src/pages/session/helpers.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { createOpenReviewFile, focusTerminalById, getTabReorderIndex } from "./helpers" +import { createOpenReviewFile, createOpenSessionFileTab, focusTerminalById, getTabReorderIndex } from "./helpers" describe("createOpenReviewFile", () => { test("opens and loads selected review file", () => { @@ -20,6 +20,37 @@ describe("createOpenReviewFile", () => { }) }) +describe("createOpenSessionFileTab", () => { + test("activates the opened file tab", () => { + const calls: string[] = [] + const openTab = createOpenSessionFileTab({ + normalizeTab: (value) => { + calls.push(`normalize:${value}`) + return `file://${value}` + }, + openTab: (tab) => calls.push(`open:${tab}`), + pathFromTab: (tab) => { + calls.push(`path:${tab}`) + return tab.slice("file://".length) + }, + loadFile: (path) => calls.push(`load:${path}`), + openReviewPanel: () => calls.push("review"), + setActive: (tab) => calls.push(`active:${tab}`), + }) + + openTab("src/a.ts") + + expect(calls).toEqual([ + "normalize:src/a.ts", + "open:file://src/a.ts", + "path:file://src/a.ts", + "load:src/a.ts", + "review", + "active:file://src/a.ts", + ]) + }) +}) + describe("focusTerminalById", () => { test("focuses textarea when present", () => { document.body.innerHTML = `
` diff --git a/packages/app/src/pages/session/helpers.ts b/packages/app/src/pages/session/helpers.ts index 5ca355d1d2..995f6eb191 100644 --- a/packages/app/src/pages/session/helpers.ts +++ b/packages/app/src/pages/session/helpers.ts @@ -35,6 +35,27 @@ export const createOpenReviewFile = (input: { } } +export const createOpenSessionFileTab = (input: { + normalizeTab: (tab: string) => string + openTab: (tab: string) => void + pathFromTab: (tab: string) => string | undefined + loadFile: (path: string) => void + openReviewPanel: () => void + setActive: (tab: string) => void +}) => { + return (value: string) => { + const next = input.normalizeTab(value) + input.openTab(next) + + const path = input.pathFromTab(next) + if (!path) return + + input.loadFile(path) + input.openReviewPanel() + input.setActive(next) + } +} + export const getTabReorderIndex = (tabs: readonly string[], from: string, to: string) => { const fromIndex = tabs.indexOf(from) const toIndex = tabs.indexOf(to) diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index b949424085..c65e2600e8 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -1,4 +1,4 @@ -import { For, createEffect, createMemo, on, onCleanup, onMount, Show, type JSX } from "solid-js" +import { For, createEffect, createMemo, on, onCleanup, Show, type JSX } from "solid-js" import { createStore, produce } from "solid-js/store" import { useNavigate, useParams } from "@solidjs/router" import { Button } from "@opencode-ai/ui/button" @@ -72,7 +72,6 @@ export function MessageTimeline(props: { anchor: (id: string) => string onRegisterMessage: (el: HTMLDivElement, id: string) => void onUnregisterMessage: (id: string) => void - onFirstTurnMount?: () => void lastUserMessageID?: string }) { let touchGesture: number | undefined @@ -516,37 +515,31 @@ export function MessageTimeline(props: { - {(message) => { - if (import.meta.env.DEV && props.onFirstTurnMount) { - onMount(() => props.onFirstTurnMount?.()) - } - - return ( -
{ - props.onRegisterMessage(el, message.id) - onCleanup(() => props.onUnregisterMessage(message.id)) + {(message) => ( +
{ + props.onRegisterMessage(el, message.id) + onCleanup(() => props.onUnregisterMessage(message.id)) + }} + classList={{ + "min-w-0 w-full max-w-full": true, + "md:max-w-200 2xl:max-w-[1000px]": props.centered, + }} + > + - -
- ) - }} + /> +
+ )}
diff --git a/packages/app/src/pages/session/review-tab.tsx b/packages/app/src/pages/session/review-tab.tsx index 634491c72d..3a9f63949a 100644 --- a/packages/app/src/pages/session/review-tab.tsx +++ b/packages/app/src/pages/session/review-tab.tsx @@ -144,8 +144,8 @@ export function SessionReviewTab(props: SessionReviewTabProps) { onOpenChange={props.view().review.setOpen} classes={{ root: props.classes?.root ?? "pb-6", - header: props.classes?.header ?? "px-6", - container: props.classes?.container ?? "px-6", + header: props.classes?.header ?? "px-3", + container: props.classes?.container ?? "px-3", }} diffs={props.diffs()} diffStyle={props.diffStyle} diff --git a/packages/app/src/pages/session/session-prompt-dock.tsx b/packages/app/src/pages/session/session-prompt-dock.tsx index 3f0b7a6e8e..abe12bcb00 100644 --- a/packages/app/src/pages/session/session-prompt-dock.tsx +++ b/packages/app/src/pages/session/session-prompt-dock.tsx @@ -70,29 +70,28 @@ export function SessionPromptDock(props: { setSessionHandoff(sessionKey(), { prompt: previewPrompt() }) }) - const [responding, setResponding] = createSignal(false) - - createEffect( - on( - () => permissionRequest()?.id, - () => setResponding(false), - { defer: true }, - ), - ) + const [responding, setResponding] = createSignal() + const permissionResponding = () => { + const perm = permissionRequest() + if (!perm) return false + return responding() === perm.id + } const decide = (response: "once" | "always" | "reject") => { const perm = permissionRequest() if (!perm) return - if (responding()) return + if (responding() === perm.id) return - setResponding(true) + setResponding(perm.id) sdk.client.permission .respond({ sessionID: perm.sessionID, permissionID: perm.id, response }) .catch((err: unknown) => { const message = err instanceof Error ? err.message : String(err) showToast({ title: language.t("common.requestFailed"), description: message }) }) - .finally(() => setResponding(false)) + .finally(() => { + setResponding((id) => (id === perm.id ? undefined : id)) + }) } const done = createMemo( @@ -218,18 +217,28 @@ export function SessionPromptDock(props: { <>
- -
diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx index 68dfc346f9..07b18f3146 100644 --- a/packages/app/src/pages/session/session-side-panel.tsx +++ b/packages/app/src/pages/session/session-side-panel.tsx @@ -23,7 +23,7 @@ import { useLayout } from "@/context/layout" import { useSync } from "@/context/sync" import { createFileTabListSync } from "@/pages/session/file-tab-scroll" import { FileTabContent } from "@/pages/session/file-tabs" -import { getTabReorderIndex } from "@/pages/session/helpers" +import { createOpenSessionFileTab, getTabReorderIndex } from "@/pages/session/helpers" import { StickyAddButton } from "@/pages/session/review-tab" import { setSessionHandoff } from "@/pages/session/handoff" @@ -96,15 +96,14 @@ export function SessionSidePanel(props: { if (!view().reviewPanel.opened()) view().reviewPanel.open() } - const openTab = (value: string) => { - const next = normalizeTab(value) - tabs().open(next) - - const path = file.pathFromTab(next) - if (!path) return - file.load(path) - openReviewPanel() - } + const openTab = createOpenSessionFileTab({ + normalizeTab, + openTab: tabs().open, + pathFromTab: file.pathFromTab, + loadFile: file.load, + openReviewPanel, + setActive: tabs().setActive, + }) const contextOpen = createMemo(() => tabs().active() === "context" || tabs().all().includes("context")) const openedTabs = createMemo(() => @@ -355,7 +354,7 @@ export function SessionSidePanel(props: { {language.t("session.files.all")} - + - + - dialog: ReturnType - file: ReturnType - language: ReturnType - local: ReturnType - permission: ReturnType - prompt: ReturnType - sdk: ReturnType - sync: ReturnType - terminal: ReturnType - layout: ReturnType - params: ReturnType - navigate: ReturnType - tabs: () => ReturnType["tabs"]> - view: () => ReturnType["view"]> - info: () => { revert?: { messageID?: string }; share?: { url?: string } } | undefined - status: () => { type: string } - userMessages: () => UserMessage[] - visibleUserMessages: () => UserMessage[] - showAllFiles: () => void navigateMessageByOffset: (offset: number) => void setActiveMessage: (message: UserMessage | undefined) => void - addSelectionToContext: (path: string, selection: FileSelection) => void focusInput: () => void } @@ -55,45 +34,98 @@ const withCategory = (category: string) => { }) } -export const useSessionCommands = (input: SessionCommandContext) => { - const sessionCommand = withCategory(input.language.t("command.category.session")) - const fileCommand = withCategory(input.language.t("command.category.file")) - const contextCommand = withCategory(input.language.t("command.category.context")) - const viewCommand = withCategory(input.language.t("command.category.view")) - const terminalCommand = withCategory(input.language.t("command.category.terminal")) - const modelCommand = withCategory(input.language.t("command.category.model")) - const mcpCommand = withCategory(input.language.t("command.category.mcp")) - const agentCommand = withCategory(input.language.t("command.category.agent")) - const permissionsCommand = withCategory(input.language.t("command.category.permissions")) +export const useSessionCommands = (actions: SessionCommandContext) => { + const command = useCommand() + const dialog = useDialog() + const file = useFile() + const language = useLanguage() + const local = useLocal() + const permission = usePermission() + const prompt = usePrompt() + const sdk = useSDK() + const sync = useSync() + const terminal = useTerminal() + const layout = useLayout() + const params = useParams() + const navigate = useNavigate() + + const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) + const tabs = createMemo(() => layout.tabs(sessionKey)) + const view = createMemo(() => layout.view(sessionKey)) + const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) + + const idle = { type: "idle" as const } + const status = createMemo(() => sync.data.session_status[params.id ?? ""] ?? idle) + const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : [])) + const userMessages = createMemo(() => messages().filter((m) => m.role === "user") as UserMessage[]) + const visibleUserMessages = createMemo(() => { + const revert = info()?.revert?.messageID + if (!revert) return userMessages() + return userMessages().filter((m) => m.id < revert) + }) + + const showAllFiles = () => { + if (layout.fileTree.tab() !== "changes") return + layout.fileTree.setTab("all") + } + + const selectionPreview = (path: string, selection: FileSelection) => { + const content = file.get(path)?.content?.content + if (!content) return undefined + const start = Math.max(1, Math.min(selection.startLine, selection.endLine)) + const end = Math.max(selection.startLine, selection.endLine) + const lines = content.split("\n").slice(start - 1, end) + if (lines.length === 0) return undefined + return lines.slice(0, 2).join("\n") + } + + const addSelectionToContext = (path: string, selection: FileSelection) => { + const preview = selectionPreview(path, selection) + prompt.context.add({ type: "file", path, selection, preview }) + } + + const navigateMessageByOffset = actions.navigateMessageByOffset + const setActiveMessage = actions.setActiveMessage + const focusInput = actions.focusInput + + const sessionCommand = withCategory(language.t("command.category.session")) + const fileCommand = withCategory(language.t("command.category.file")) + const contextCommand = withCategory(language.t("command.category.context")) + const viewCommand = withCategory(language.t("command.category.view")) + const terminalCommand = withCategory(language.t("command.category.terminal")) + const modelCommand = withCategory(language.t("command.category.model")) + const mcpCommand = withCategory(language.t("command.category.mcp")) + const agentCommand = withCategory(language.t("command.category.agent")) + const permissionsCommand = withCategory(language.t("command.category.permissions")) const sessionCommands = createMemo(() => [ sessionCommand({ id: "session.new", - title: input.language.t("command.session.new"), + title: language.t("command.session.new"), keybind: "mod+shift+s", slash: "new", - onSelect: () => input.navigate(`/${input.params.dir}/session`), + onSelect: () => navigate(`/${params.dir}/session`), }), ]) const fileCommands = createMemo(() => [ fileCommand({ id: "file.open", - title: input.language.t("command.file.open"), - description: input.language.t("palette.search.placeholder"), + title: language.t("command.file.open"), + description: language.t("palette.search.placeholder"), keybind: "mod+p", slash: "open", - onSelect: () => input.dialog.show(() => ), + onSelect: () => dialog.show(() => ), }), fileCommand({ id: "tab.close", - title: input.language.t("command.tab.close"), + title: language.t("command.tab.close"), keybind: "mod+w", - disabled: !input.tabs().active(), + disabled: !tabs().active(), onSelect: () => { - const active = input.tabs().active() + const active = tabs().active() if (!active) return - input.tabs().close(active) + tabs().close(active) }, }), ]) @@ -101,30 +133,30 @@ export const useSessionCommands = (input: SessionCommandContext) => { const contextCommands = createMemo(() => [ contextCommand({ id: "context.addSelection", - title: input.language.t("command.context.addSelection"), - description: input.language.t("command.context.addSelection.description"), + title: language.t("command.context.addSelection"), + description: language.t("command.context.addSelection.description"), keybind: "mod+shift+l", disabled: !canAddSelectionContext({ - active: input.tabs().active(), - pathFromTab: input.file.pathFromTab, - selectedLines: input.file.selectedLines, + active: tabs().active(), + pathFromTab: file.pathFromTab, + selectedLines: file.selectedLines, }), onSelect: () => { - const active = input.tabs().active() + const active = tabs().active() if (!active) return - const path = input.file.pathFromTab(active) + const path = file.pathFromTab(active) if (!path) return - const range = input.file.selectedLines(path) as SelectedLineRange | null | undefined + const range = file.selectedLines(path) as SelectedLineRange | null | undefined if (!range) { showToast({ - title: input.language.t("toast.context.noLineSelection.title"), - description: input.language.t("toast.context.noLineSelection.description"), + title: language.t("toast.context.noLineSelection.title"), + description: language.t("toast.context.noLineSelection.description"), }) return } - input.addSelectionToContext(path, selectionFromLines(range)) + addSelectionToContext(path, selectionFromLines(range)) }, }), ]) @@ -132,37 +164,37 @@ export const useSessionCommands = (input: SessionCommandContext) => { const viewCommands = createMemo(() => [ viewCommand({ id: "terminal.toggle", - title: input.language.t("command.terminal.toggle"), + title: language.t("command.terminal.toggle"), keybind: "ctrl+`", slash: "terminal", - onSelect: () => input.view().terminal.toggle(), + onSelect: () => view().terminal.toggle(), }), viewCommand({ id: "review.toggle", - title: input.language.t("command.review.toggle"), + title: language.t("command.review.toggle"), keybind: "mod+shift+r", - onSelect: () => input.view().reviewPanel.toggle(), + onSelect: () => view().reviewPanel.toggle(), }), viewCommand({ id: "fileTree.toggle", - title: input.language.t("command.fileTree.toggle"), + title: language.t("command.fileTree.toggle"), keybind: "mod+\\", - onSelect: () => input.layout.fileTree.toggle(), + onSelect: () => layout.fileTree.toggle(), }), viewCommand({ id: "input.focus", - title: input.language.t("command.input.focus"), + title: language.t("command.input.focus"), keybind: "ctrl+l", - onSelect: () => input.focusInput(), + onSelect: () => focusInput(), }), terminalCommand({ id: "terminal.new", - title: input.language.t("command.terminal.new"), - description: input.language.t("command.terminal.new.description"), + title: language.t("command.terminal.new"), + description: language.t("command.terminal.new.description"), keybind: "ctrl+alt+t", onSelect: () => { - if (input.terminal.all().length > 0) input.terminal.new() - input.view().terminal.open() + if (terminal.all().length > 0) terminal.new() + view().terminal.open() }, }), ]) @@ -170,61 +202,61 @@ export const useSessionCommands = (input: SessionCommandContext) => { const messageCommands = createMemo(() => [ sessionCommand({ id: "message.previous", - title: input.language.t("command.message.previous"), - description: input.language.t("command.message.previous.description"), + title: language.t("command.message.previous"), + description: language.t("command.message.previous.description"), keybind: "mod+arrowup", - disabled: !input.params.id, - onSelect: () => input.navigateMessageByOffset(-1), + disabled: !params.id, + onSelect: () => navigateMessageByOffset(-1), }), sessionCommand({ id: "message.next", - title: input.language.t("command.message.next"), - description: input.language.t("command.message.next.description"), + title: language.t("command.message.next"), + description: language.t("command.message.next.description"), keybind: "mod+arrowdown", - disabled: !input.params.id, - onSelect: () => input.navigateMessageByOffset(1), + disabled: !params.id, + onSelect: () => navigateMessageByOffset(1), }), ]) const agentCommands = createMemo(() => [ modelCommand({ id: "model.choose", - title: input.language.t("command.model.choose"), - description: input.language.t("command.model.choose.description"), + title: language.t("command.model.choose"), + description: language.t("command.model.choose.description"), keybind: "mod+'", slash: "model", - onSelect: () => input.dialog.show(() => ), + onSelect: () => dialog.show(() => ), }), mcpCommand({ id: "mcp.toggle", - title: input.language.t("command.mcp.toggle"), - description: input.language.t("command.mcp.toggle.description"), + title: language.t("command.mcp.toggle"), + description: language.t("command.mcp.toggle.description"), keybind: "mod+;", slash: "mcp", - onSelect: () => input.dialog.show(() => ), + onSelect: () => dialog.show(() => ), }), agentCommand({ id: "agent.cycle", - title: input.language.t("command.agent.cycle"), - description: input.language.t("command.agent.cycle.description"), + title: language.t("command.agent.cycle"), + description: language.t("command.agent.cycle.description"), keybind: "mod+.", slash: "agent", - onSelect: () => input.local.agent.move(1), + onSelect: () => local.agent.move(1), }), agentCommand({ id: "agent.cycle.reverse", - title: input.language.t("command.agent.cycle.reverse"), - description: input.language.t("command.agent.cycle.reverse.description"), + title: language.t("command.agent.cycle.reverse"), + description: language.t("command.agent.cycle.reverse.description"), keybind: "shift+mod+.", - onSelect: () => input.local.agent.move(-1), + onSelect: () => local.agent.move(-1), }), modelCommand({ id: "model.variant.cycle", - title: input.language.t("command.model.variant.cycle"), - description: input.language.t("command.model.variant.cycle.description"), + title: language.t("command.model.variant.cycle"), + description: language.t("command.model.variant.cycle.description"), keybind: "shift+mod+d", onSelect: () => { - input.local.model.variant.cycle() + local.model.variant.cycle() }, }), ]) @@ -233,22 +265,22 @@ export const useSessionCommands = (input: SessionCommandContext) => { permissionsCommand({ id: "permissions.autoaccept", title: - input.params.id && input.permission.isAutoAccepting(input.params.id, input.sdk.directory) - ? input.language.t("command.permissions.autoaccept.disable") - : input.language.t("command.permissions.autoaccept.enable"), + params.id && permission.isAutoAccepting(params.id, sdk.directory) + ? language.t("command.permissions.autoaccept.disable") + : language.t("command.permissions.autoaccept.enable"), keybind: "mod+shift+a", - disabled: !input.params.id || !input.permission.permissionsEnabled(), + disabled: !params.id || !permission.permissionsEnabled(), onSelect: () => { - const sessionID = input.params.id + const sessionID = params.id if (!sessionID) return - input.permission.toggleAutoAccept(sessionID, input.sdk.directory) + permission.toggleAutoAccept(sessionID, sdk.directory) showToast({ - title: input.permission.isAutoAccepting(sessionID, input.sdk.directory) - ? input.language.t("toast.permissions.autoaccept.on.title") - : input.language.t("toast.permissions.autoaccept.off.title"), - description: input.permission.isAutoAccepting(sessionID, input.sdk.directory) - ? input.language.t("toast.permissions.autoaccept.on.description") - : input.language.t("toast.permissions.autoaccept.off.description"), + title: permission.isAutoAccepting(sessionID, sdk.directory) + ? language.t("toast.permissions.autoaccept.on.title") + : language.t("toast.permissions.autoaccept.off.title"), + description: permission.isAutoAccepting(sessionID, sdk.directory) + ? language.t("toast.permissions.autoaccept.on.description") + : language.t("toast.permissions.autoaccept.off.description"), }) }, }), @@ -257,71 +289,71 @@ export const useSessionCommands = (input: SessionCommandContext) => { const sessionActionCommands = createMemo(() => [ sessionCommand({ id: "session.undo", - title: input.language.t("command.session.undo"), - description: input.language.t("command.session.undo.description"), + title: language.t("command.session.undo"), + description: language.t("command.session.undo.description"), slash: "undo", - disabled: !input.params.id || input.visibleUserMessages().length === 0, + disabled: !params.id || visibleUserMessages().length === 0, onSelect: async () => { - const sessionID = input.params.id + const sessionID = params.id if (!sessionID) return - if (input.status()?.type !== "idle") { - await input.sdk.client.session.abort({ sessionID }).catch(() => {}) + if (status()?.type !== "idle") { + await sdk.client.session.abort({ sessionID }).catch(() => {}) } - const revert = input.info()?.revert?.messageID - const message = findLast(input.userMessages(), (x) => !revert || x.id < revert) + const revert = info()?.revert?.messageID + const message = findLast(userMessages(), (x) => !revert || x.id < revert) if (!message) return - await input.sdk.client.session.revert({ sessionID, messageID: message.id }) - const parts = input.sync.data.part[message.id] + await sdk.client.session.revert({ sessionID, messageID: message.id }) + const parts = sync.data.part[message.id] if (parts) { - const restored = extractPromptFromParts(parts, { directory: input.sdk.directory }) - input.prompt.set(restored) + const restored = extractPromptFromParts(parts, { directory: sdk.directory }) + prompt.set(restored) } - const priorMessage = findLast(input.userMessages(), (x) => x.id < message.id) - input.setActiveMessage(priorMessage) + const priorMessage = findLast(userMessages(), (x) => x.id < message.id) + setActiveMessage(priorMessage) }, }), sessionCommand({ id: "session.redo", - title: input.language.t("command.session.redo"), - description: input.language.t("command.session.redo.description"), + title: language.t("command.session.redo"), + description: language.t("command.session.redo.description"), slash: "redo", - disabled: !input.params.id || !input.info()?.revert?.messageID, + disabled: !params.id || !info()?.revert?.messageID, onSelect: async () => { - const sessionID = input.params.id + const sessionID = params.id if (!sessionID) return - const revertMessageID = input.info()?.revert?.messageID + const revertMessageID = info()?.revert?.messageID if (!revertMessageID) return - const nextMessage = input.userMessages().find((x) => x.id > revertMessageID) + const nextMessage = userMessages().find((x) => x.id > revertMessageID) if (!nextMessage) { - await input.sdk.client.session.unrevert({ sessionID }) - input.prompt.reset() - const lastMsg = findLast(input.userMessages(), (x) => x.id >= revertMessageID) - input.setActiveMessage(lastMsg) + await sdk.client.session.unrevert({ sessionID }) + prompt.reset() + const lastMsg = findLast(userMessages(), (x) => x.id >= revertMessageID) + setActiveMessage(lastMsg) return } - await input.sdk.client.session.revert({ sessionID, messageID: nextMessage.id }) - const priorMsg = findLast(input.userMessages(), (x) => x.id < nextMessage.id) - input.setActiveMessage(priorMsg) + await sdk.client.session.revert({ sessionID, messageID: nextMessage.id }) + const priorMsg = findLast(userMessages(), (x) => x.id < nextMessage.id) + setActiveMessage(priorMsg) }, }), sessionCommand({ id: "session.compact", - title: input.language.t("command.session.compact"), - description: input.language.t("command.session.compact.description"), + title: language.t("command.session.compact"), + description: language.t("command.session.compact.description"), slash: "compact", - disabled: !input.params.id || input.visibleUserMessages().length === 0, + disabled: !params.id || visibleUserMessages().length === 0, onSelect: async () => { - const sessionID = input.params.id + const sessionID = params.id if (!sessionID) return - const model = input.local.model.current() + const model = local.model.current() if (!model) { showToast({ - title: input.language.t("toast.model.none.title"), - description: input.language.t("toast.model.none.description"), + title: language.t("toast.model.none.title"), + description: language.t("toast.model.none.description"), }) return } - await input.sdk.client.session.summarize({ + await sdk.client.session.summarize({ sessionID, modelID: model.id, providerID: model.provider.id, @@ -330,29 +362,27 @@ export const useSessionCommands = (input: SessionCommandContext) => { }), sessionCommand({ id: "session.fork", - title: input.language.t("command.session.fork"), - description: input.language.t("command.session.fork.description"), + title: language.t("command.session.fork"), + description: language.t("command.session.fork.description"), slash: "fork", - disabled: !input.params.id || input.visibleUserMessages().length === 0, - onSelect: () => input.dialog.show(() => ), + disabled: !params.id || visibleUserMessages().length === 0, + onSelect: () => dialog.show(() => ), }), ]) const shareCommands = createMemo(() => { - if (input.sync.data.config.share === "disabled") return [] + if (sync.data.config.share === "disabled") return [] return [ sessionCommand({ id: "session.share", - title: input.info()?.share?.url - ? input.language.t("session.share.copy.copyLink") - : input.language.t("command.session.share"), - description: input.info()?.share?.url - ? input.language.t("toast.session.share.success.description") - : input.language.t("command.session.share.description"), + title: info()?.share?.url ? language.t("session.share.copy.copyLink") : language.t("command.session.share"), + description: info()?.share?.url + ? language.t("toast.session.share.success.description") + : language.t("command.session.share.description"), slash: "share", - disabled: !input.params.id, + disabled: !params.id, onSelect: async () => { - if (!input.params.id) return + if (!params.id) return const write = (value: string) => { const body = typeof document === "undefined" ? undefined : document.body @@ -382,7 +412,7 @@ export const useSessionCommands = (input: SessionCommandContext) => { const ok = await write(url) if (!ok) { showToast({ - title: input.language.t("toast.session.share.copyFailed.title"), + title: language.t("toast.session.share.copyFailed.title"), variant: "error", }) return @@ -390,27 +420,27 @@ export const useSessionCommands = (input: SessionCommandContext) => { showToast({ title: existing - ? input.language.t("session.share.copy.copied") - : input.language.t("toast.session.share.success.title"), - description: input.language.t("toast.session.share.success.description"), + ? language.t("session.share.copy.copied") + : language.t("toast.session.share.success.title"), + description: language.t("toast.session.share.success.description"), variant: "success", }) } - const existing = input.info()?.share?.url + const existing = info()?.share?.url if (existing) { await copy(existing, true) return } - const url = await input.sdk.client.session - .share({ sessionID: input.params.id }) + const url = await sdk.client.session + .share({ sessionID: params.id }) .then((res) => res.data?.share?.url) .catch(() => undefined) if (!url) { showToast({ - title: input.language.t("toast.session.share.failed.title"), - description: input.language.t("toast.session.share.failed.description"), + title: language.t("toast.session.share.failed.title"), + description: language.t("toast.session.share.failed.description"), variant: "error", }) return @@ -421,25 +451,25 @@ export const useSessionCommands = (input: SessionCommandContext) => { }), sessionCommand({ id: "session.unshare", - title: input.language.t("command.session.unshare"), - description: input.language.t("command.session.unshare.description"), + title: language.t("command.session.unshare"), + description: language.t("command.session.unshare.description"), slash: "unshare", - disabled: !input.params.id || !input.info()?.share?.url, + disabled: !params.id || !info()?.share?.url, onSelect: async () => { - if (!input.params.id) return - await input.sdk.client.session - .unshare({ sessionID: input.params.id }) + if (!params.id) return + await sdk.client.session + .unshare({ sessionID: params.id }) .then(() => showToast({ - title: input.language.t("toast.session.unshare.success.title"), - description: input.language.t("toast.session.unshare.success.description"), + title: language.t("toast.session.unshare.success.title"), + description: language.t("toast.session.unshare.success.description"), variant: "success", }), ) .catch(() => showToast({ - title: input.language.t("toast.session.unshare.failed.title"), - description: input.language.t("toast.session.unshare.failed.description"), + title: language.t("toast.session.unshare.failed.title"), + description: language.t("toast.session.unshare.failed.description"), variant: "error", }), ) @@ -448,7 +478,7 @@ export const useSessionCommands = (input: SessionCommandContext) => { ] }) - input.command.register("session", () => + command.register("session", () => [ sessionCommands(), fileCommands(), diff --git a/packages/app/src/sst-env.d.ts b/packages/app/src/sst-env.d.ts index 47a8fbec7b..035e323c04 100644 --- a/packages/app/src/sst-env.d.ts +++ b/packages/app/src/sst-env.d.ts @@ -1,6 +1,8 @@ /* This file is auto-generated by SST. Do not edit. */ /* tslint:disable */ /* eslint-disable */ +/* biome-ignore-all lint: auto-generated */ + /// interface ImportMetaEnv { diff --git a/packages/app/src/utils/perf.ts b/packages/app/src/utils/perf.ts deleted file mode 100644 index 105d02a827..0000000000 --- a/packages/app/src/utils/perf.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { uuid } from "@/utils/uuid" - -type Nav = { - id: string - dir?: string - from?: string - to: string - trigger?: string - start: number - marks: Record - logged: boolean - timer?: ReturnType -} - -const dev = import.meta.env.DEV - -const key = (dir: string | undefined, to: string) => `${dir ?? ""}:${to}` - -const now = () => performance.now() - -const navs = new Map() -const pending = new Map() -const active = new Map() - -const required = [ - "session:params", - "session:data-ready", - "session:first-turn-mounted", - "storage:prompt-ready", - "storage:terminal-ready", - "storage:file-view-ready", -] - -function flush(id: string, reason: "complete" | "timeout") { - if (!dev) return - const nav = navs.get(id) - if (!nav) return - if (nav.logged) return - - nav.logged = true - if (nav.timer) clearTimeout(nav.timer) - - const baseName = nav.marks["navigate:start"] !== undefined ? "navigate:start" : "session:params" - const base = nav.marks[baseName] ?? nav.start - - const ms = Object.fromEntries( - Object.entries(nav.marks) - .slice() - .sort(([a], [b]) => a.localeCompare(b)) - .map(([name, t]) => [name, Math.round((t - base) * 100) / 100]), - ) - - console.log( - "perf.session-nav " + - JSON.stringify({ - type: "perf.session-nav.v0", - id: nav.id, - dir: nav.dir, - from: nav.from, - to: nav.to, - trigger: nav.trigger, - base: baseName, - reason, - ms, - }), - ) - - navs.delete(id) -} - -function maybeFlush(id: string) { - if (!dev) return - const nav = navs.get(id) - if (!nav) return - if (nav.logged) return - if (!required.every((name) => nav.marks[name] !== undefined)) return - flush(id, "complete") -} - -function ensure(id: string, data: Omit) { - const existing = navs.get(id) - if (existing) return existing - - const nav: Nav = { - ...data, - marks: {}, - logged: false, - } - nav.timer = setTimeout(() => flush(id, "timeout"), 5000) - navs.set(id, nav) - return nav -} - -export function navStart(input: { dir?: string; from?: string; to: string; trigger?: string }) { - if (!dev) return - - const id = uuid() - const start = now() - const nav = ensure(id, { ...input, id, start }) - nav.marks["navigate:start"] = start - - pending.set(key(input.dir, input.to), id) - return id -} - -export function navParams(input: { dir?: string; from?: string; to: string }) { - if (!dev) return - - const k = key(input.dir, input.to) - const pendingId = pending.get(k) - if (pendingId) pending.delete(k) - const id = pendingId ?? uuid() - - const start = now() - const nav = ensure(id, { ...input, id, start, trigger: pendingId ? "key" : "route" }) - nav.marks["session:params"] = start - - active.set(k, id) - maybeFlush(id) - return id -} - -export function navMark(input: { dir?: string; to: string; name: string }) { - if (!dev) return - - const id = active.get(key(input.dir, input.to)) - if (!id) return - - const nav = navs.get(id) - if (!nav) return - if (nav.marks[input.name] !== undefined) return - - nav.marks[input.name] = now() - maybeFlush(id) -} diff --git a/packages/app/sst-env.d.ts b/packages/app/sst-env.d.ts index b6a7e9066e..64441936d7 100644 --- a/packages/app/sst-env.d.ts +++ b/packages/app/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/console/app/sst-env.d.ts b/packages/console/app/sst-env.d.ts index 9b9de73273..301538ccb2 100644 --- a/packages/console/app/sst-env.d.ts +++ b/packages/console/app/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/console/core/sst-env.d.ts b/packages/console/core/sst-env.d.ts index 737af71d41..73f83d1676 100644 --- a/packages/console/core/sst-env.d.ts +++ b/packages/console/core/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ import "sst" declare module "sst" { diff --git a/packages/console/function/sst-env.d.ts b/packages/console/function/sst-env.d.ts index 737af71d41..73f83d1676 100644 --- a/packages/console/function/sst-env.d.ts +++ b/packages/console/function/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ import "sst" declare module "sst" { diff --git a/packages/console/mail/sst-env.d.ts b/packages/console/mail/sst-env.d.ts index 9b9de73273..301538ccb2 100644 --- a/packages/console/mail/sst-env.d.ts +++ b/packages/console/mail/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/console/resource/sst-env.d.ts b/packages/console/resource/sst-env.d.ts index 737af71d41..73f83d1676 100644 --- a/packages/console/resource/sst-env.d.ts +++ b/packages/console/resource/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ import "sst" declare module "sst" { diff --git a/packages/desktop/src-tauri/Cargo.lock b/packages/desktop/src-tauri/Cargo.lock index c8575a7593..f9516350e1 100644 --- a/packages/desktop/src-tauri/Cargo.lock +++ b/packages/desktop/src-tauri/Cargo.lock @@ -3136,6 +3136,7 @@ dependencies = [ "tracing-subscriber", "uuid", "webkit2gtk", + "windows 0.62.2", ] [[package]] diff --git a/packages/desktop/src-tauri/Cargo.toml b/packages/desktop/src-tauri/Cargo.toml index a5539645d6..e98b8965c1 100644 --- a/packages/desktop/src-tauri/Cargo.toml +++ b/packages/desktop/src-tauri/Cargo.toml @@ -54,6 +54,9 @@ chrono = "0.4" tokio-stream = { version = "0.1.18", features = ["sync"] } process-wrap = { version = "9.0.3", features = ["tokio1"] } +[target.'cfg(windows)'.dependencies] +windows = { version = "0.62", features = ["Win32_System_Threading"] } + [target.'cfg(target_os = "linux")'.dependencies] gtk = "0.18.2" webkit2gtk = "=2.0.2" diff --git a/packages/desktop/src-tauri/src/cli.rs b/packages/desktop/src-tauri/src/cli.rs index cad942acbb..130958bf7c 100644 --- a/packages/desktop/src-tauri/src/cli.rs +++ b/packages/desktop/src-tauri/src/cli.rs @@ -3,7 +3,7 @@ use process_wrap::tokio::CommandWrap; #[cfg(unix)] use process_wrap::tokio::ProcessGroup; #[cfg(windows)] -use process_wrap::tokio::{JobObject, KillOnDrop}; +use process_wrap::tokio::{CommandWrapper, JobObject, KillOnDrop}; #[cfg(unix)] use std::os::unix::process::ExitStatusExt; use std::sync::Arc; @@ -18,9 +18,24 @@ use tokio::{ }; use tokio_stream::wrappers::ReceiverStream; use tracing::Instrument; +#[cfg(windows)] +use windows::Win32::System::Threading::{CREATE_NO_WINDOW, CREATE_SUSPENDED}; use crate::server::get_wsl_config; +#[cfg(windows)] +#[derive(Clone, Copy, Debug)] +// Keep this as a custom wrapper instead of process_wrap::CreationFlags. +// JobObject pre_spawn rewrites creation flags, so this must run after it. +struct WinCreationFlags; + +#[cfg(windows)] +impl CommandWrapper for WinCreationFlags { + fn pre_spawn(&mut self, command: &mut Command, _core: &CommandWrap) -> std::io::Result<()> { + command.creation_flags((CREATE_NO_WINDOW | CREATE_SUSPENDED).0); + Ok(()) + } +} const CLI_INSTALL_DIR: &str = ".opencode/bin"; const CLI_BINARY_NAME: &str = "opencode"; @@ -203,7 +218,7 @@ fn get_user_shell() -> String { } fn is_wsl_enabled(_app: &tauri::AppHandle) -> bool { - get_wsl_config(_app.clone()).is_ok_and(|v| v.enabled) + get_wsl_config(_app.clone()).is_ok_and(|v| v.enabled) } fn shell_escape(input: &str) -> String { @@ -318,9 +333,6 @@ pub fn spawn_command( cmd.stderr(Stdio::piped()); cmd.stdin(Stdio::null()); - #[cfg(windows)] - cmd.creation_flags(0x0800_0000); - let mut wrap = CommandWrap::from(cmd); #[cfg(unix)] @@ -330,7 +342,7 @@ pub fn spawn_command( #[cfg(windows)] { - wrap.wrap(JobObject).wrap(KillOnDrop); + wrap.wrap(JobObject).wrap(WinCreationFlags).wrap(KillOnDrop); } let mut child = wrap.spawn()?; diff --git a/packages/desktop/sst-env.d.ts b/packages/desktop/sst-env.d.ts index b6a7e9066e..64441936d7 100644 --- a/packages/desktop/sst-env.d.ts +++ b/packages/desktop/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/enterprise/sst-env.d.ts b/packages/enterprise/sst-env.d.ts index 737af71d41..73f83d1676 100644 --- a/packages/enterprise/sst-env.d.ts +++ b/packages/enterprise/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ import "sst" declare module "sst" { diff --git a/packages/function/sst-env.d.ts b/packages/function/sst-env.d.ts index 737af71d41..73f83d1676 100644 --- a/packages/function/sst-env.d.ts +++ b/packages/function/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ import "sst" declare module "sst" { diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 6e1288b28d..dc9bfdaac8 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -75,7 +75,7 @@ "@ai-sdk/vercel": "1.0.33", "@ai-sdk/xai": "2.0.51", "@clack/prompts": "1.0.0-alpha.1", - "@gitlab/gitlab-ai-provider": "3.5.1", + "@gitlab/gitlab-ai-provider": "3.6.0", "@gitlab/opencode-gitlab-auth": "1.3.3", "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 9512406b3d..765c741c0d 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -30,6 +30,7 @@ import { import { Log } from "../util/log" import { pathToFileURL } from "bun" +import { Filesystem } from "../util/filesystem" import { ACPSessionManager } from "./session" import type { ACPConfig } from "./types" import { Provider } from "../provider/provider" @@ -228,8 +229,7 @@ export namespace ACP { const metadata = permission.metadata || {} const filepath = typeof metadata["filepath"] === "string" ? metadata["filepath"] : "" const diff = typeof metadata["diff"] === "string" ? metadata["diff"] : "" - const file = Bun.file(filepath) - const content = (await file.exists()) ? await file.text() : "" + const content = (await Filesystem.exists(filepath)) ? await Filesystem.readText(filepath) : "" const newContent = getNewContent(content, diff) if (newContent) { diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index ce948b92ac..776cc99b44 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -1,6 +1,7 @@ import path from "path" import { Global } from "../global" import z from "zod" +import { Filesystem } from "../util/filesystem" export const OAUTH_DUMMY_KEY = "opencode-oauth-dummy-key" @@ -42,8 +43,7 @@ export namespace Auth { } export async function all(): Promise> { - const file = Bun.file(filepath) - const data = await file.json().catch(() => ({}) as Record) + const data = await Filesystem.readJson>(filepath).catch(() => ({})) return Object.entries(data).reduce( (acc, [key, value]) => { const parsed = Info.safeParse(value) @@ -56,15 +56,13 @@ export namespace Auth { } export async function set(key: string, info: Info) { - const file = Bun.file(filepath) const data = await all() - await Bun.write(file, JSON.stringify({ ...data, [key]: info }, null, 2), { mode: 0o600 }) + await Filesystem.writeJson(filepath, { ...data, [key]: info }, 0o600) } export async function remove(key: string) { - const file = Bun.file(filepath) const data = await all() delete data[key] - await Bun.write(file, JSON.stringify(data, null, 2), { mode: 0o600 }) + await Filesystem.writeJson(filepath, data, 0o600) } } diff --git a/packages/opencode/src/bun/index.ts b/packages/opencode/src/bun/index.ts index bdb7cff78e..79aaae2bcc 100644 --- a/packages/opencode/src/bun/index.ts +++ b/packages/opencode/src/bun/index.ts @@ -66,14 +66,14 @@ export namespace BunProc { using _ = await Lock.write("bun-install") const mod = path.join(Global.Path.cache, "node_modules", pkg) - const pkgjson = Bun.file(path.join(Global.Path.cache, "package.json")) - const parsed = await pkgjson.json().catch(async () => { - const result = { dependencies: {} } - await Bun.write(pkgjson.name!, JSON.stringify(result, null, 2)) + const pkgjsonPath = path.join(Global.Path.cache, "package.json") + const parsed = await Filesystem.readJson<{ dependencies: Record }>(pkgjsonPath).catch(async () => { + const result = { dependencies: {} as Record } + await Filesystem.writeJson(pkgjsonPath, result) return result }) - const dependencies = parsed.dependencies ?? {} - if (!parsed.dependencies) parsed.dependencies = dependencies + if (!parsed.dependencies) parsed.dependencies = {} as Record + const dependencies = parsed.dependencies const modExists = await Filesystem.exists(mod) const cachedVersion = dependencies[pkg] @@ -123,15 +123,16 @@ export namespace BunProc { // This ensures subsequent starts use the cached version until explicitly updated let resolvedVersion = version if (version === "latest") { - const installedPkgJson = Bun.file(path.join(mod, "package.json")) - const installedPkg = await installedPkgJson.json().catch(() => null) + const installedPkg = await Filesystem.readJson<{ version?: string }>(path.join(mod, "package.json")).catch( + () => null, + ) if (installedPkg?.version) { resolvedVersion = installedPkg.version } } parsed.dependencies[pkg] = resolvedVersion - await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2)) + await Filesystem.writeJson(pkgjsonPath, parsed) return mod } } diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts index e5da9fdb38..22ea5d46a2 100644 --- a/packages/opencode/src/cli/cmd/agent.ts +++ b/packages/opencode/src/cli/cmd/agent.ts @@ -6,6 +6,7 @@ import { Agent } from "../../agent/agent" import { Provider } from "../../provider/provider" import path from "path" import fs from "fs/promises" +import { Filesystem } from "../../util/filesystem" import matter from "gray-matter" import { Instance } from "../../project/instance" import { EOL } from "os" @@ -202,8 +203,7 @@ const AgentCreateCommand = cmd({ await fs.mkdir(targetPath, { recursive: true }) - const file = Bun.file(filePath) - if (await file.exists()) { + if (await Filesystem.exists(filePath)) { if (isFullyNonInteractive) { console.error(`Error: Agent file already exists: ${filePath}`) process.exit(1) @@ -212,7 +212,7 @@ const AgentCreateCommand = cmd({ throw new UI.CancelledError() } - await Bun.write(filePath, content) + await Filesystem.write(filePath, content) if (isFullyNonInteractive) { console.log(filePath) diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 7f9a03d948..fd1a2f7e58 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -1,5 +1,6 @@ import path from "path" import { exec } from "child_process" +import { Filesystem } from "../../util/filesystem" import * as prompts from "@clack/prompts" import { map, pipe, sortBy, values } from "remeda" import { Octokit } from "@octokit/rest" @@ -173,6 +174,18 @@ export function extractResponseText(parts: MessageV2.Part[]): string | null { throw new Error("Failed to parse response: no parts returned") } +/** + * Formats a PROMPT_TOO_LARGE error message with details about files in the prompt. + * Content is base64 encoded, so we calculate original size by multiplying by 0.75. + */ +export function formatPromptTooLargeError(files: { filename: string; content: string }[]): string { + const fileDetails = + files.length > 0 + ? `\n\nFiles in prompt:\n${files.map((f) => ` - ${f.filename} (${((f.content.length * 0.75) / 1024).toFixed(0)} KB)`).join("\n")}` + : "" + return `PROMPT_TOO_LARGE: The prompt exceeds the model's context limit.${fileDetails}` +} + export const GithubCommand = cmd({ command: "github", describe: "manage GitHub agent", @@ -360,7 +373,7 @@ export const GithubInstallCommand = cmd({ ? "" : `\n env:${providers[provider].env.map((e) => `\n ${e}: \${{ secrets.${e} }}`).join("")}` - await Bun.write( + await Filesystem.write( path.join(app.root, WORKFLOW_FILE), `name: opencode @@ -802,6 +815,7 @@ export const GithubRunCommand = cmd({ replacement, }) } + return { userPrompt: prompt, promptFiles: imgData } } @@ -909,10 +923,15 @@ export const GithubRunCommand = cmd({ // result should always be assistant just satisfying type checker if (result.info.role === "assistant" && result.info.error) { - console.error("Agent error:", result.info.error) - throw new Error( - `${result.info.error.name}: ${"message" in result.info.error ? result.info.error.message : ""}`, - ) + const err = result.info.error + console.error("Agent error:", err) + + if (err.name === "ContextOverflowError") { + throw new Error(formatPromptTooLargeError(files)) + } + + const errorMsg = err.data?.message || "" + throw new Error(`${err.name}: ${errorMsg}`) } const text = extractResponseText(result.parts) @@ -938,10 +957,15 @@ export const GithubRunCommand = cmd({ }) if (summary.info.role === "assistant" && summary.info.error) { - console.error("Summary agent error:", summary.info.error) - throw new Error( - `${summary.info.error.name}: ${"message" in summary.info.error ? summary.info.error.message : ""}`, - ) + const err = summary.info.error + console.error("Summary agent error:", err) + + if (err.name === "ContextOverflowError") { + throw new Error(formatPromptTooLargeError(files)) + } + + const errorMsg = err.data?.message || "" + throw new Error(`${err.name}: ${errorMsg}`) } const summaryText = extractResponseText(summary.parts) diff --git a/packages/opencode/src/cli/cmd/import.ts b/packages/opencode/src/cli/cmd/import.ts index fd45a09b73..4d65060f18 100644 --- a/packages/opencode/src/cli/cmd/import.ts +++ b/packages/opencode/src/cli/cmd/import.ts @@ -8,6 +8,7 @@ import { SessionTable, MessageTable, PartTable } from "../../session/session.sql import { Instance } from "../../project/instance" import { ShareNext } from "../../share/share-next" import { EOL } from "os" +import { Filesystem } from "../../util/filesystem" /** Discriminated union returned by the ShareNext API (GET /api/share/:id/data) */ export type ShareData = @@ -116,8 +117,7 @@ export const ImportCommand = cmd({ exportData = transformed } else { - const file = Bun.file(args.file) - exportData = await file.json().catch(() => {}) + exportData = await Filesystem.readJson>(args.file).catch(() => undefined) if (!exportData) { process.stdout.write(`File not found: ${args.file}`) process.stdout.write(EOL) diff --git a/packages/opencode/src/cli/cmd/mcp.ts b/packages/opencode/src/cli/cmd/mcp.ts index 95719215e3..c45b9e55d0 100644 --- a/packages/opencode/src/cli/cmd/mcp.ts +++ b/packages/opencode/src/cli/cmd/mcp.ts @@ -13,6 +13,7 @@ import { Installation } from "../../installation" import path from "path" import { Global } from "../../global" import { modify, applyEdits } from "jsonc-parser" +import { Filesystem } from "../../util/filesystem" import { Bus } from "../../bus" function getAuthStatusIcon(status: MCP.AuthStatus): string { @@ -388,7 +389,7 @@ async function resolveConfigPath(baseDir: string, global = false) { } for (const candidate of candidates) { - if (await Bun.file(candidate).exists()) { + if (await Filesystem.exists(candidate)) { return candidate } } @@ -398,11 +399,9 @@ async function resolveConfigPath(baseDir: string, global = false) { } async function addMcpToConfig(name: string, mcpConfig: Config.Mcp, configPath: string) { - const file = Bun.file(configPath) - let text = "{}" - if (await file.exists()) { - text = await file.text() + if (await Filesystem.exists(configPath)) { + text = await Filesystem.readText(configPath) } // Use jsonc-parser to modify while preserving comments @@ -411,7 +410,7 @@ async function addMcpToConfig(name: string, mcpConfig: Config.Mcp, configPath: s }) const result = applyEdits(text, edits) - await Bun.write(configPath, result) + await Filesystem.write(configPath, result) return configPath } diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 55cf9a2a0a..bf63eabf81 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -6,6 +6,7 @@ import { cmd } from "./cmd" import { Flag } from "../../flag/flag" import { bootstrap } from "../bootstrap" import { EOL } from "os" +import { Filesystem } from "../../util/filesystem" import { createOpencodeClient, type Message, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2" import { Server } from "../../server/server" import { Provider } from "../../provider/provider" @@ -315,19 +316,12 @@ export const RunCommand = cmd({ for (const filePath of list) { const resolvedPath = path.resolve(process.cwd(), filePath) - const file = Bun.file(resolvedPath) - const stats = await file.stat().catch(() => {}) - if (!stats) { - UI.error(`File not found: ${filePath}`) - process.exit(1) - } - if (!(await file.exists())) { + if (!(await Filesystem.exists(resolvedPath))) { UI.error(`File not found: ${filePath}`) process.exit(1) } - const stat = await file.stat() - const mime = stat.isDirectory() ? "application/x-directory" : "text/plain" + const mime = (await Filesystem.isDir(resolvedPath)) ? "application/x-directory" : "text/plain" files.push({ type: "file", diff --git a/packages/opencode/src/cli/cmd/session.ts b/packages/opencode/src/cli/cmd/session.ts index 1803f84952..4aa702359d 100644 --- a/packages/opencode/src/cli/cmd/session.ts +++ b/packages/opencode/src/cli/cmd/session.ts @@ -5,6 +5,7 @@ import { bootstrap } from "../bootstrap" import { UI } from "../ui" import { Locale } from "../../util/locale" import { Flag } from "../../flag/flag" +import { Filesystem } from "../../util/filesystem" import { EOL } from "os" import path from "path" @@ -17,18 +18,18 @@ function pagerCmd(): string[] { // user could have less installed via other options const lessOnPath = Bun.which("less") if (lessOnPath) { - if (Bun.file(lessOnPath).size) return [lessOnPath, ...lessOptions] + if (Filesystem.stat(lessOnPath)?.size) return [lessOnPath, ...lessOptions] } if (Flag.OPENCODE_GIT_BASH_PATH) { const less = path.join(Flag.OPENCODE_GIT_BASH_PATH, "..", "..", "usr", "bin", "less.exe") - if (Bun.file(less).size) return [less, ...lessOptions] + if (Filesystem.stat(less)?.size) return [less, ...lessOptions] } const git = Bun.which("git") if (git) { const less = path.join(git, "..", "..", "usr", "bin", "less.exe") - if (Bun.file(less).size) return [less, ...lessOptions] + if (Filesystem.stat(less)?.size) return [less, ...lessOptions] } // Fall back to Windows built-in more (via cmd.exe) @@ -85,26 +86,17 @@ export const SessionListCommand = cmd({ }, handler: async (args) => { await bootstrap(process.cwd(), async () => { - const sessions = [] - for await (const session of Session.list()) { - if (!session.parentID) { - sessions.push(session) - } - } + const sessions = [...Session.list({ roots: true, limit: args.maxCount })] - sessions.sort((a, b) => b.time.updated - a.time.updated) - - const limitedSessions = args.maxCount ? sessions.slice(0, args.maxCount) : sessions - - if (limitedSessions.length === 0) { + if (sessions.length === 0) { return } let output: string if (args.format === "json") { - output = formatSessionJSON(limitedSessions) + output = formatSessionJSON(sessions) } else { - output = formatSessionTable(limitedSessions) + output = formatSessionTable(sessions) } const shouldPaginate = process.stdout.isTTY && !args.maxCount && args.format === "table" diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index cefef208de..4114daf6c6 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -694,7 +694,7 @@ export function Prompt(props: PromptProps) { async function pasteImage(file: { filename?: string; content: string; mime: string }) { const currentOffset = input.visualCursor.offset const extmarkStart = currentOffset - const count = store.prompt.parts.filter((x) => x.type === "file").length + const count = store.prompt.parts.filter((x) => x.type === "file" && x.mime.startsWith("image/")).length const virtualText = `[Image ${count + 1}]` const extmarkEnd = extmarkStart + virtualText.length const textToInsert = virtualText + " " diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 9eb2960327..50f63c3dfb 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -3,10 +3,12 @@ import { tui } from "./app" import { Rpc } from "@/util/rpc" import { type rpc } from "./worker" import path from "path" +import { fileURLToPath } from "url" import { UI } from "@/cli/ui" import { iife } from "@/util/iife" import { Log } from "@/util/log" import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network" +import { Filesystem } from "@/util/filesystem" import type { Event } from "@opencode-ai/sdk/v2" import type { EventSource } from "./context/sdk" import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32" @@ -99,7 +101,7 @@ export const TuiThreadCommand = cmd({ const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url) const workerPath = await iife(async () => { if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH - if (await Bun.file(distWorker).exists()) return distWorker + if (await Filesystem.exists(fileURLToPath(distWorker))) return distWorker return localWorker }) try { diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts index 4be6787346..7d1aad3a86 100644 --- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts +++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts @@ -4,6 +4,7 @@ import clipboardy from "clipboardy" import { lazy } from "../../../../util/lazy.js" import { tmpdir } from "os" import path from "path" +import { Filesystem } from "../../../../util/filesystem" /** * Writes text to clipboard via OSC 52 escape sequence. @@ -34,9 +35,8 @@ export namespace Clipboard { await $`osascript -e 'set imageData to the clipboard as "PNGf"' -e 'set fileRef to open for access POSIX file "${tmpfile}" with write permission' -e 'set eof fileRef to 0' -e 'write imageData to fileRef' -e 'close access fileRef'` .nothrow() .quiet() - const file = Bun.file(tmpfile) - const buffer = await file.arrayBuffer() - return { data: Buffer.from(buffer).toString("base64"), mime: "image/png" } + const buffer = await Filesystem.readBytes(tmpfile) + return { data: buffer.toString("base64"), mime: "image/png" } } catch { } finally { await $`rm -f "${tmpfile}"`.nothrow().quiet() diff --git a/packages/opencode/src/cli/cmd/tui/util/editor.ts b/packages/opencode/src/cli/cmd/tui/util/editor.ts index f98e24b069..cb7c691bbd 100644 --- a/packages/opencode/src/cli/cmd/tui/util/editor.ts +++ b/packages/opencode/src/cli/cmd/tui/util/editor.ts @@ -3,6 +3,7 @@ import { rm } from "node:fs/promises" import { tmpdir } from "node:os" import { join } from "node:path" import { CliRenderer } from "@opentui/core" +import { Filesystem } from "@/util/filesystem" export namespace Editor { export async function open(opts: { value: string; renderer: CliRenderer }): Promise { @@ -12,7 +13,7 @@ export namespace Editor { const filepath = join(tmpdir(), `${Date.now()}.md`) await using _ = defer(async () => rm(filepath, { force: true })) - await Bun.write(filepath, opts.value) + await Filesystem.write(filepath, opts.value) opts.renderer.suspend() opts.renderer.currentRenderBuffer.clear() const parts = editor.split(" ") @@ -23,7 +24,7 @@ export namespace Editor { stderr: "inherit", }) await proc.exited - const content = await Bun.file(filepath).text() + const content = await Filesystem.readText(filepath) opts.renderer.currentRenderBuffer.clear() opts.renderer.resume() opts.renderer.requestRender() diff --git a/packages/opencode/src/cli/cmd/uninstall.ts b/packages/opencode/src/cli/cmd/uninstall.ts index 704d3572bb..3d8e7e3f75 100644 --- a/packages/opencode/src/cli/cmd/uninstall.ts +++ b/packages/opencode/src/cli/cmd/uninstall.ts @@ -7,6 +7,7 @@ import { $ } from "bun" import fs from "fs/promises" import path from "path" import os from "os" +import { Filesystem } from "../../util/filesystem" interface UninstallArgs { keepConfig: boolean @@ -267,9 +268,7 @@ async function getShellConfigFile(): Promise { .catch(() => false) if (!exists) continue - const content = await Bun.file(file) - .text() - .catch(() => "") + const content = await Filesystem.readText(file).catch(() => "") if (content.includes("# opencode") || content.includes(".opencode/bin")) { return file } @@ -279,7 +278,7 @@ async function getShellConfigFile(): Promise { } async function cleanShellConfig(file: string) { - const content = await Bun.file(file).text() + const content = await Filesystem.readText(file) const lines = content.split("\n") const filtered: string[] = [] @@ -315,7 +314,7 @@ async function cleanShellConfig(file: string) { } const output = filtered.join("\n") + "\n" - await Bun.write(file, output) + await Filesystem.write(file, output) } async function getDirectorySize(dir: string): Promise { diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 261731b8b0..dfdcb0343e 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -255,19 +255,20 @@ export namespace Config { const pkg = path.join(dir, "package.json") const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION - const json = await Bun.file(pkg) - .json() - .catch(() => ({})) + const json = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => ({ + dependencies: {}, + })) json.dependencies = { ...json.dependencies, "@opencode-ai/plugin": targetVersion, } - await Bun.write(pkg, JSON.stringify(json, null, 2)) + await Filesystem.writeJson(pkg, json) await new Promise((resolve) => setTimeout(resolve, 3000)) const gitignore = path.join(dir, ".gitignore") - const hasGitIgnore = await Bun.file(gitignore).exists() - if (!hasGitIgnore) await Bun.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n")) + const hasGitIgnore = await Filesystem.exists(gitignore) + if (!hasGitIgnore) + await Filesystem.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n")) // Install any additional dependencies defined in the package.json // This allows local plugins and custom tools to use external packages @@ -303,11 +304,10 @@ export namespace Config { if (!existsSync(nodeModules)) return true const pkg = path.join(dir, "package.json") - const pkgFile = Bun.file(pkg) - const pkgExists = await pkgFile.exists() + const pkgExists = await Filesystem.exists(pkg) if (!pkgExists) return true - const parsed = await pkgFile.json().catch(() => null) + const parsed = await Filesystem.readJson<{ dependencies?: Record }>(pkg).catch(() => null) const dependencies = parsed?.dependencies ?? {} const depVersion = dependencies["@opencode-ai/plugin"] if (!depVersion) return true @@ -1220,7 +1220,7 @@ export namespace Config { if (provider && model) result.model = `${provider}/${model}` result["$schema"] = "https://opencode.ai/config.json" result = mergeDeep(result, rest) - await Bun.write(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2)) + await Filesystem.writeJson(path.join(Global.Path.config, "config.json"), result) await fs.unlink(legacy) }) .catch(() => {}) @@ -1231,12 +1231,10 @@ export namespace Config { async function loadFile(filepath: string): Promise { log.info("loading", { path: filepath }) - let text = await Bun.file(filepath) - .text() - .catch((err) => { - if (err.code === "ENOENT") return - throw new JsonError({ path: filepath }, { cause: err }) - }) + let text = await Filesystem.readText(filepath).catch((err: any) => { + if (err.code === "ENOENT") return + throw new JsonError({ path: filepath }, { cause: err }) + }) if (!text) return {} return load(text, filepath) } @@ -1263,21 +1261,19 @@ export namespace Config { } const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(configDir, filePath) const fileContent = ( - await Bun.file(resolvedPath) - .text() - .catch((error) => { - const errMsg = `bad file reference: "${match}"` - if (error.code === "ENOENT") { - throw new InvalidError( - { - path: configFilepath, - message: errMsg + ` ${resolvedPath} does not exist`, - }, - { cause: error }, - ) - } - throw new InvalidError({ path: configFilepath, message: errMsg }, { cause: error }) - }) + await Filesystem.readText(resolvedPath).catch((error: any) => { + const errMsg = `bad file reference: "${match}"` + if (error.code === "ENOENT") { + throw new InvalidError( + { + path: configFilepath, + message: errMsg + ` ${resolvedPath} does not exist`, + }, + { cause: error }, + ) + } + throw new InvalidError({ path: configFilepath, message: errMsg }, { cause: error }) + }) ).trim() // escape newlines/quotes, strip outer quotes text = text.replace(match, () => JSON.stringify(fileContent).slice(1, -1)) @@ -1314,7 +1310,7 @@ export namespace Config { parsed.data.$schema = "https://opencode.ai/config.json" // Write the $schema to the original text to preserve variables like {env:VAR} const updated = original.replace(/^\s*\{/, '{\n "$schema": "https://opencode.ai/config.json",') - await Bun.write(configFilepath, updated).catch(() => {}) + await Filesystem.write(configFilepath, updated).catch(() => {}) } const data = parsed.data if (data.plugin) { @@ -1370,7 +1366,7 @@ export namespace Config { export async function update(config: Info) { const filepath = path.join(Instance.directory, "config.json") const existing = await loadFile(filepath) - await Bun.write(filepath, JSON.stringify(mergeDeep(existing, config), null, 2)) + await Filesystem.writeJson(filepath, mergeDeep(existing, config)) await Instance.dispose() } @@ -1441,24 +1437,22 @@ export namespace Config { export async function updateGlobal(config: Info) { const filepath = globalConfigFile() - const before = await Bun.file(filepath) - .text() - .catch((err) => { - if (err.code === "ENOENT") return "{}" - throw new JsonError({ path: filepath }, { cause: err }) - }) + const before = await Filesystem.readText(filepath).catch((err: any) => { + if (err.code === "ENOENT") return "{}" + throw new JsonError({ path: filepath }, { cause: err }) + }) const next = await (async () => { if (!filepath.endsWith(".jsonc")) { const existing = parseConfig(before, filepath) const merged = mergeDeep(existing, config) - await Bun.write(filepath, JSON.stringify(merged, null, 2)) + await Filesystem.writeJson(filepath, merged) return merged } const updated = patchJsonc(before, config) const merged = parseConfig(updated, filepath) - await Bun.write(filepath, updated) + await Filesystem.write(filepath, updated) return merged })() diff --git a/packages/opencode/src/config/markdown.ts b/packages/opencode/src/config/markdown.ts index 4cd17746c5..5b4ccf0477 100644 --- a/packages/opencode/src/config/markdown.ts +++ b/packages/opencode/src/config/markdown.ts @@ -1,6 +1,7 @@ import { NamedError } from "@opencode-ai/util/error" import matter from "gray-matter" import { z } from "zod" +import { Filesystem } from "../util/filesystem" export namespace ConfigMarkdown { export const FILE_REGEX = /(? { - const type = file.type?.toLowerCase() + async function shouldEncode(mimeType: string): Promise { + const type = mimeType.toLowerCase() log.info("shouldEncode", { type }) if (!type) return false @@ -385,7 +384,7 @@ export namespace File { const untrackedFiles = untrackedOutput.trim().split("\n") for (const filepath of untrackedFiles) { try { - const content = await Bun.file(path.join(Instance.directory, filepath)).text() + const content = await Filesystem.readText(path.join(Instance.directory, filepath)) const lines = content.split("\n").length changedFiles.push({ path: filepath, @@ -437,10 +436,9 @@ export namespace File { // Fast path: check extension before any filesystem operations if (isImageByExtension(file)) { - const bunFile = Bun.file(full) - if (await bunFile.exists()) { - const buffer = await bunFile.arrayBuffer().catch(() => new ArrayBuffer(0)) - const content = Buffer.from(buffer).toString("base64") + if (await Filesystem.exists(full)) { + const buffer = await Filesystem.readBytes(full).catch(() => Buffer.from([])) + const content = buffer.toString("base64") const mimeType = getImageMimeType(file) return { type: "text", content, mimeType, encoding: "base64" } } @@ -451,29 +449,24 @@ export namespace File { return { type: "binary", content: "" } } - const bunFile = Bun.file(full) - - if (!(await bunFile.exists())) { + if (!(await Filesystem.exists(full))) { return { type: "text", content: "" } } - const encode = await shouldEncode(bunFile) - const mimeType = bunFile.type || "application/octet-stream" + const mimeType = Filesystem.mimeType(full) + const encode = await shouldEncode(mimeType) if (encode && !isImage(mimeType)) { return { type: "binary", content: "", mimeType } } if (encode) { - const buffer = await bunFile.arrayBuffer().catch(() => new ArrayBuffer(0)) - const content = Buffer.from(buffer).toString("base64") + const buffer = await Filesystem.readBytes(full).catch(() => Buffer.from([])) + const content = buffer.toString("base64") return { type: "text", content, mimeType, encoding: "base64" } } - const content = await bunFile - .text() - .catch(() => "") - .then((x) => x.trim()) + const content = (await Filesystem.readText(full).catch(() => "")).trim() if (project.vcs === "git") { let diff = await $`git diff ${file}`.cwd(Instance.directory).quiet().nothrow().text() @@ -497,13 +490,13 @@ export namespace File { let ignored = (_: string) => false if (project.vcs === "git") { const ig = ignore() - const gitignore = Bun.file(path.join(Instance.worktree, ".gitignore")) - if (await gitignore.exists()) { - ig.add(await gitignore.text()) + const gitignorePath = path.join(Instance.worktree, ".gitignore") + if (await Filesystem.exists(gitignorePath)) { + ig.add(await Filesystem.readText(gitignorePath)) } - const ignoreFile = Bun.file(path.join(Instance.worktree, ".ignore")) - if (await ignoreFile.exists()) { - ig.add(await ignoreFile.text()) + const ignorePath = path.join(Instance.worktree, ".ignore") + if (await Filesystem.exists(ignorePath)) { + ig.add(await Filesystem.readText(ignorePath)) } ignored = ig.ignores.bind(ig) } diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 58f9af7cdb..ca1eadae8e 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -6,6 +6,7 @@ import z from "zod" import { NamedError } from "@opencode-ai/util/error" import { lazy } from "../util/lazy" import { $ } from "bun" +import { Filesystem } from "../util/filesystem" import { ZipReader, BlobReader, BlobWriter } from "@zip.js/zip.js" import { Log } from "@/util/log" @@ -131,8 +132,7 @@ export namespace Ripgrep { } const filepath = path.join(Global.Path.bin, "rg" + (process.platform === "win32" ? ".exe" : "")) - const file = Bun.file(filepath) - if (!(await file.exists())) { + if (!(await Filesystem.exists(filepath))) { const platformKey = `${process.arch}-${process.platform}` as keyof typeof PLATFORM const config = PLATFORM[platformKey] if (!config) throw new UnsupportedPlatformError({ platform: platformKey }) @@ -144,9 +144,9 @@ export namespace Ripgrep { const response = await fetch(url) if (!response.ok) throw new DownloadFailedError({ url, status: response.status }) - const buffer = await response.arrayBuffer() + const arrayBuffer = await response.arrayBuffer() const archivePath = path.join(Global.Path.bin, filename) - await Bun.write(archivePath, buffer) + await Filesystem.write(archivePath, Buffer.from(arrayBuffer)) if (config.extension === "tar.gz") { const args = ["tar", "-xzf", archivePath, "--strip-components=1"] @@ -166,7 +166,7 @@ export namespace Ripgrep { }) } if (config.extension === "zip") { - const zipFileReader = new ZipReader(new BlobReader(new Blob([await Bun.file(archivePath).arrayBuffer()]))) + const zipFileReader = new ZipReader(new BlobReader(new Blob([arrayBuffer]))) const entries = await zipFileReader.getEntries() let rgEntry: any for (const entry of entries) { @@ -190,7 +190,7 @@ export namespace Ripgrep { stderr: "Failed to extract rg.exe from zip archive", }) } - await Bun.write(filepath, await rgBlob.arrayBuffer()) + await Filesystem.write(filepath, Buffer.from(await rgBlob.arrayBuffer())) await zipFileReader.close() } await fs.unlink(archivePath) diff --git a/packages/opencode/src/file/time.ts b/packages/opencode/src/file/time.ts index 35c780fbdd..c85781eb41 100644 --- a/packages/opencode/src/file/time.ts +++ b/packages/opencode/src/file/time.ts @@ -1,6 +1,7 @@ import { Instance } from "../project/instance" import { Log } from "../util/log" import { Flag } from "../flag/flag" +import { Filesystem } from "../util/filesystem" export namespace FileTime { const log = Log.create({ service: "file.time" }) @@ -59,10 +60,10 @@ export namespace FileTime { const time = get(sessionID, filepath) if (!time) throw new Error(`You must read file ${filepath} before overwriting it. Use the Read tool first`) - const stats = await Bun.file(filepath).stat() - if (stats.mtime.getTime() > time.getTime()) { + const mtime = Filesystem.stat(filepath)?.mtime + if (mtime && mtime.getTime() > time.getTime()) { throw new Error( - `File ${filepath} has been modified since it was last read.\nLast modification: ${stats.mtime.toISOString()}\nLast read: ${time.toISOString()}\n\nPlease read the file again before modifying it.`, + `File ${filepath} has been modified since it was last read.\nLast modification: ${mtime.toISOString()}\nLast read: ${time.toISOString()}\n\nPlease read the file again before modifying it.`, ) } } diff --git a/packages/opencode/src/format/formatter.ts b/packages/opencode/src/format/formatter.ts index 694c23d558..47b2d6a12d 100644 --- a/packages/opencode/src/format/formatter.ts +++ b/packages/opencode/src/format/formatter.ts @@ -67,7 +67,10 @@ export const prettier: Info = { async enabled() { const items = await Filesystem.findUp("package.json", Instance.directory, Instance.worktree) for (const item of items) { - const json = await Bun.file(item).json() + const json = await Filesystem.readJson<{ + dependencies?: Record + devDependencies?: Record + }>(item) if (json.dependencies?.prettier) return true if (json.devDependencies?.prettier) return true } @@ -86,7 +89,10 @@ export const oxfmt: Info = { if (!Flag.OPENCODE_EXPERIMENTAL_OXFMT) return false const items = await Filesystem.findUp("package.json", Instance.directory, Instance.worktree) for (const item of items) { - const json = await Bun.file(item).json() + const json = await Filesystem.readJson<{ + dependencies?: Record + devDependencies?: Record + }>(item) if (json.dependencies?.oxfmt) return true if (json.devDependencies?.oxfmt) return true } @@ -179,7 +185,7 @@ export const ruff: Info = { const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree) if (found.length > 0) { if (config === "pyproject.toml") { - const content = await Bun.file(found[0]).text() + const content = await Filesystem.readText(found[0]) if (content.includes("[tool.ruff]")) return true } else { return true @@ -190,7 +196,7 @@ export const ruff: Info = { for (const dep of deps) { const found = await Filesystem.findUp(dep, Instance.directory, Instance.worktree) if (found.length > 0) { - const content = await Bun.file(found[0]).text() + const content = await Filesystem.readText(found[0]) if (content.includes("ruff")) return true } } @@ -348,7 +354,10 @@ export const pint: Info = { async enabled() { const items = await Filesystem.findUp("composer.json", Instance.directory, Instance.worktree) for (const item of items) { - const json = await Bun.file(item).json() + const json = await Filesystem.readJson<{ + require?: Record + "require-dev"?: Record + }>(item) if (json.require?.["laravel/pint"]) return true if (json["require-dev"]?.["laravel/pint"]) return true } diff --git a/packages/opencode/src/global/index.ts b/packages/opencode/src/global/index.ts index 10b6125a6a..2913ac90fe 100644 --- a/packages/opencode/src/global/index.ts +++ b/packages/opencode/src/global/index.ts @@ -2,6 +2,7 @@ import fs from "fs/promises" import { xdgData, xdgCache, xdgConfig, xdgState } from "xdg-basedir" import path from "path" import os from "os" +import { Filesystem } from "../util/filesystem" const app = "opencode" @@ -35,9 +36,7 @@ await Promise.all([ const CACHE_VERSION = "21" -const version = await Bun.file(path.join(Global.Path.cache, "version")) - .text() - .catch(() => "0") +const version = await Filesystem.readText(path.join(Global.Path.cache, "version")).catch(() => "0") if (version !== CACHE_VERSION) { try { @@ -51,5 +50,5 @@ if (version !== CACHE_VERSION) { ), ) } catch (e) {} - await Bun.file(path.join(Global.Path.cache, "version")).write(CACHE_VERSION) + await Filesystem.write(path.join(Global.Path.cache, "version"), CACHE_VERSION) } diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 39e77782f5..6551565886 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -13,6 +13,7 @@ import { Installation } from "./installation" import { NamedError } from "@opencode-ai/util/error" import { FormatError } from "./cli/error" import { ServeCommand } from "./cli/cmd/serve" +import { Filesystem } from "./util/filesystem" import { DebugCommand } from "./cli/cmd/debug" import { StatsCommand } from "./cli/cmd/stats" import { McpCommand } from "./cli/cmd/mcp" @@ -81,7 +82,7 @@ const cli = yargs(hideBin(process.argv)) }) const marker = path.join(Global.Path.data, "opencode.db") - if (!(await Bun.file(marker).exists())) { + if (!(await Filesystem.exists(marker))) { const tty = process.stderr.isTTY process.stderr.write("Performing one time database migration, may take a few minutes..." + EOL) const width = 36 diff --git a/packages/opencode/src/lsp/client.ts b/packages/opencode/src/lsp/client.ts index 8704b65acb..084ccf831e 100644 --- a/packages/opencode/src/lsp/client.ts +++ b/packages/opencode/src/lsp/client.ts @@ -147,8 +147,7 @@ export namespace LSPClient { notify: { async open(input: { path: string }) { input.path = path.isAbsolute(input.path) ? input.path : path.resolve(Instance.directory, input.path) - const file = Bun.file(input.path) - const text = await file.text() + const text = await Filesystem.readText(input.path) const extension = path.extname(input.path) const languageId = LANGUAGE_EXTENSIONS[extension] ?? "plaintext" diff --git a/packages/opencode/src/lsp/language.ts b/packages/opencode/src/lsp/language.ts index 430b10caa2..58f4c8488b 100644 --- a/packages/opencode/src/lsp/language.ts +++ b/packages/opencode/src/lsp/language.ts @@ -44,6 +44,7 @@ export const LANGUAGE_EXTENSIONS: Record = { ".htm": "html", ".ini": "ini", ".java": "java", + ".jl": "julia", ".js": "javascript", ".kt": "kotlin", ".kts": "kotlin", diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index b0755b8b56..a4ebeb5a25 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -131,7 +131,7 @@ export namespace LSPServer { "bin", "vue-language-server.js", ) - if (!(await Bun.file(js).exists())) { + if (!(await Filesystem.exists(js))) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return await Bun.spawn([BunProc.which(), "install", "@vue/language-server"], { cwd: Global.Path.bin, @@ -173,14 +173,14 @@ export namespace LSPServer { if (!eslint) return log.info("spawning eslint server") const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js") - if (!(await Bun.file(serverPath).exists())) { + if (!(await Filesystem.exists(serverPath))) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return log.info("downloading and building VS Code ESLint server") const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip") if (!response.ok) return const zipPath = path.join(Global.Path.bin, "vscode-eslint.zip") - await Bun.file(zipPath).write(response) + if (response.body) await Filesystem.writeStream(zipPath, response.body) const ok = await Archive.extractZip(zipPath, Global.Path.bin) .then(() => true) @@ -242,7 +242,7 @@ export namespace LSPServer { const resolveBin = async (target: string) => { const localBin = path.join(root, target) - if (await Bun.file(localBin).exists()) return localBin + if (await Filesystem.exists(localBin)) return localBin const candidates = Filesystem.up({ targets: [target], @@ -326,7 +326,7 @@ export namespace LSPServer { async spawn(root) { const localBin = path.join(root, "node_modules", ".bin", "biome") let bin: string | undefined - if (await Bun.file(localBin).exists()) bin = localBin + if (await Filesystem.exists(localBin)) bin = localBin if (!bin) { const found = Bun.which("biome") if (found) bin = found @@ -467,7 +467,7 @@ export namespace LSPServer { const potentialPythonPath = isWindows ? path.join(venvPath, "Scripts", "python.exe") : path.join(venvPath, "bin", "python") - if (await Bun.file(potentialPythonPath).exists()) { + if (await Filesystem.exists(potentialPythonPath)) { initialization["pythonPath"] = potentialPythonPath break } @@ -479,7 +479,7 @@ export namespace LSPServer { const potentialTyPath = isWindows ? path.join(venvPath, "Scripts", "ty.exe") : path.join(venvPath, "bin", "ty") - if (await Bun.file(potentialTyPath).exists()) { + if (await Filesystem.exists(potentialTyPath)) { binary = potentialTyPath break } @@ -511,7 +511,7 @@ export namespace LSPServer { const args = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js") - if (!(await Bun.file(js).exists())) { + if (!(await Filesystem.exists(js))) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return await Bun.spawn([BunProc.which(), "install", "pyright"], { cwd: Global.Path.bin, @@ -536,7 +536,7 @@ export namespace LSPServer { const potentialPythonPath = isWindows ? path.join(venvPath, "Scripts", "python.exe") : path.join(venvPath, "bin", "python") - if (await Bun.file(potentialPythonPath).exists()) { + if (await Filesystem.exists(potentialPythonPath)) { initialization["pythonPath"] = potentialPythonPath break } @@ -571,7 +571,7 @@ export namespace LSPServer { process.platform === "win32" ? "language_server.bat" : "language_server.sh", ) - if (!(await Bun.file(binary).exists())) { + if (!(await Filesystem.exists(binary))) { const elixir = Bun.which("elixir") if (!elixir) { log.error("elixir is required to run elixir-ls") @@ -584,7 +584,7 @@ export namespace LSPServer { const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip") if (!response.ok) return const zipPath = path.join(Global.Path.bin, "elixir-ls.zip") - await Bun.file(zipPath).write(response) + if (response.body) await Filesystem.writeStream(zipPath, response.body) const ok = await Archive.extractZip(zipPath, Global.Path.bin) .then(() => true) @@ -692,7 +692,7 @@ export namespace LSPServer { } const tempPath = path.join(Global.Path.bin, assetName) - await Bun.file(tempPath).write(downloadResponse) + if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body) if (ext === "zip") { const ok = await Archive.extractZip(tempPath, Global.Path.bin) @@ -710,7 +710,7 @@ export namespace LSPServer { bin = path.join(Global.Path.bin, "zls" + (platform === "win32" ? ".exe" : "")) - if (!(await Bun.file(bin).exists())) { + if (!(await Filesystem.exists(bin))) { log.error("Failed to extract zls binary") return } @@ -857,7 +857,7 @@ export namespace LSPServer { // Stop at filesystem root const cargoTomlPath = path.join(currentDir, "Cargo.toml") try { - const cargoTomlContent = await Bun.file(cargoTomlPath).text() + const cargoTomlContent = await Filesystem.readText(cargoTomlPath) if (cargoTomlContent.includes("[workspace]")) { return currentDir } @@ -907,7 +907,7 @@ export namespace LSPServer { const ext = process.platform === "win32" ? ".exe" : "" const direct = path.join(Global.Path.bin, "clangd" + ext) - if (await Bun.file(direct).exists()) { + if (await Filesystem.exists(direct)) { return { process: spawn(direct, args, { cwd: root, @@ -920,7 +920,7 @@ export namespace LSPServer { if (!entry.isDirectory()) continue if (!entry.name.startsWith("clangd_")) continue const candidate = path.join(Global.Path.bin, entry.name, "bin", "clangd" + ext) - if (await Bun.file(candidate).exists()) { + if (await Filesystem.exists(candidate)) { return { process: spawn(candidate, args, { cwd: root, @@ -990,7 +990,7 @@ export namespace LSPServer { log.error("Failed to write clangd archive") return } - await Bun.write(archive, buf) + await Filesystem.write(archive, Buffer.from(buf)) const zip = name.endsWith(".zip") const tar = name.endsWith(".tar.xz") @@ -1014,7 +1014,7 @@ export namespace LSPServer { await fs.rm(archive, { force: true }) const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext) - if (!(await Bun.file(bin).exists())) { + if (!(await Filesystem.exists(bin))) { log.error("Failed to extract clangd binary") return } @@ -1045,7 +1045,7 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js") - if (!(await Bun.file(js).exists())) { + if (!(await Filesystem.exists(js))) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return await Bun.spawn([BunProc.which(), "install", "svelte-language-server"], { cwd: Global.Path.bin, @@ -1092,7 +1092,7 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "@astrojs", "language-server", "bin", "nodeServer.js") - if (!(await Bun.file(js).exists())) { + if (!(await Filesystem.exists(js))) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return await Bun.spawn([BunProc.which(), "install", "@astrojs/language-server"], { cwd: Global.Path.bin, @@ -1248,7 +1248,7 @@ export namespace LSPServer { const distPath = path.join(Global.Path.bin, "kotlin-ls") const launcherScript = process.platform === "win32" ? path.join(distPath, "kotlin-lsp.cmd") : path.join(distPath, "kotlin-lsp.sh") - const installed = await Bun.file(launcherScript).exists() + const installed = await Filesystem.exists(launcherScript) if (!installed) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return log.info("Downloading Kotlin Language Server from GitHub.") @@ -1307,7 +1307,7 @@ export namespace LSPServer { } log.info("Installed Kotlin Language Server", { path: launcherScript }) } - if (!(await Bun.file(launcherScript).exists())) { + if (!(await Filesystem.exists(launcherScript))) { log.error(`Failed to locate the Kotlin LS launcher script in the installed directory: ${distPath}.`) return } @@ -1336,7 +1336,7 @@ export namespace LSPServer { "src", "server.js", ) - const exists = await Bun.file(js).exists() + const exists = await Filesystem.exists(js) if (!exists) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return await Bun.spawn([BunProc.which(), "install", "yaml-language-server"], { @@ -1443,7 +1443,7 @@ export namespace LSPServer { } const tempPath = path.join(Global.Path.bin, assetName) - await Bun.file(tempPath).write(downloadResponse) + if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body) // Unlike zls which is a single self-contained binary, // lua-language-server needs supporting files (meta/, locale/, etc.) @@ -1482,7 +1482,7 @@ export namespace LSPServer { // Binary is located in bin/ subdirectory within the extracted archive bin = path.join(installDir, "bin", "lua-language-server" + (platform === "win32" ? ".exe" : "")) - if (!(await Bun.file(bin).exists())) { + if (!(await Filesystem.exists(bin))) { log.error("Failed to extract lua-language-server binary") return } @@ -1516,7 +1516,7 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "intelephense", "lib", "intelephense.js") - if (!(await Bun.file(js).exists())) { + if (!(await Filesystem.exists(js))) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return await Bun.spawn([BunProc.which(), "install", "intelephense"], { cwd: Global.Path.bin, @@ -1613,7 +1613,7 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "bash-language-server", "out", "cli.js") - if (!(await Bun.file(js).exists())) { + if (!(await Filesystem.exists(js))) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return await Bun.spawn([BunProc.which(), "install", "bash-language-server"], { cwd: Global.Path.bin, @@ -1654,22 +1654,17 @@ export namespace LSPServer { if (!bin) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return - log.info("downloading terraform-ls from GitHub releases") + log.info("downloading terraform-ls from HashiCorp releases") - const releaseResponse = await fetch("https://api.github.com/repos/hashicorp/terraform-ls/releases/latest") + const releaseResponse = await fetch("https://api.releases.hashicorp.com/v1/releases/terraform-ls/latest") if (!releaseResponse.ok) { log.error("Failed to fetch terraform-ls release info") return } const release = (await releaseResponse.json()) as { - tag_name?: string - assets?: { name?: string; browser_download_url?: string }[] - } - const version = release.tag_name?.replace("v", "") - if (!version) { - log.error("terraform-ls release did not include a version tag") - return + version?: string + builds?: { arch?: string; os?: string; url?: string }[] } const platform = process.platform @@ -1678,23 +1673,21 @@ export namespace LSPServer { const tfArch = arch === "arm64" ? "arm64" : "amd64" const tfPlatform = platform === "win32" ? "windows" : platform - const assetName = `terraform-ls_${version}_${tfPlatform}_${tfArch}.zip` - - const assets = release.assets ?? [] - const asset = assets.find((a) => a.name === assetName) - if (!asset?.browser_download_url) { - log.error(`Could not find asset ${assetName} in terraform-ls release`) + const builds = release.builds ?? [] + const build = builds.find((b) => b.arch === tfArch && b.os === tfPlatform) + if (!build?.url) { + log.error(`Could not find build for ${tfPlatform}/${tfArch} terraform-ls release version ${release.version}`) return } - const downloadResponse = await fetch(asset.browser_download_url) + const downloadResponse = await fetch(build.url) if (!downloadResponse.ok) { log.error("Failed to download terraform-ls") return } - const tempPath = path.join(Global.Path.bin, assetName) - await Bun.file(tempPath).write(downloadResponse) + const tempPath = path.join(Global.Path.bin, "terraform-ls.zip") + if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body) const ok = await Archive.extractZip(tempPath, Global.Path.bin) .then(() => true) @@ -1707,7 +1700,7 @@ export namespace LSPServer { bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : "")) - if (!(await Bun.file(bin).exists())) { + if (!(await Filesystem.exists(bin))) { log.error("Failed to extract terraform-ls binary") return } @@ -1784,7 +1777,7 @@ export namespace LSPServer { } const tempPath = path.join(Global.Path.bin, assetName) - await Bun.file(tempPath).write(downloadResponse) + if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body) if (ext === "zip") { const ok = await Archive.extractZip(tempPath, Global.Path.bin) @@ -1803,7 +1796,7 @@ export namespace LSPServer { bin = path.join(Global.Path.bin, "texlab" + (platform === "win32" ? ".exe" : "")) - if (!(await Bun.file(bin).exists())) { + if (!(await Filesystem.exists(bin))) { log.error("Failed to extract texlab binary") return } @@ -1832,7 +1825,7 @@ export namespace LSPServer { const args: string[] = [] if (!binary) { const js = path.join(Global.Path.bin, "node_modules", "dockerfile-language-server-nodejs", "lib", "server.js") - if (!(await Bun.file(js).exists())) { + if (!(await Filesystem.exists(js))) { if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return await Bun.spawn([BunProc.which(), "install", "dockerfile-language-server-nodejs"], { cwd: Global.Path.bin, @@ -1990,7 +1983,7 @@ export namespace LSPServer { } const tempPath = path.join(Global.Path.bin, assetName) - await Bun.file(tempPath).write(downloadResponse) + if (downloadResponse.body) await Filesystem.writeStream(tempPath, downloadResponse.body) if (ext === "zip") { const ok = await Archive.extractZip(tempPath, Global.Path.bin) @@ -2008,7 +2001,7 @@ export namespace LSPServer { bin = path.join(Global.Path.bin, "tinymist" + (platform === "win32" ? ".exe" : "")) - if (!(await Bun.file(bin).exists())) { + if (!(await Filesystem.exists(bin))) { log.error("Failed to extract tinymist binary") return } @@ -2043,4 +2036,22 @@ export namespace LSPServer { } }, } + + export const JuliaLS: Info = { + id: "julials", + extensions: [".jl"], + root: NearestRoot(["Project.toml", "Manifest.toml", "*.jl"]), + async spawn(root) { + const julia = Bun.which("julia") + if (!julia) { + log.info("julia not found, please install julia first (https://julialang.org/downloads/)") + return + } + return { + process: spawn(julia, ["--startup-file=no", "--history-file=no", "-e", "using LanguageServer; runserver()"], { + cwd: root, + }), + } + }, + } } diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index 0960176e20..bae3317846 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -5,6 +5,7 @@ import z from "zod" import { Installation } from "../installation" import { Flag } from "../flag/flag" import { lazy } from "@/util/lazy" +import { Filesystem } from "../util/filesystem" // Try to import bundled snapshot (generated at build time) // Falls back to undefined in dev mode when snapshot doesn't exist @@ -85,8 +86,7 @@ export namespace ModelsDev { } export const Data = lazy(async () => { - const file = Bun.file(Flag.OPENCODE_MODELS_PATH ?? filepath) - const result = await file.json().catch(() => {}) + const result = await Filesystem.readJson(Flag.OPENCODE_MODELS_PATH ?? filepath).catch(() => {}) if (result) return result // @ts-ignore const snapshot = await import("./models-snapshot") @@ -104,7 +104,6 @@ export namespace ModelsDev { } export async function refresh() { - const file = Bun.file(filepath) const result = await fetch(`${url()}/api.json`, { headers: { "User-Agent": Installation.USER_AGENT, @@ -116,7 +115,7 @@ export namespace ModelsDev { }) }) if (result && result.ok) { - await Bun.write(file, await result.text()) + await Filesystem.write(filepath, await result.text()) ModelsDev.Data.reset() } } diff --git a/packages/opencode/src/pty/index.ts b/packages/opencode/src/pty/index.ts index a9052a79eb..c98e99daf5 100644 --- a/packages/opencode/src/pty/index.ts +++ b/packages/opencode/src/pty/index.ts @@ -18,18 +18,24 @@ export namespace Pty { type Socket = { readyState: number + data: object send: (data: string | Uint8Array | ArrayBuffer) => void close: (code?: number, reason?: string) => void } - const sockets = new WeakMap() - let socketCounter = 0 + // Bun's ServerWebSocket has a per-connection `.data` object (set during + // `server.upgrade`) that changes when the underlying connection is recycled. + // We keep a reference to a stable part of it so output can't leak even when + // websocket objects are reused. + const token = (ws: Socket) => { + const data = ws.data + const events = (data as { events?: unknown }).events + if (events && typeof events === "object") return events - const tagSocket = (ws: Socket) => { - if (!ws || typeof ws !== "object") return - const next = (socketCounter = (socketCounter + 1) % Number.MAX_SAFE_INTEGER) - sockets.set(ws, next) - return next + const url = (data as { url?: unknown }).url + if (url && typeof url === "object") return url + + return data } // WebSocket control frame: 0x00 + UTF-8 JSON (currently { cursor }). @@ -96,7 +102,7 @@ export namespace Pty { buffer: string bufferCursor: number cursor: number - subscribers: Map + subscribers: Map } const state = Instance.state( @@ -176,26 +182,27 @@ export namespace Pty { subscribers: new Map(), } state().set(id, session) - ptyProcess.onData((data) => { - session.cursor += data.length + ptyProcess.onData((chunk) => { + session.cursor += chunk.length - for (const [ws, id] of session.subscribers) { + for (const [ws, data] of session.subscribers) { if (ws.readyState !== 1) { session.subscribers.delete(ws) continue } - if (typeof ws === "object" && sockets.get(ws) !== id) { + + if (token(ws) !== data) { session.subscribers.delete(ws) continue } try { - ws.send(data) + ws.send(chunk) } catch { session.subscribers.delete(ws) } } - session.buffer += data + session.buffer += chunk if (session.buffer.length <= BUFFER_LIMIT) return const excess = session.buffer.length - BUFFER_LIMIT session.buffer = session.buffer.slice(excess) @@ -305,8 +312,12 @@ export namespace Pty { return } - const socketId = tagSocket(ws) - if (typeof socketId === "number") session.subscribers.set(ws, socketId) + if (!ws.data || typeof ws.data !== "object") { + ws.close() + return + } + + session.subscribers.set(ws, token(ws)) return { onMessage: (message: string | ArrayBuffer) => { session.process.write(String(message)) diff --git a/packages/opencode/src/server/routes/pty.ts b/packages/opencode/src/server/routes/pty.ts index 21156190dc..d516859f7f 100644 --- a/packages/opencode/src/server/routes/pty.ts +++ b/packages/opencode/src/server/routes/pty.ts @@ -163,6 +163,7 @@ export const PtyRoutes = lazy(() => type Socket = { readyState: number + data: object send: (data: string | Uint8Array | ArrayBuffer) => void close: (code?: number, reason?: string) => void } @@ -170,6 +171,10 @@ export const PtyRoutes = lazy(() => const isSocket = (value: unknown): value is Socket => { if (!value || typeof value !== "object") return false if (!("readyState" in value)) return false + if (!("data" in value)) return false + if (!((value as { data?: unknown }).data && typeof (value as { data?: unknown }).data === "object")) { + return false + } if (!("send" in value) || typeof (value as { send?: unknown }).send !== "function") return false if (!("close" in value) || typeof (value as { close?: unknown }).close !== "function") return false return typeof (value as { readyState?: unknown }).readyState === "number" @@ -177,11 +182,16 @@ export const PtyRoutes = lazy(() => return { onOpen(_event, ws) { - const socket = isSocket(ws.raw) ? ws.raw : ws - handler = Pty.connect(id, socket, cursor) + const raw = ws.raw + if (!isSocket(raw)) { + ws.close() + return + } + handler = Pty.connect(id, raw, cursor) }, onMessage(event) { - handler?.onMessage(String(event.data)) + if (typeof event.data !== "string") return + handler?.onMessage(event.data) }, onClose() { handler?.onClose() diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 43ad9a09d3..d1f4072586 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1618,7 +1618,11 @@ NOTE: At any point in time through this workflow you should feel free to ask the const args = matchingInvocation?.args const cwd = Instance.directory - const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} }) + const shellEnv = await Plugin.trigger( + "shell.env", + { cwd, sessionID: input.sessionID, callID: part.callID }, + { env: {} }, + ) const proc = spawn(shell, args, { cwd, detached: process.platform !== "win32", diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 67559b78c0..ee2279bbfb 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -163,7 +163,11 @@ export const BashTool = Tool.define("bash", async () => { }) } - const shellEnv = await Plugin.trigger("shell.env", { cwd }, { env: {} }) + const shellEnv = await Plugin.trigger( + "shell.env", + { cwd, sessionID: ctx.sessionID, callID: ctx.callID }, + { env: {} }, + ) const proc = spawn(params.command, { shell, cwd, diff --git a/packages/opencode/src/util/filesystem.ts b/packages/opencode/src/util/filesystem.ts index 5c63af0303..b60b06e08e 100644 --- a/packages/opencode/src/util/filesystem.ts +++ b/packages/opencode/src/util/filesystem.ts @@ -1,8 +1,10 @@ -import { mkdir, readFile, writeFile } from "fs/promises" -import { existsSync, statSync } from "fs" +import { chmod, mkdir, readFile, writeFile } from "fs/promises" +import { createWriteStream, existsSync, statSync } from "fs" import { lookup } from "mime-types" import { realpathSync } from "fs" import { dirname, join, relative } from "path" +import { Readable } from "stream" +import { pipeline } from "stream/promises" export namespace Filesystem { // Fast sync version for metadata checks @@ -18,19 +20,20 @@ export namespace Filesystem { } } + export function stat(p: string): ReturnType | undefined { + return statSync(p, { throwIfNoEntry: false }) ?? undefined + } + export async function size(p: string): Promise { - try { - return statSync(p).size - } catch { - return 0 - } + const s = stat(p)?.size ?? 0 + return typeof s === "bigint" ? Number(s) : s } export async function readText(p: string): Promise { return readFile(p, "utf-8") } - export async function readJson(p: string): Promise { + export async function readJson(p: string): Promise { return JSON.parse(await readFile(p, "utf-8")) } @@ -67,6 +70,25 @@ export namespace Filesystem { return write(p, JSON.stringify(data, null, 2), mode) } + export async function writeStream( + p: string, + stream: ReadableStream | Readable, + mode?: number, + ): Promise { + const dir = dirname(p) + if (!existsSync(dir)) { + await mkdir(dir, { recursive: true }) + } + + const nodeStream = stream instanceof ReadableStream ? Readable.fromWeb(stream as any) : stream + const writeStream = createWriteStream(p) + await pipeline(nodeStream, writeStream) + + if (mode) { + await chmod(p, mode) + } + } + export function mimeType(p: string): string { return lookup(p) || "application/octet-stream" } diff --git a/packages/opencode/src/util/log.ts b/packages/opencode/src/util/log.ts index 6941310bbb..c62d592997 100644 --- a/packages/opencode/src/util/log.ts +++ b/packages/opencode/src/util/log.ts @@ -1,5 +1,6 @@ import path from "path" import fs from "fs/promises" +import { createWriteStream } from "fs" import { Global } from "../global" import z from "zod" @@ -63,13 +64,15 @@ export namespace Log { Global.Path.log, options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log", ) - const logfile = Bun.file(logpath) await fs.truncate(logpath).catch(() => {}) - const writer = logfile.writer() + const stream = createWriteStream(logpath, { flags: "a" }) write = async (msg: any) => { - const num = writer.write(msg) - writer.flush() - return num + return new Promise((resolve, reject) => { + stream.write(msg, (err) => { + if (err) reject(err) + else resolve(msg.length) + }) + }) } } diff --git a/packages/opencode/sst-env.d.ts b/packages/opencode/sst-env.d.ts index b6a7e9066e..64441936d7 100644 --- a/packages/opencode/sst-env.d.ts +++ b/packages/opencode/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/opencode/test/cli/github-action.test.ts b/packages/opencode/test/cli/github-action.test.ts index 773d9eb6a4..cd64bb59ec 100644 --- a/packages/opencode/test/cli/github-action.test.ts +++ b/packages/opencode/test/cli/github-action.test.ts @@ -1,5 +1,5 @@ import { test, expect, describe } from "bun:test" -import { extractResponseText } from "../../src/cli/cmd/github" +import { extractResponseText, formatPromptTooLargeError } from "../../src/cli/cmd/github" import type { MessageV2 } from "../../src/session/message-v2" // Helper to create minimal valid parts @@ -159,3 +159,39 @@ describe("extractResponseText", () => { expect(extractResponseText(parts)).toBe("Here's what I found") }) }) + +describe("formatPromptTooLargeError", () => { + test("formats error without files", () => { + const result = formatPromptTooLargeError([]) + expect(result).toBe("PROMPT_TOO_LARGE: The prompt exceeds the model's context limit.") + }) + + test("formats error with files (base64 content)", () => { + // Base64 is ~33% larger than original, so we multiply by 0.75 to get original size + // 400 KB base64 = 300 KB original, 200 KB base64 = 150 KB original + const files = [ + { filename: "screenshot.png", content: "a".repeat(400 * 1024) }, + { filename: "diagram.png", content: "b".repeat(200 * 1024) }, + ] + const result = formatPromptTooLargeError(files) + + expect(result).toStartWith("PROMPT_TOO_LARGE: The prompt exceeds the model's context limit.") + expect(result).toInclude("Files in prompt:") + expect(result).toInclude("screenshot.png (300 KB)") + expect(result).toInclude("diagram.png (150 KB)") + }) + + test("lists all files when multiple present", () => { + // Base64 sizes: 4KB -> 3KB, 8KB -> 6KB, 12KB -> 9KB + const files = [ + { filename: "img1.png", content: "x".repeat(4 * 1024) }, + { filename: "img2.jpg", content: "y".repeat(8 * 1024) }, + { filename: "img3.gif", content: "z".repeat(12 * 1024) }, + ] + const result = formatPromptTooLargeError(files) + + expect(result).toInclude("img1.png (3 KB)") + expect(result).toInclude("img2.jpg (6 KB)") + expect(result).toInclude("img3.gif (9 KB)") + }) +}) diff --git a/packages/opencode/test/pty/pty-output-isolation.test.ts b/packages/opencode/test/pty/pty-output-isolation.test.ts index b80d373458..337280d18d 100644 --- a/packages/opencode/test/pty/pty-output-isolation.test.ts +++ b/packages/opencode/test/pty/pty-output-isolation.test.ts @@ -18,6 +18,7 @@ describe("pty", () => { const ws = { readyState: 1, + data: { events: { connection: "a" } }, send: (data: unknown) => { outA.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8")) }, @@ -30,6 +31,7 @@ describe("pty", () => { Pty.connect(a.id, ws as any) // Now "reuse" the same ws object for another connection. + ws.data = { events: { connection: "b" } } ws.send = (data: unknown) => { outB.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8")) } @@ -51,4 +53,48 @@ describe("pty", () => { }, }) }) + + test("does not leak output when Bun recycles websocket objects before re-connect", async () => { + await using dir = await tmpdir({ git: true }) + + await Instance.provide({ + directory: dir.path, + fn: async () => { + const a = await Pty.create({ command: "cat", title: "a" }) + try { + const outA: string[] = [] + const outB: string[] = [] + + const ws = { + readyState: 1, + data: { events: { connection: "a" } }, + send: (data: unknown) => { + outA.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8")) + }, + close: () => { + // no-op (simulate abrupt drop) + }, + } + + // Connect "a" first. + Pty.connect(a.id, ws as any) + outA.length = 0 + + // Simulate Bun reusing the same websocket object for another connection + // before the new onOpen handler has a chance to tag it. + ws.data = { events: { connection: "b" } } + ws.send = (data: unknown) => { + outB.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8")) + } + + Pty.write(a.id, "AAA\n") + await Bun.sleep(100) + + expect(outB.join("")).not.toContain("AAA") + } finally { + await Pty.remove(a.id) + } + }, + }) + }) }) diff --git a/packages/opencode/test/util/filesystem.test.ts b/packages/opencode/test/util/filesystem.test.ts index 3c3da0fc79..0f54479373 100644 --- a/packages/opencode/test/util/filesystem.test.ts +++ b/packages/opencode/test/util/filesystem.test.ts @@ -285,4 +285,125 @@ describe("filesystem", () => { expect(Filesystem.mimeType("Makefile")).toBe("application/octet-stream") }) }) + + describe("writeStream()", () => { + test("writes from Web ReadableStream", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "streamed.txt") + const content = "Hello from stream!" + const encoder = new TextEncoder() + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode(content)) + controller.close() + }, + }) + + await Filesystem.writeStream(filepath, stream) + + expect(await fs.readFile(filepath, "utf-8")).toBe(content) + }) + + test("writes from Node.js Readable stream", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "node-streamed.txt") + const content = "Hello from Node stream!" + const { Readable } = await import("stream") + const stream = Readable.from([content]) + + await Filesystem.writeStream(filepath, stream) + + expect(await fs.readFile(filepath, "utf-8")).toBe(content) + }) + + test("writes binary data from Web ReadableStream", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "binary.dat") + const binaryData = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0xff]) + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(binaryData) + controller.close() + }, + }) + + await Filesystem.writeStream(filepath, stream) + + const read = await fs.readFile(filepath) + expect(Buffer.from(read)).toEqual(Buffer.from(binaryData)) + }) + + test("writes large content in chunks", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "large.txt") + const chunks = ["chunk1", "chunk2", "chunk3", "chunk4", "chunk5"] + const stream = new ReadableStream({ + start(controller) { + for (const chunk of chunks) { + controller.enqueue(new TextEncoder().encode(chunk)) + } + controller.close() + }, + }) + + await Filesystem.writeStream(filepath, stream) + + expect(await fs.readFile(filepath, "utf-8")).toBe(chunks.join("")) + }) + + test("creates parent directories", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "nested", "deep", "streamed.txt") + const content = "nested stream content" + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(content)) + controller.close() + }, + }) + + await Filesystem.writeStream(filepath, stream) + + expect(await fs.readFile(filepath, "utf-8")).toBe(content) + }) + + test("writes with permissions", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "protected-stream.txt") + const content = "secret stream content" + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(content)) + controller.close() + }, + }) + + await Filesystem.writeStream(filepath, stream, 0o600) + + const stats = await fs.stat(filepath) + if (process.platform !== "win32") { + expect(stats.mode & 0o777).toBe(0o600) + } + }) + + test("writes executable with permissions", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "script.sh") + const content = "#!/bin/bash\necho hello" + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(content)) + controller.close() + }, + }) + + await Filesystem.writeStream(filepath, stream, 0o755) + + const stats = await fs.stat(filepath) + if (process.platform !== "win32") { + expect(stats.mode & 0o777).toBe(0o755) + } + expect(await fs.readFile(filepath, "utf-8")).toBe(content) + }) + }) }) diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index bd4ba53049..76370d1d5a 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -185,7 +185,10 @@ export interface Hooks { input: { tool: string; sessionID: string; callID: string }, output: { args: any }, ) => Promise - "shell.env"?: (input: { cwd: string }, output: { env: Record }) => Promise + "shell.env"?: ( + input: { cwd: string; sessionID?: string; callID?: string }, + output: { env: Record }, + ) => Promise "tool.execute.after"?: ( input: { tool: string; sessionID: string; callID: string; args: any }, output: { diff --git a/packages/plugin/sst-env.d.ts b/packages/plugin/sst-env.d.ts index b6a7e9066e..64441936d7 100644 --- a/packages/plugin/sst-env.d.ts +++ b/packages/plugin/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/script/sst-env.d.ts b/packages/script/sst-env.d.ts index b6a7e9066e..64441936d7 100644 --- a/packages/script/sst-env.d.ts +++ b/packages/script/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/sdk/js/sst-env.d.ts b/packages/sdk/js/sst-env.d.ts index 9b9de73273..301538ccb2 100644 --- a/packages/sdk/js/sst-env.d.ts +++ b/packages/sdk/js/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/slack/sst-env.d.ts b/packages/slack/sst-env.d.ts index b6a7e9066e..64441936d7 100644 --- a/packages/slack/sst-env.d.ts +++ b/packages/slack/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/ui/src/components/button.css b/packages/ui/src/components/button.css index 794002888a..4a6eeaf694 100644 --- a/packages/ui/src/components/button.css +++ b/packages/ui/src/components/button.css @@ -48,13 +48,13 @@ } &:hover:not(:disabled) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); } &:focus-visible:not(:disabled) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); } &:active:not(:disabled) { - background-color: var(--surface-raised-base-active); + background-color: var(--surface-base-active); } &:disabled { color: var(--text-weak); @@ -65,10 +65,10 @@ } } &[data-selected="true"]:not(:disabled) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); } &[data-active="true"] { - background-color: var(--surface-raised-base-active); + background-color: var(--surface-base-active); } } @@ -76,7 +76,7 @@ border: transparent; background-color: var(--button-secondary-base); color: var(--text-strong); - box-shadow: var(--shadow-xs-border); + box-shadow: var(--shadow-xs-border-base); &:hover:not(:disabled) { background-color: var(--button-secondary-hover); @@ -93,8 +93,6 @@ } &:active:not(:disabled) { background-color: var(--button-secondary-base); - scale: 0.99; - transition: all 150ms ease-out; } &:disabled { border-color: var(--border-disabled); @@ -104,7 +102,7 @@ } [data-slot="icon-svg"] { - color: var(--icon-strong-base); + color: var(--icon-base); } } @@ -171,10 +169,6 @@ } } -[data-component="button"].titlebar-icon[data-variant="ghost"]:hover:not(:disabled) { - background-color: var(--surface-raised-base-active); -} - [data-component="button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"] { background-color: var(--surface-base-active); } diff --git a/packages/ui/src/components/icon-button.css b/packages/ui/src/components/icon-button.css index ad8200ae68..7a47270fe9 100644 --- a/packages/ui/src/components/icon-button.css +++ b/packages/ui/src/components/icon-button.css @@ -45,7 +45,7 @@ border: transparent; background-color: var(--button-secondary-base); color: var(--text-strong); - box-shadow: var(--shadow-xs-border); + box-shadow: var(--shadow-xs-border-base); &:hover:not(:disabled) { background-color: var(--button-secondary-hover); @@ -84,23 +84,23 @@ } &:hover:not(:disabled) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); /* [data-slot="icon-svg"] { */ /* color: var(--icon-hover); */ /* } */ } &:focus-visible:not(:disabled) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); } &:active:not(:disabled) { - background-color: var(--surface-raised-base-active); + background-color: var(--surface-base-active); /* [data-slot="icon-svg"] { */ /* color: var(--icon-active); */ /* } */ } &:selected:not(:disabled) { - background-color: var(--surface-raised-base-active); + background-color: var(--surface-base-active); /* [data-slot="icon-svg"] { */ /* color: var(--icon-selected); */ /* } */ @@ -168,12 +168,8 @@ aspect-ratio: auto; } -[data-component="icon-button"].titlebar-icon[data-variant="ghost"]:hover:not(:disabled) { - background-color: var(--surface-raised-base-active); -} - [data-component="icon-button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"] { - background-color: var(--surface-base-active); + background-color: var(--surface-raised-base-active); } [data-component="icon-button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"] [data-slot="icon-svg"] { @@ -181,5 +177,5 @@ } [data-component="icon-button"].titlebar-icon[data-variant="ghost"][aria-expanded="true"]:hover:not(:disabled) { - background-color: var(--surface-base-active); + background-color: var(--surface-raised-base-active); } diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css index 9e571c9f16..d123847cbc 100644 --- a/packages/ui/src/components/message-part.css +++ b/packages/ui/src/components/message-part.css @@ -227,12 +227,13 @@ [data-component="reasoning-part"] { width: 100%; color: var(--text-base); - opacity: 0.8; + font-size: var(--font-size-small); line-height: var(--line-height-large); [data-component="markdown"] { margin-top: 24px; font-style: normal; + font-size: inherit; p:has(strong) { margin-top: 24px; diff --git a/packages/ui/src/components/radio-group.css b/packages/ui/src/components/radio-group.css index 9f7bd5a9ef..4faaa33f43 100644 --- a/packages/ui/src/components/radio-group.css +++ b/packages/ui/src/components/radio-group.css @@ -38,9 +38,9 @@ } [data-slot="radio-group-indicator"] { - background: var(--surface-raised-stronger-non-alpha); + background: var(--button-secondary-base); border-radius: var(--radius-sm); - box-shadow: var(--shadow-xs-border); + box-shadow: var(--shadow-xs-border-base); content: ""; opacity: var(--indicator-opacity, 1); pointer-events: none; @@ -80,7 +80,7 @@ [data-slot="radio-group-item-label"] { color: var(--text-weak); - cursor: pointer; + cursor: default; display: flex; align-items: center; justify-content: center; diff --git a/packages/ui/src/components/session-review.css b/packages/ui/src/components/session-review.css index 46473b75e5..c618ed58c8 100644 --- a/packages/ui/src/components/session-review.css +++ b/packages/ui/src/components/session-review.css @@ -38,10 +38,24 @@ [data-slot="session-review-actions"] { display: flex; align-items: center; - column-gap: 16px; + column-gap: 12px; padding-right: 1px; } + [data-slot="session-review-actions"] [data-component="radio-group"] { + [data-slot="radio-group-wrapper"], + [data-slot="radio-group-indicator"], + [data-slot="radio-group-item-control"] { + border-radius: 6px; + } + + [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled]) + + [data-slot="radio-group-item-label"]:hover + [data-slot="radio-group-item-control"] { + border-radius: 4px; + } + } + [data-component="sticky-accordion-header"] { top: 40px; } diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx index 6ab922262b..aa9558fe47 100644 --- a/packages/ui/src/components/session-review.tsx +++ b/packages/ui/src/components/session-review.tsx @@ -286,18 +286,14 @@ export const SessionReview = (props: SessionReviewProps) => { [props.class ?? ""]: !!props.class, }} > -
+
{props.title ?? i18n.t("ui.sessionReview.title")}
style} label={(style) => i18n.t(style === "unified" ? "ui.sessionReview.diffStyle.unified" : "ui.sessionReview.diffStyle.split") @@ -306,7 +302,12 @@ export const SessionReview = (props: SessionReviewProps) => { /> -
-
+
diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css index 7668c29764..03df9cd84e 100644 --- a/packages/ui/src/components/tabs.css +++ b/packages/ui/src/components/tabs.css @@ -253,16 +253,20 @@ } &:hover:not(:disabled) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); color: var(--text-strong); } + &:active:not(:disabled) { + background-color: var(--surface-base-active); + } + &:has([data-selected]) { - background-color: var(--surface-raised-base-active); + background-color: var(--surface-base-active); color: var(--text-strong); &:hover:not(:disabled) { - background-color: var(--surface-raised-base-active); + background-color: var(--surface-base-active); } } } @@ -274,10 +278,11 @@ padding-inline: 12px; gap: 8px; align-items: center; + background-color: var(--background-stronger); } [data-slot="tabs-trigger-wrapper"] { - height: 26px; + height: 24px; border-radius: 6px; color: var(--text-weak); @@ -325,11 +330,11 @@ } &:hover:not(:disabled) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); } &:has([data-selected]) { - background-color: var(--surface-raised-base-active); + background-color: var(--surface-base-active); color: var(--text-strong); } } @@ -363,11 +368,11 @@ } &:hover:not(:disabled) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); } &:has([data-selected]) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); color: var(--text-strong); } } @@ -426,16 +431,16 @@ } &:hover:not(:disabled) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); } &:has([data-slot="tabs-trigger"]:focus-visible) { - background-color: var(--surface-raised-base-hover); + background-color: var(--surface-base-hover); box-shadow: var(--shadow-xs-border-focus); } &:has([data-selected]) { - background-color: var(--surface-raised-base-active); + background-color: var(--surface-base-active); color: var(--text-strong); [data-component="icon"] { @@ -443,13 +448,13 @@ } &:hover:not(:disabled) { - background-color: var(--surface-raised-base-active); + background-color: var(--surface-base-active); } } } [data-slot="tabs-content"] { - background-color: var(--surface-raised-stronger-non-alpha); + background-color: var(--surface-stronger-non-alpha); } } } diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index e574e9ce03..4d79f3d001 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -33,7 +33,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "تفويض العمل", "ui.sessionTurn.status.planning": "تخطيط الخطوات التالية", - "ui.sessionTurn.status.gatheringContext": "استكشاف...", + "ui.sessionTurn.status.gatheringContext": "استكشاف", "ui.sessionTurn.status.gatheredContext": "تم الاستكشاف", "ui.sessionTurn.status.searchingCodebase": "البحث في قاعدة التعليمات البرمجية", "ui.sessionTurn.status.searchingWeb": "البحث في الويب", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index e985396827..777f1455bd 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -33,7 +33,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegando trabalho", "ui.sessionTurn.status.planning": "Planejando próximos passos", - "ui.sessionTurn.status.gatheringContext": "Explorando...", + "ui.sessionTurn.status.gatheringContext": "Explorando", "ui.sessionTurn.status.gatheredContext": "Explorado", "ui.sessionTurn.status.searchingCodebase": "Pesquisando no código", "ui.sessionTurn.status.searchingWeb": "Pesquisando na web", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index 6726c45d2e..e499647dff 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -37,7 +37,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegiranje posla", "ui.sessionTurn.status.planning": "Planiranje sljedećih koraka", - "ui.sessionTurn.status.gatheringContext": "Istraživanje...", + "ui.sessionTurn.status.gatheringContext": "Istraživanje", "ui.sessionTurn.status.gatheredContext": "Istraženo", "ui.sessionTurn.status.searchingCodebase": "Pretraživanje baze koda", "ui.sessionTurn.status.searchingWeb": "Pretraživanje weba", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index bd7f06a230..546040598f 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -32,7 +32,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegerer arbejde", "ui.sessionTurn.status.planning": "Planlægger næste trin", - "ui.sessionTurn.status.gatheringContext": "Udforsker...", + "ui.sessionTurn.status.gatheringContext": "Udforsker", "ui.sessionTurn.status.gatheredContext": "Udforsket", "ui.sessionTurn.status.searchingCodebase": "Søger i koden", "ui.sessionTurn.status.searchingWeb": "Søger på nettet", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index a07ff6241a..bf5730f85f 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -36,7 +36,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "Arbeit delegieren", "ui.sessionTurn.status.planning": "Nächste Schritte planen", - "ui.sessionTurn.status.gatheringContext": "Erkunden...", + "ui.sessionTurn.status.gatheringContext": "Erkunden", "ui.sessionTurn.status.gatheredContext": "Erkundet", "ui.sessionTurn.status.searchingCodebase": "Codebasis durchsuchen", "ui.sessionTurn.status.searchingWeb": "Web durchsuchen", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index a33ff76404..4c9b89c6cf 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -33,7 +33,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegating work", "ui.sessionTurn.status.planning": "Planning next steps", - "ui.sessionTurn.status.gatheringContext": "Exploring...", + "ui.sessionTurn.status.gatheringContext": "Exploring", "ui.sessionTurn.status.gatheredContext": "Explored", "ui.sessionTurn.status.searchingCodebase": "Searching the codebase", "ui.sessionTurn.status.searchingWeb": "Searching the web", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index dcf8d569dc..2f21b398f1 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -33,7 +33,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegando trabajo", "ui.sessionTurn.status.planning": "Planificando siguientes pasos", - "ui.sessionTurn.status.gatheringContext": "Explorando...", + "ui.sessionTurn.status.gatheringContext": "Explorando", "ui.sessionTurn.status.gatheredContext": "Explorado", "ui.sessionTurn.status.searchingCodebase": "Buscando en la base de código", "ui.sessionTurn.status.searchingWeb": "Buscando en la web", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index c1832e8389..d4ea938684 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -33,7 +33,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "Délégation du travail", "ui.sessionTurn.status.planning": "Planification des prochaines étapes", - "ui.sessionTurn.status.gatheringContext": "Exploration...", + "ui.sessionTurn.status.gatheringContext": "Exploration", "ui.sessionTurn.status.gatheredContext": "Exploré", "ui.sessionTurn.status.searchingCodebase": "Recherche dans la base de code", "ui.sessionTurn.status.searchingWeb": "Recherche sur le web", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index 24d8497c13..0a4366ebef 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -32,7 +32,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "作業を委任中", "ui.sessionTurn.status.planning": "次のステップを計画中", - "ui.sessionTurn.status.gatheringContext": "探索中...", + "ui.sessionTurn.status.gatheringContext": "探索中", "ui.sessionTurn.status.gatheredContext": "探索済み", "ui.sessionTurn.status.searchingCodebase": "コードベースを検索中", "ui.sessionTurn.status.searchingWeb": "ウェブを検索中", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index 412234c61f..58bd51b991 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -33,7 +33,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "작업 위임 중", "ui.sessionTurn.status.planning": "다음 단계 계획 중", - "ui.sessionTurn.status.gatheringContext": "탐색 중...", + "ui.sessionTurn.status.gatheringContext": "탐색 중", "ui.sessionTurn.status.gatheredContext": "탐색됨", "ui.sessionTurn.status.searchingCodebase": "코드베이스 검색 중", "ui.sessionTurn.status.searchingWeb": "웹 검색 중", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index 434b8228b3..b7e604f9ac 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -36,7 +36,7 @@ export const dict: Record = { "ui.sessionTurn.status.delegating": "Delegerer arbeid", "ui.sessionTurn.status.planning": "Planlegger neste trinn", - "ui.sessionTurn.status.gatheringContext": "Utforsker...", + "ui.sessionTurn.status.gatheringContext": "Utforsker", "ui.sessionTurn.status.gatheredContext": "Utforsket", "ui.sessionTurn.status.searchingCodebase": "Søker i kodebasen", "ui.sessionTurn.status.searchingWeb": "Søker på nettet", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index 5fd3092b7f..fbccb92207 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -32,7 +32,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "Delegowanie pracy", "ui.sessionTurn.status.planning": "Planowanie kolejnych kroków", - "ui.sessionTurn.status.gatheringContext": "Eksplorowanie...", + "ui.sessionTurn.status.gatheringContext": "Eksplorowanie", "ui.sessionTurn.status.gatheredContext": "Wyeksplorowano", "ui.sessionTurn.status.searchingCodebase": "Przeszukiwanie bazy kodu", "ui.sessionTurn.status.searchingWeb": "Przeszukiwanie sieci", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index f5bed24d5f..705f2d2109 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -32,7 +32,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "Делегирование работы", "ui.sessionTurn.status.planning": "Планирование следующих шагов", - "ui.sessionTurn.status.gatheringContext": "Исследование...", + "ui.sessionTurn.status.gatheringContext": "Исследование", "ui.sessionTurn.status.gatheredContext": "Исследовано", "ui.sessionTurn.status.searchingCodebase": "Поиск в кодовой базе", "ui.sessionTurn.status.searchingWeb": "Поиск в интернете", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index 2f25641926..cf536e1ff6 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -34,7 +34,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "มอบหมายงาน", "ui.sessionTurn.status.planning": "วางแผนขั้นตอนถัดไป", - "ui.sessionTurn.status.gatheringContext": "กำลังสำรวจ...", + "ui.sessionTurn.status.gatheringContext": "กำลังสำรวจ", "ui.sessionTurn.status.gatheredContext": "สำรวจแล้ว", "ui.sessionTurn.status.searchingCodebase": "กำลังค้นหาโค้ดเบส", "ui.sessionTurn.status.searchingWeb": "กำลังค้นหาบนเว็บ", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index d651d25ab1..5d3d5613da 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -37,7 +37,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "正在委派工作", "ui.sessionTurn.status.planning": "正在规划下一步", - "ui.sessionTurn.status.gatheringContext": "正在探索...", + "ui.sessionTurn.status.gatheringContext": "正在探索", "ui.sessionTurn.status.gatheredContext": "已探索", "ui.sessionTurn.status.searchingCodebase": "正在搜索代码库", "ui.sessionTurn.status.searchingWeb": "正在搜索网页", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index d20c2f9b2e..b61349e25d 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -37,7 +37,7 @@ export const dict = { "ui.sessionTurn.status.delegating": "正在委派工作", "ui.sessionTurn.status.planning": "正在規劃下一步", - "ui.sessionTurn.status.gatheringContext": "正在探索...", + "ui.sessionTurn.status.gatheringContext": "正在探索", "ui.sessionTurn.status.gatheredContext": "已探索", "ui.sessionTurn.status.searchingCodebase": "正在搜尋程式碼庫", "ui.sessionTurn.status.searchingWeb": "正在搜尋網頁", diff --git a/packages/ui/src/styles/colors.css b/packages/ui/src/styles/colors.css index 61e3ac950a..893f252aa9 100644 --- a/packages/ui/src/styles/colors.css +++ b/packages/ui/src/styles/colors.css @@ -1,4 +1,100 @@ :root { + --gray-dark-1: #161616; + --gray-dark-2: #1c1c1c; + --gray-dark-3: #232323; + --gray-dark-4: #282828; + --gray-dark-5: #2e2e2e; + --gray-dark-6: #343434; + --gray-dark-7: #3e3e3e; + --gray-dark-8: #505050; + --gray-dark-9: #707070; + --gray-dark-10: #7e7e7e; + --gray-dark-11: #a0a0a0; + --gray-dark-12: #ededed; + --gray-light-1: #fcfcfc; + --gray-light-2: #f8f8f8; + --gray-light-3: #f3f3f3; + --gray-light-4: #ededed; + --gray-light-5: #e8e8e8; + --gray-light-6: #e2e2e2; + --gray-light-7: #dbdbdb; + --gray-light-8: #c7c7c7; + --gray-light-9: #8f8f8f; + --gray-light-10: #858585; + --gray-light-11: #6f6f6f; + --gray-light-12: #171717; + --gray-dark-alpha-1: #00000000; + --gray-dark-alpha-2: #ffffff08; + --gray-dark-alpha-3: #ffffff0f; + --gray-dark-alpha-4: #ffffff14; + --gray-dark-alpha-5: #ffffff1a; + --gray-dark-alpha-6: #ffffff21; + --gray-dark-alpha-7: #ffffff2b; + --gray-dark-alpha-8: #ffffff40; + --gray-dark-alpha-9: #ffffff63; + --gray-dark-alpha-10: #ffffff73; + --gray-dark-alpha-11: #ffffff96; + --gray-dark-alpha-12: #ffffffeb; + --gray-light-alpha-1: #00000003; + --gray-light-alpha-2: #00000008; + --gray-light-alpha-3: #0000000d; + --gray-light-alpha-4: #00000012; + --gray-light-alpha-5: #00000017; + --gray-light-alpha-6: #0000001c; + --gray-light-alpha-7: #00000024; + --gray-light-alpha-8: #00000038; + --gray-light-alpha-9: #00000070; + --gray-light-alpha-10: #0000007a; + --gray-light-alpha-11: #0000008f; + --gray-light-alpha-12: #000000e8; + --gray-dark-1: #131010; + --gray-dark-2: #1b1818; + --gray-dark-3: #252121; + --gray-dark-4: #2d2828; + --gray-dark-5: #343030; + --gray-dark-6: #3e3939; + --gray-dark-7: #4b4646; + --gray-dark-8: #645f5f; + --gray-dark-9: #716c6b; + --gray-dark-10: #7f7979; + --gray-dark-11: #b7b1b1; + --gray-dark-12: #f1ecec; + --gray-light-1: #fdfcfc; + --gray-light-2: #f9f8f8; + --gray-light-3: #f1f0f0; + --gray-light-4: #e9e8e8; + --gray-light-5: #e2e0e0; + --gray-light-6: #dad9d9; + --gray-light-7: #cfcecd; + --gray-light-8: #bcbbbb; + --gray-light-9: #8e8b8b; + --gray-light-10: #848181; + --gray-light-11: #656363; + --gray-light-12: #211e1e; + --gray-dark-alpha-1: #82383803; + --gray-dark-alpha-2: #e6c6c60b; + --gray-dark-alpha-3: #edd5d516; + --gray-dark-alpha-4: #f2e1e11e; + --gray-dark-alpha-5: #f5e8e826; + --gray-dark-alpha-6: #f5e8e831; + --gray-dark-alpha-7: #f7ecec3f; + --gray-dark-alpha-8: #faf5f559; + --gray-dark-alpha-9: #faf5f467; + --gray-dark-alpha-10: #fbf5f576; + --gray-dark-alpha-11: #fcf9f9b2; + --gray-dark-alpha-12: #fdfbfbf0; + --gray-light-alpha-1: #55000003; + --gray-light-alpha-2: #25000007; + --gray-light-alpha-3: #1100000f; + --gray-light-alpha-4: #0c000017; + --gray-light-alpha-5: #1100001f; + --gray-light-alpha-6: #07000026; + --gray-light-alpha-7: #0b060032; + --gray-light-alpha-8: #04000044; + --gray-light-alpha-9: #07000074; + --gray-light-alpha-10: #0400009c; + --gray-light-alpha-11: #0700007e; + --gray-light-alpha-12: #020000df; --smoke-dark-1: #131010; --smoke-dark-2: #1b1818; --smoke-dark-3: #252121; @@ -589,4 +685,88 @@ --amber-dark-alpha-10: #ffce48f9; --amber-dark-alpha-11: #ffab0eef; --amber-dark-alpha-12: #fff8e1f9; + + /* Legacy palette aliases (keeps older themes working) */ + --smoke-light-1: var(--gray-light-1); + --smoke-light-2: var(--gray-light-2); + --smoke-light-3: var(--gray-light-3); + --smoke-light-4: var(--gray-light-4); + --smoke-light-5: var(--gray-light-5); + --smoke-light-6: var(--gray-light-6); + --smoke-light-7: var(--gray-light-7); + --smoke-light-8: var(--gray-light-8); + --smoke-light-9: var(--gray-light-9); + --smoke-light-10: var(--gray-light-10); + --smoke-light-11: var(--gray-light-11); + --smoke-light-12: var(--gray-light-12); + + --smoke-dark-1: var(--gray-dark-1); + --smoke-dark-2: var(--gray-dark-2); + --smoke-dark-3: var(--gray-dark-3); + --smoke-dark-4: var(--gray-dark-4); + --smoke-dark-5: var(--gray-dark-5); + --smoke-dark-6: var(--gray-dark-6); + --smoke-dark-7: var(--gray-dark-7); + --smoke-dark-8: var(--gray-dark-8); + --smoke-dark-9: var(--gray-dark-9); + --smoke-dark-10: var(--gray-dark-10); + --smoke-dark-11: var(--gray-dark-11); + --smoke-dark-12: var(--gray-dark-12); + + --smoke-light-alpha-1: var(--gray-light-alpha-1); + --smoke-light-alpha-2: var(--gray-light-alpha-2); + --smoke-light-alpha-3: var(--gray-light-alpha-3); + --smoke-light-alpha-4: var(--gray-light-alpha-4); + --smoke-light-alpha-5: var(--gray-light-alpha-5); + --smoke-light-alpha-6: var(--gray-light-alpha-6); + --smoke-light-alpha-7: var(--gray-light-alpha-7); + --smoke-light-alpha-8: var(--gray-light-alpha-8); + --smoke-light-alpha-9: var(--gray-light-alpha-9); + --smoke-light-alpha-10: var(--gray-light-alpha-10); + --smoke-light-alpha-11: var(--gray-light-alpha-11); + --smoke-light-alpha-12: var(--gray-light-alpha-12); + + --smoke-dark-alpha-1: var(--gray-dark-alpha-1); + --smoke-dark-alpha-2: var(--gray-dark-alpha-2); + --smoke-dark-alpha-3: var(--gray-dark-alpha-3); + --smoke-dark-alpha-4: var(--gray-dark-alpha-4); + --smoke-dark-alpha-5: var(--gray-dark-alpha-5); + --smoke-dark-alpha-6: var(--gray-dark-alpha-6); + --smoke-dark-alpha-7: var(--gray-dark-alpha-7); + --smoke-dark-alpha-8: var(--gray-dark-alpha-8); + --smoke-dark-alpha-9: var(--gray-dark-alpha-9); + --smoke-dark-alpha-10: var(--gray-dark-alpha-10); + --smoke-dark-alpha-11: var(--gray-dark-alpha-11); + --smoke-dark-alpha-12: var(--gray-dark-alpha-12); + + --amber-lightalpha-1: var(--amber-light-alpha-1); + --amber-lightalpha-2: var(--amber-light-alpha-2); + --amber-lightalpha-3: var(--amber-light-alpha-3); + --amber-lightalpha-4: var(--amber-light-alpha-4); + --amber-lightalpha-5: var(--amber-light-alpha-5); + --amber-lightalpha-6: var(--amber-light-alpha-6); + --amber-lightalpha-7: var(--amber-light-alpha-7); + --amber-lightalpha-8: var(--amber-light-alpha-8); + --amber-lightalpha-9: var(--amber-light-alpha-9); + --amber-lightalpha-10: var(--amber-light-alpha-10); + --amber-lightalpha-11: var(--amber-light-alpha-11); + --amber-lightalpha-12: var(--amber-light-alpha-12); + + --amber-darkalpha-1: var(--amber-dark-alpha-1); + --amber-darkalpha-2: var(--amber-dark-alpha-2); + --amber-darkalpha-3: var(--amber-dark-alpha-3); + --amber-darkalpha-4: var(--amber-dark-alpha-4); + --amber-darkalpha-5: var(--amber-dark-alpha-5); + --amber-darkalpha-6: var(--amber-dark-alpha-6); + --amber-darkalpha-7: var(--amber-dark-alpha-7); + --amber-darkalpha-8: var(--amber-dark-alpha-8); + --amber-darkalpha-9: var(--amber-dark-alpha-9); + --amber-darkalpha-10: var(--amber-dark-alpha-10); + --amber-darkalpha-11: var(--amber-dark-alpha-11); + --amber-darkalpha-12: var(--amber-dark-alpha-12); + + --purple-light-9: var(--lilac-light-9); + --purple-dark-9: var(--lilac-dark-9); + --cyan-light-9: var(--blue-light-9); + --cyan-dark-9: var(--blue-dark-9); } diff --git a/packages/ui/src/theme/default-themes.ts b/packages/ui/src/theme/default-themes.ts index 4d44c3b018..52b2b42eba 100644 --- a/packages/ui/src/theme/default-themes.ts +++ b/packages/ui/src/theme/default-themes.ts @@ -1,5 +1,6 @@ import type { DesktopTheme } from "./types" import oc1ThemeJson from "./themes/oc-1.json" +import oc2ThemeJson from "./themes/oc-2.json" import tokyoThemeJson from "./themes/tokyonight.json" import draculaThemeJson from "./themes/dracula.json" import monokaiThemeJson from "./themes/monokai.json" @@ -16,6 +17,7 @@ import gruvboxThemeJson from "./themes/gruvbox.json" import auraThemeJson from "./themes/aura.json" export const oc1Theme = oc1ThemeJson as DesktopTheme +export const oc2Theme = oc2ThemeJson as DesktopTheme export const tokyonightTheme = tokyoThemeJson as DesktopTheme export const draculaTheme = draculaThemeJson as DesktopTheme export const monokaiTheme = monokaiThemeJson as DesktopTheme @@ -33,6 +35,7 @@ export const auraTheme = auraThemeJson as DesktopTheme export const DEFAULT_THEMES: Record = { "oc-1": oc1Theme, + "oc-2": oc2Theme, aura: auraTheme, ayu: ayuTheme, carbonfox: carbonfoxTheme, diff --git a/packages/ui/src/theme/index.ts b/packages/ui/src/theme/index.ts index e8d2fe79f8..d2c60179ec 100644 --- a/packages/ui/src/theme/index.ts +++ b/packages/ui/src/theme/index.ts @@ -32,6 +32,7 @@ export { ThemeProvider, useTheme, type ColorScheme } from "./context" export { DEFAULT_THEMES, oc1Theme, + oc2Theme, tokyonightTheme, draculaTheme, monokaiTheme, diff --git a/packages/ui/src/theme/themes/oc-2.json b/packages/ui/src/theme/themes/oc-2.json new file mode 100644 index 0000000000..8c12c33b2e --- /dev/null +++ b/packages/ui/src/theme/themes/oc-2.json @@ -0,0 +1,532 @@ +{ + "$schema": "https://opencode.ai/desktop-theme.json", + "name": "OC-2", + "id": "oc-2", + "light": { + "seeds": { + "neutral": "#8e8b8b", + "primary": "#dcde8d", + "success": "#12c905", + "warning": "#ffdc17", + "error": "#fc533a", + "info": "#a753ae", + "interactive": "#034cff", + "diffAdd": "#9ff29a", + "diffDelete": "#fc533a" + }, + "overrides": { + "background-base": "#f8f7f7", + "background-weak": "var(--gray-light-3)", + "background-strong": "var(--gray-light-1)", + "background-stronger": "#fcfcfc", + "surface-base": "var(--gray-light-alpha-2)", + "base": "var(--gray-light-alpha-2)", + "surface-base-hover": "#0500000f", + "surface-base-active": "var(--gray-light-alpha-3)", + "surface-base-interactive-active": "var(--cobalt-light-alpha-3)", + "base2": "var(--gray-light-alpha-2)", + "base3": "var(--gray-light-alpha-2)", + "surface-inset-base": "var(--gray-light-alpha-2)", + "surface-inset-base-hover": "var(--gray-light-alpha-3)", + "surface-inset-strong": "#1f000017", + "surface-inset-strong-hover": "#1f000017", + "surface-raised-base": "var(--gray-light-alpha-2)", + "surface-float-base": "var(--gray-dark-1)", + "surface-float-base-hover": "var(--gray-dark-2)", + "surface-raised-base-hover": "var(--gray-light-alpha-3)", + "surface-raised-base-active": "var(--gray-light-alpha-5)", + "surface-raised-strong": "var(--gray-light-1)", + "surface-raised-strong-hover": "var(--white)", + "surface-raised-stronger": "var(--white)", + "surface-raised-stronger-hover": "var(--white)", + "surface-weak": "var(--gray-light-alpha-3)", + "surface-weaker": "var(--gray-light-alpha-4)", + "surface-strong": "#ffffff", + "surface-raised-stronger-non-alpha": "var(--white)", + "surface-brand-base": "var(--yuzu-light-9)", + "surface-brand-hover": "var(--yuzu-light-10)", + "surface-interactive-base": "var(--cobalt-light-3)", + "surface-interactive-hover": "#E5F0FF", + "surface-interactive-weak": "var(--cobalt-light-2)", + "surface-interactive-weak-hover": "var(--cobalt-light-3)", + "surface-success-base": "var(--apple-light-3)", + "surface-success-weak": "var(--apple-light-2)", + "surface-success-strong": "var(--apple-light-9)", + "surface-warning-base": "var(--solaris-light-3)", + "surface-warning-weak": "var(--solaris-light-2)", + "surface-warning-strong": "var(--solaris-light-9)", + "surface-critical-base": "var(--ember-light-3)", + "surface-critical-weak": "var(--ember-light-2)", + "surface-critical-strong": "var(--ember-light-9)", + "surface-info-base": "var(--lilac-light-3)", + "surface-info-weak": "var(--lilac-light-2)", + "surface-info-strong": "var(--lilac-light-9)", + "surface-diff-unchanged-base": "#ffffff00", + "surface-diff-skip-base": "var(--gray-light-2)", + "surface-diff-hidden-base": "var(--blue-light-3)", + "surface-diff-hidden-weak": "var(--blue-light-2)", + "surface-diff-hidden-weaker": "var(--blue-light-1)", + "surface-diff-hidden-strong": "var(--blue-light-5)", + "surface-diff-hidden-stronger": "var(--blue-light-9)", + "surface-diff-add-base": "#dafbe0", + "surface-diff-add-weak": "var(--mint-light-2)", + "surface-diff-add-weaker": "var(--mint-light-1)", + "surface-diff-add-strong": "var(--mint-light-5)", + "surface-diff-add-stronger": "var(--mint-light-9)", + "surface-diff-delete-base": "var(--ember-light-3)", + "surface-diff-delete-weak": "var(--ember-light-2)", + "surface-diff-delete-weaker": "var(--ember-light-1)", + "surface-diff-delete-strong": "var(--ember-light-6)", + "surface-diff-delete-stronger": "var(--ember-light-9)", + "input-base": "var(--gray-light-1)", + "input-hover": "var(--gray-light-2)", + "input-active": "var(--cobalt-light-1)", + "input-selected": "var(--cobalt-light-4)", + "input-focus": "var(--cobalt-light-1)", + "input-disabled": "var(--gray-light-4)", + "text-base": "var(--gray-light-11)", + "text-weak": "var(--gray-light-9)", + "text-weaker": "var(--gray-light-8)", + "text-strong": "var(--gray-light-12)", + "text-invert-base": "var(--gray-dark-alpha-11)", + "text-invert-weak": "var(--gray-dark-alpha-9)", + "text-invert-weaker": "var(--gray-dark-alpha-8)", + "text-invert-strong": "var(--gray-dark-alpha-12)", + "text-interactive-base": "var(--cobalt-light-9)", + "text-on-brand-base": "var(--gray-light-alpha-11)", + "text-on-interactive-base": "var(--gray-light-1)", + "text-on-interactive-weak": "var(--gray-dark-alpha-11)", + "text-on-success-base": "var(--apple-light-10)", + "text-on-critical-base": "var(--ember-light-10)", + "text-on-critical-weak": "var(--ember-light-8)", + "text-on-critical-strong": "var(--ember-light-12)", + "text-on-warning-base": "var(--gray-dark-alpha-11)", + "text-on-info-base": "var(--gray-dark-alpha-11)", + "text-diff-add-base": "var(--mint-light-11)", + "text-diff-delete-base": "var(--ember-light-10)", + "text-diff-delete-strong": "var(--ember-light-12)", + "text-diff-add-strong": "var(--mint-light-12)", + "text-on-info-weak": "var(--gray-dark-alpha-9)", + "text-on-info-strong": "var(--gray-dark-alpha-12)", + "text-on-warning-weak": "var(--gray-dark-alpha-9)", + "text-on-warning-strong": "var(--gray-dark-alpha-12)", + "text-on-success-weak": "var(--apple-light-6)", + "text-on-success-strong": "var(--apple-light-12)", + "text-on-brand-weak": "var(--gray-light-alpha-9)", + "text-on-brand-weaker": "var(--gray-light-alpha-8)", + "text-on-brand-strong": "var(--gray-light-alpha-12)", + "button-primary-base": "var(--gray-light-12)", + "button-secondary-base": "var(--gray-light-1)", + "button-secondary-hover": "FFFFFF0A", + "border-base": "var(--gray-light-alpha-7)", + "border-hover": "var(--gray-light-alpha-8)", + "border-active": "var(--gray-light-alpha-9)", + "border-selected": "var(--cobalt-light-alpha-9)", + "border-disabled": "var(--gray-light-alpha-8)", + "border-focus": "var(--gray-light-alpha-9)", + "border-weak-base": "var(--gray-light-alpha-5)", + "border-strong-base": "var(--gray-light-alpha-7)", + "border-strong-hover": "var(--gray-light-alpha-8)", + "border-strong-active": "var(--gray-light-alpha-7)", + "border-strong-selected": "var(--cobalt-light-alpha-6)", + "border-strong-disabled": "var(--gray-light-alpha-6)", + "border-strong-focus": "var(--gray-light-alpha-7)", + "border-weak-hover": "var(--gray-light-alpha-6)", + "border-weak-active": "var(--gray-light-alpha-7)", + "border-weak-selected": "var(--cobalt-light-alpha-5)", + "border-weak-disabled": "var(--gray-light-alpha-6)", + "border-weak-focus": "var(--gray-light-alpha-7)", + "border-interactive-base": "var(--cobalt-light-7)", + "border-interactive-hover": "var(--cobalt-light-8)", + "border-interactive-active": "var(--cobalt-light-9)", + "border-interactive-selected": "var(--cobalt-light-9)", + "border-interactive-disabled": "var(--gray-light-8)", + "border-interactive-focus": "var(--cobalt-light-9)", + "border-success-base": "var(--apple-light-6)", + "border-success-hover": "var(--apple-light-7)", + "border-success-selected": "var(--apple-light-9)", + "border-warning-base": "var(--solaris-light-6)", + "border-warning-hover": "var(--solaris-light-7)", + "border-warning-selected": "var(--solaris-light-9)", + "border-critical-base": "var(--ember-light-6)", + "border-critical-hover": "var(--ember-light-7)", + "border-critical-selected": "var(--ember-light-9)", + "border-info-base": "var(--lilac-light-6)", + "border-info-hover": "var(--lilac-light-7)", + "border-info-selected": "var(--lilac-light-9)", + "icon-base": "var(--gray-light-9)", + "icon-hover": "var(--gray-light-11)", + "icon-active": "var(--gray-light-12)", + "icon-selected": "var(--gray-light-12)", + "icon-disabled": "var(--gray-light-8)", + "icon-focus": "var(--gray-light-12)", + "icon-invert-base": "#ffffff", + "icon-weak-base": "var(--gray-light-7)", + "icon-weak-hover": "var(--gray-light-8)", + "icon-weak-active": "var(--gray-light-9)", + "icon-weak-selected": "var(--gray-light-10)", + "icon-weak-disabled": "var(--gray-light-6)", + "icon-weak-focus": "var(--gray-light-9)", + "icon-strong-base": "var(--gray-light-12)", + "icon-strong-hover": "#151313", + "icon-strong-active": "#020202", + "icon-strong-selected": "#020202", + "icon-strong-disabled": "var(--gray-light-6)", + "icon-strong-focus": "#020202", + "icon-brand-base": "var(--gray-light-12)", + "icon-interactive-base": "var(--cobalt-light-9)", + "icon-success-base": "var(--apple-light-7)", + "icon-success-hover": "var(--apple-light-8)", + "icon-success-active": "var(--apple-light-11)", + "icon-warning-base": "var(--amber-light-7)", + "icon-warning-hover": "var(--amber-light-8)", + "icon-warning-active": "var(--amber-light-11)", + "icon-critical-base": "var(--ember-light-10)", + "icon-critical-hover": "var(--ember-light-11)", + "icon-critical-active": "var(--ember-light-12)", + "icon-info-base": "var(--lilac-light-7)", + "icon-info-hover": "var(--lilac-light-8)", + "icon-info-active": "var(--lilac-light-11)", + "icon-on-brand-base": "var(--gray-light-alpha-11)", + "icon-on-brand-hover": "var(--gray-light-alpha-12)", + "icon-on-brand-selected": "var(--gray-light-alpha-12)", + "icon-on-interactive-base": "var(--gray-light-1)", + "icon-agent-plan-base": "var(--purple-light-9)", + "icon-agent-docs-base": "var(--amber-light-9)", + "icon-agent-ask-base": "var(--cyan-light-9)", + "icon-agent-build-base": "var(--cobalt-light-9)", + "icon-on-success-base": "var(--apple-light-alpha-9)", + "icon-on-success-hover": "var(--apple-light-alpha-10)", + "icon-on-success-selected": "var(--apple-light-alpha-11)", + "icon-on-warning-base": "var(--amber-lightalpha-9)", + "icon-on-warning-hover": "var(--amber-lightalpha-10)", + "icon-on-warning-selected": "var(--amber-lightalpha-11)", + "icon-on-critical-base": "var(--ember-light-alpha-9)", + "icon-on-critical-hover": "var(--ember-light-alpha-10)", + "icon-on-critical-selected": "var(--ember-light-alpha-11)", + "icon-on-info-base": "var(--lilac-light-9)", + "icon-on-info-hover": "var(--lilac-light-alpha-10)", + "icon-on-info-selected": "var(--lilac-light-alpha-11)", + "icon-diff-add-base": "var(--mint-light-11)", + "icon-diff-add-hover": "var(--mint-light-12)", + "icon-diff-add-active": "var(--mint-light-12)", + "icon-diff-delete-base": "var(--ember-light-10)", + "icon-diff-delete-hover": "var(--ember-light-11)", + "syntax-comment": "var(--text-weak)", + "syntax-regexp": "var(--text-base)", + "syntax-string": "#006656", + "syntax-keyword": "var(--text-weak)", + "syntax-primitive": "#fb4804", + "syntax-operator": "var(--text-base)", + "syntax-variable": "var(--text-strong)", + "syntax-property": "#ed6dc8", + "syntax-type": "#596600", + "syntax-constant": "#007b80", + "syntax-punctuation": "var(--text-base)", + "syntax-object": "var(--text-strong)", + "syntax-success": "var(--apple-light-10)", + "syntax-warning": "var(--amber-light-10)", + "syntax-critical": "var(--ember-light-10)", + "syntax-info": "#0092a8", + "syntax-diff-add": "var(--mint-light-11)", + "syntax-diff-delete": "var(--ember-light-11)", + "syntax-diff-unknown": "#ff0000", + "markdown-heading": "#d68c27", + "markdown-text": "#1a1a1a", + "markdown-link": "#3b7dd8", + "markdown-link-text": "#318795", + "markdown-code": "#3d9a57", + "markdown-block-quote": "#b0851f", + "markdown-emph": "#b0851f", + "markdown-strong": "#d68c27", + "markdown-horizontal-rule": "#8a8a8a", + "markdown-list-item": "#3b7dd8", + "markdown-list-enumeration": "#318795", + "markdown-image": "#3b7dd8", + "markdown-image-text": "#318795", + "markdown-code-block": "#1a1a1a", + "border-color": "#ffffff", + "border-weaker-base": "var(--gray-light-alpha-3)", + "border-weaker-hover": "var(--gray-light-alpha-4)", + "border-weaker-active": "var(--gray-light-alpha-6)", + "border-weaker-selected": "var(--cobalt-light-alpha-4)", + "border-weaker-disabled": "var(--gray-light-alpha-2)", + "border-weaker-focus": "var(--gray-light-alpha-6)", + "button-ghost-hover": "var(--gray-light-alpha-2)", + "button-ghost-hover2": "var(--gray-light-alpha-3)", + "avatar-background-pink": "#feeef8", + "avatar-background-mint": "#e1fbf4", + "avatar-background-orange": "#fff1e7", + "avatar-background-purple": "#f9f1fe", + "avatar-background-cyan": "#e7f9fb", + "avatar-background-lime": "#eefadc", + "avatar-text-pink": "#cd1d8d", + "avatar-text-mint": "#147d6f", + "avatar-text-orange": "#ed5f00", + "avatar-text-purple": "#8445bc", + "avatar-text-cyan": "#0894b3", + "avatar-text-lime": "#5d770d" + } + }, + "dark": { + "seeds": { + "neutral": "#716c6b", + "primary": "#fab283", + "success": "#12c905", + "warning": "#fcd53a", + "error": "#fc533a", + "info": "#edb2f1", + "interactive": "#034cff", + "diffAdd": "#c8ffc4", + "diffDelete": "#fc533a" + }, + "overrides": { + "base": "var(--gray-dark-alpha-2)", + "base2": "var(--gray-dark-alpha-2)", + "base3": "var(--gray-dark-alpha-2)", + "background-base": "#101010", + "background-weak": "#1E1E1E", + "background-strong": "#121212", + "background-stronger": "#151515", + "surface-base": "var(--gray-dark-alpha-2)", + "surface-base-hover": "#FFFFFF0A", + "surface-base-active": "var(--gray-dark-alpha-3)", + "surface-base-interactive-active": "var(--cobalt-dark-alpha-2)", + "surface-inset-base": "#0e0b0b7f", + "surface-inset-base-hover": "#0e0b0b7f", + "surface-inset-strong": "#060505cc", + "surface-inset-strong-hover": "#060505cc", + "surface-raised-base": "var(--gray-dark-alpha-3)", + "surface-float-base": "var(--gray-dark-1)", + "surface-float-base-hover": "var(--gray-dark-2)", + "surface-raised-base-hover": "var(--gray-dark-alpha-4)", + "surface-raised-base-active": "var(--gray-dark-alpha-5)", + "surface-raised-strong": "var(--gray-dark-alpha-4)", + "surface-raised-strong-hover": "var(--gray-dark-alpha-6)", + "surface-raised-stronger": "var(--gray-dark-alpha-6)", + "surface-raised-stronger-hover": "var(--gray-dark-alpha-7)", + "surface-weak": "var(--gray-dark-alpha-4)", + "surface-weaker": "var(--gray-dark-alpha-5)", + "surface-strong": "var(--gray-dark-alpha-7)", + "surface-raised-stronger-non-alpha": "#1B1B1B", + "surface-brand-base": "var(--yuzu-light-9)", + "surface-brand-hover": "var(--yuzu-light-10)", + "surface-interactive-base": "var(--cobalt-dark-3)", + "surface-interactive-hover": "#0A1D4D", + "surface-interactive-weak": "var(--cobalt-dark-2)", + "surface-interactive-weak-hover": "var(--cobalt-light-3)", + "surface-success-base": "var(--apple-dark-3)", + "surface-success-weak": "var(--apple-dark-2)", + "surface-success-strong": "var(--apple-dark-9)", + "surface-warning-base": "var(--solaris-light-3)", + "surface-warning-weak": "var(--solaris-light-2)", + "surface-warning-strong": "var(--solaris-light-9)", + "surface-critical-base": "var(--ember-dark-3)", + "surface-critical-weak": "var(--ember-dark-2)", + "surface-critical-strong": "var(--ember-dark-9)", + "surface-info-base": "var(--lilac-light-3)", + "surface-info-weak": "var(--lilac-light-2)", + "surface-info-strong": "var(--lilac-light-9)", + "surface-diff-unchanged-base": "var(--gray-dark-1)", + "surface-diff-skip-base": "var(--gray-dark-alpha-1)", + "surface-diff-hidden-base": "var(--blue-dark-2)", + "surface-diff-hidden-weak": "var(--blue-dark-1)", + "surface-diff-hidden-weaker": "var(--blue-dark-3)", + "surface-diff-hidden-strong": "var(--blue-dark-5)", + "surface-diff-hidden-stronger": "var(--blue-dark-11)", + "surface-diff-add-base": "var(--mint-dark-3)", + "surface-diff-add-weak": "var(--mint-dark-4)", + "surface-diff-add-weaker": "var(--mint-dark-3)", + "surface-diff-add-strong": "var(--mint-dark-5)", + "surface-diff-add-stronger": "var(--mint-dark-11)", + "surface-diff-delete-base": "var(--ember-dark-3)", + "surface-diff-delete-weak": "var(--ember-dark-4)", + "surface-diff-delete-weaker": "var(--ember-dark-3)", + "surface-diff-delete-strong": "var(--ember-dark-5)", + "surface-diff-delete-stronger": "var(--ember-dark-11)", + "input-base": "var(--gray-dark-2)", + "input-hover": "var(--gray-dark-2)", + "input-active": "var(--cobalt-dark-1)", + "input-selected": "var(--cobalt-dark-2)", + "input-focus": "var(--cobalt-dark-1)", + "input-disabled": "var(--gray-dark-4)", + "text-base": "var(--gray-dark-alpha-11)", + "text-weak": "var(--gray-dark-alpha-9)", + "text-weaker": "var(--gray-dark-alpha-8)", + "text-strong": "var(--gray-dark-alpha-12)", + "text-invert-base": "var(--gray-dark-alpha-11)", + "text-invert-weak": "var(--gray-dark-alpha-9)", + "text-invert-weaker": "var(--gray-dark-alpha-8)", + "text-invert-strong": "var(--gray-dark-alpha-12)", + "text-interactive-base": "var(--cobalt-dark-11)", + "text-on-brand-base": "var(--gray-dark-alpha-11)", + "text-on-interactive-base": "var(--gray-dark-12)", + "text-on-interactive-weak": "var(--gray-dark-alpha-11)", + "text-on-success-base": "var(--apple-dark-9)", + "text-on-critical-base": "var(--ember-dark-9)", + "text-on-critical-weak": "var(--ember-dark-8)", + "text-on-critical-strong": "var(--ember-dark-12)", + "text-on-warning-base": "var(--gray-dark-alpha-11)", + "text-on-info-base": "var(--gray-dark-alpha-11)", + "text-diff-add-base": "var(--mint-dark-11)", + "text-diff-delete-base": "var(--ember-dark-9)", + "text-diff-delete-strong": "var(--ember-dark-12)", + "text-diff-add-strong": "var(--mint-dark-8)", + "text-on-info-weak": "var(--gray-dark-alpha-9)", + "text-on-info-strong": "var(--gray-dark-alpha-12)", + "text-on-warning-weak": "var(--gray-dark-alpha-9)", + "text-on-warning-strong": "var(--gray-dark-alpha-12)", + "text-on-success-weak": "var(--apple-dark-8)", + "text-on-success-strong": "var(--apple-dark-12)", + "text-on-brand-weak": "var(--gray-dark-alpha-9)", + "text-on-brand-weaker": "var(--gray-dark-alpha-8)", + "text-on-brand-strong": "var(--gray-dark-alpha-12)", + "button-primary-base": "var(--gray-dark-12)", + "button-secondary-base": "var(--gray-dark-2)", + "button-secondary-hover": "#FFFFFF0A", + "border-base": "var(--gray-dark-alpha-7)", + "border-hover": "var(--gray-dark-alpha-8)", + "border-active": "var(--gray-dark-alpha-9)", + "border-selected": "var(--cobalt-dark-alpha-11)", + "border-disabled": "var(--gray-dark-alpha-8)", + "border-focus": "var(--gray-dark-alpha-9)", + "border-weak-base": "var(--gray-dark-alpha-4)", + "border-weak-hover": "var(--gray-dark-alpha-7)", + "border-weak-active": "var(--gray-dark-alpha-8)", + "border-weak-selected": "var(--cobalt-dark-alpha-6)", + "border-weak-disabled": "var(--gray-dark-alpha-6)", + "border-weak-focus": "var(--gray-dark-alpha-8)", + "border-strong-base": "var(--gray-dark-alpha-8)", + "border-interactive-base": "var(--cobalt-light-7)", + "border-interactive-hover": "var(--cobalt-light-8)", + "border-interactive-active": "var(--cobalt-light-9)", + "border-interactive-selected": "var(--cobalt-light-9)", + "border-interactive-disabled": "var(--gray-light-8)", + "border-interactive-focus": "var(--cobalt-light-9)", + "border-success-base": "var(--apple-light-6)", + "border-success-hover": "var(--apple-light-7)", + "border-success-selected": "var(--apple-light-9)", + "border-warning-base": "var(--solaris-light-6)", + "border-warning-hover": "var(--solaris-light-7)", + "border-warning-selected": "var(--solaris-light-9)", + "border-critical-base": "var(--ember-dark-5)", + "border-critical-hover": "var(--ember-dark-7)", + "border-critical-selected": "var(--ember-dark-9)", + "border-info-base": "var(--lilac-light-6)", + "border-info-hover": "var(--lilac-light-7)", + "border-info-selected": "var(--lilac-light-9)", + "icon-base": "var(--gray-dark-10)", + "icon-hover": "var(--gray-dark-11)", + "icon-active": "var(--gray-dark-12)", + "icon-selected": "var(--gray-dark-12)", + "icon-disabled": "var(--gray-dark-8)", + "icon-focus": "var(--gray-dark-12)", + "icon-invert-base": "var(--gray-dark-1)", + "icon-weak-base": "var(--gray-dark-6)", + "icon-weak-hover": "var(--gray-light-7)", + "icon-weak-active": "var(--gray-light-8)", + "icon-weak-selected": "var(--gray-light-9)", + "icon-weak-disabled": "var(--gray-light-4)", + "icon-weak-focus": "var(--gray-light-9)", + "icon-strong-base": "var(--gray-dark-12)", + "icon-strong-hover": "#F3F3F3", + "icon-strong-active": "#EBEBEB", + "icon-strong-selected": "#FCFCFC", + "icon-strong-disabled": "var(--gray-dark-7)", + "icon-strong-focus": "#FCFCFC", + "icon-brand-base": "var(--white)", + "icon-interactive-base": "var(--cobalt-dark-11)", + "icon-success-base": "var(--apple-dark-9)", + "icon-success-hover": "var(--apple-dark-10)", + "icon-success-active": "var(--apple-dark-11)", + "icon-warning-base": "var(--amber-dark-9)", + "icon-warning-hover": "var(--amber-dark-8)", + "icon-warning-active": "var(--amber-dark-11)", + "icon-critical-base": "var(--ember-dark-9)", + "icon-critical-hover": "var(--ember-dark-11)", + "icon-critical-active": "var(--ember-dark-12)", + "icon-info-base": "var(--lilac-dark-7)", + "icon-info-hover": "var(--lilac-dark-8)", + "icon-info-active": "var(--lilac-dark-11)", + "icon-on-brand-base": "var(--gray-light-alpha-11)", + "icon-on-brand-hover": "var(--gray-light-alpha-12)", + "icon-on-brand-selected": "var(--gray-light-alpha-12)", + "icon-on-interactive-base": "var(--gray-dark-12)", + "icon-agent-plan-base": "var(--purple-dark-9)", + "icon-agent-docs-base": "var(--amber-dark-9)", + "icon-agent-ask-base": "var(--cyan-dark-9)", + "icon-agent-build-base": "var(--cobalt-dark-11)", + "icon-on-success-base": "var(--apple-dark-alpha-9)", + "icon-on-success-hover": "var(--apple-dark-alpha-10)", + "icon-on-success-selected": "var(--apple-dark-alpha-11)", + "icon-on-warning-base": "var(--amber-darkalpha-9)", + "icon-on-warning-hover": "var(--amber-darkalpha-10)", + "icon-on-warning-selected": "var(--amber-darkalpha-11)", + "icon-on-critical-base": "var(--ember-dark-alpha-9)", + "icon-on-critical-hover": "var(--ember-dark-alpha-10)", + "icon-on-critical-selected": "var(--ember-dark-alpha-11)", + "icon-on-info-base": "var(--lilac-dark-9)", + "icon-on-info-hover": "var(--lilac-dark-alpha-10)", + "icon-on-info-selected": "var(--lilac-dark-alpha-11)", + "icon-diff-add-base": "var(--mint-dark-11)", + "icon-diff-add-hover": "var(--mint-dark-10)", + "icon-diff-add-active": "var(--mint-dark-11)", + "icon-diff-delete-base": "var(--ember-dark-9)", + "icon-diff-delete-hover": "var(--ember-dark-10)", + "syntax-comment": "var(--text-weak)", + "syntax-regexp": "var(--text-base)", + "syntax-string": "#00ceb9", + "syntax-keyword": "var(--text-weak)", + "syntax-primitive": "#ffba92", + "syntax-operator": "var(--text-weak)", + "syntax-variable": "var(--text-strong)", + "syntax-property": "#ff9ae2", + "syntax-type": "#ecf58c", + "syntax-constant": "#93e9f6", + "syntax-punctuation": "var(--text-weak)", + "syntax-object": "var(--text-strong)", + "syntax-success": "var(--apple-dark-10)", + "syntax-warning": "var(--amber-dark-10)", + "syntax-critical": "var(--ember-dark-10)", + "syntax-info": "#93e9f6", + "syntax-diff-add": "var(--mint-dark-11)", + "syntax-diff-delete": "var(--ember-dark-11)", + "syntax-diff-unknown": "#ff0000", + "markdown-heading": "#9d7cd8", + "markdown-text": "#eeeeee", + "markdown-link": "#fab283", + "markdown-link-text": "#56b6c2", + "markdown-code": "#7fd88f", + "markdown-block-quote": "#e5c07b", + "markdown-emph": "#e5c07b", + "markdown-strong": "#f5a742", + "markdown-horizontal-rule": "#808080", + "markdown-list-item": "#fab283", + "markdown-list-enumeration": "#56b6c2", + "markdown-image": "#fab283", + "markdown-image-text": "#56b6c2", + "markdown-code-block": "#eeeeee", + "border-color": "#ffffff", + "border-weaker-base": "var(--gray-dark-alpha-3)", + "border-weaker-hover": "var(--gray-dark-alpha-4)", + "border-weaker-active": "var(--gray-dark-alpha-6)", + "border-weaker-selected": "var(--cobalt-dark-alpha-3)", + "border-weaker-disabled": "var(--gray-dark-alpha-2)", + "border-weaker-focus": "var(--gray-dark-alpha-6)", + "button-ghost-hover": "var(--gray-dark-alpha-2)", + "button-ghost-hover2": "var(--gray-dark-alpha-3)", + "avatar-background-pink": "#501b3f", + "avatar-background-mint": "#033a34", + "avatar-background-orange": "#5f2a06", + "avatar-background-purple": "#432155", + "avatar-background-cyan": "#0f3058", + "avatar-background-lime": "#2b3711", + "avatar-text-pink": "#e34ba9", + "avatar-text-mint": "#95f3d9", + "avatar-text-orange": "#ff802b", + "avatar-text-purple": "#9d5bd2", + "avatar-text-cyan": "#369eff", + "avatar-text-lime": "#c4f042" + } + } +} diff --git a/packages/ui/sst-env.d.ts b/packages/ui/sst-env.d.ts index b6a7e9066e..64441936d7 100644 --- a/packages/ui/sst-env.d.ts +++ b/packages/ui/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/util/sst-env.d.ts b/packages/util/sst-env.d.ts index b6a7e9066e..64441936d7 100644 --- a/packages/util/sst-env.d.ts +++ b/packages/util/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/packages/web/src/content/docs/lsp.mdx b/packages/web/src/content/docs/lsp.mdx index 339761a9b3..f242f4c5e4 100644 --- a/packages/web/src/content/docs/lsp.mdx +++ b/packages/web/src/content/docs/lsp.mdx @@ -27,6 +27,7 @@ OpenCode comes with several built-in LSP servers for popular languages: | gopls | .go | `go` command available | | hls | .hs, .lhs | `haskell-language-server-wrapper` command available | | jdtls | .java | `Java SDK (version 21+)` installed | +| julials | .jl | `julia` and `LanguageServer.jl` installed | | kotlin-ls | .kt, .kts | Auto-installs for Kotlin projects | | lua-ls | .lua | Auto-installs for Lua projects | | nixd | .nix | `nixd` command available | diff --git a/packages/web/sst-env.d.ts b/packages/web/sst-env.d.ts index b6a7e9066e..64441936d7 100644 --- a/packages/web/sst-env.d.ts +++ b/packages/web/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/sdks/vscode/sst-env.d.ts b/sdks/vscode/sst-env.d.ts index b6a7e9066e..64441936d7 100644 --- a/sdks/vscode/sst-env.d.ts +++ b/sdks/vscode/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ /// diff --git a/sst-env.d.ts b/sst-env.d.ts index b2ae763562..6b3ec54dee 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -2,6 +2,7 @@ /* tslint:disable */ /* eslint-disable */ /* deno-fmt-ignore-file */ +/* biome-ignore-all lint: auto-generated */ declare module "sst" { export interface Resource {