mirror of
https://github.com/anomalyco/opencode.git
synced 2026-02-09 18:34:21 +00:00
Compare commits
5 Commits
fix-plugin
...
ask-questi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5849df9bbc | ||
|
|
5a2768b546 | ||
|
|
0c3cb95f64 | ||
|
|
21cd0482ba | ||
|
|
14b827f941 |
139
.github/workflows/pr-standards.yml
vendored
139
.github/workflows/pr-standards.yml
vendored
@@ -1,139 +0,0 @@
|
||||
name: PR Standards
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, edited, synchronize]
|
||||
|
||||
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:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check PR standards
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const title = pr.title;
|
||||
|
||||
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) {
|
||||
// Label wasn't present, ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function comment(marker, body) {
|
||||
const markerText = `<!-- pr-standards:${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(markerText));
|
||||
if (existing) return;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
body: markerText + '\n' + body
|
||||
});
|
||||
}
|
||||
|
||||
// Step 1: Check title format
|
||||
// Matches: feat:, feat(scope):, feat (scope):, etc.
|
||||
const titlePattern = /^(feat|fix|docs|chore|refactor|test)\s*(\([a-zA-Z0-9-]+\))?\s*:/;
|
||||
const hasValidTitle = titlePattern.test(title);
|
||||
|
||||
if (!hasValidTitle) {
|
||||
await addLabel('needs:title');
|
||||
await comment('title', `Hey! Your PR title \`${title}\` doesn't follow conventional commit format.
|
||||
|
||||
Please update it to start with one of:
|
||||
- \`feat:\` or \`feat(scope):\` new feature
|
||||
- \`fix:\` or \`fix(scope):\` bug fix
|
||||
- \`docs:\` or \`docs(scope):\` documentation changes
|
||||
- \`chore:\` or \`chore(scope):\` maintenance tasks
|
||||
- \`refactor:\` or \`refactor(scope):\` code refactoring
|
||||
- \`test:\` or \`test(scope):\` adding or updating tests
|
||||
|
||||
Where \`scope\` is the package name (e.g., \`app\`, \`desktop\`, \`opencode\`).
|
||||
|
||||
See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#pr-titles) for details.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await removeLabel('needs:title');
|
||||
|
||||
// Step 2: Check for linked issue (skip for docs/refactor PRs)
|
||||
const skipIssueCheck = /^(docs|refactor)\s*(\([a-zA-Z0-9-]+\))?\s*:/.test(title);
|
||||
if (skipIssueCheck) {
|
||||
await removeLabel('needs:issue');
|
||||
console.log('Skipping issue check for docs/refactor PR');
|
||||
return;
|
||||
}
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!, $number: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $number) {
|
||||
closingIssuesReferences(first: 1) {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = await github.graphql(query, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
number: pr.number
|
||||
});
|
||||
|
||||
const linkedIssues = result.repository.pullRequest.closingIssuesReferences.totalCount;
|
||||
|
||||
if (linkedIssues === 0) {
|
||||
await addLabel('needs:issue');
|
||||
await comment('issue', `Thanks for your contribution!
|
||||
|
||||
This PR doesn't have a linked issue. All PRs must reference an existing issue.
|
||||
|
||||
Please:
|
||||
1. Open an issue describing the bug/feature (if one doesn't exist)
|
||||
2. Add \`Fixes #<number>\` or \`Closes #<number>\` to this PR description
|
||||
|
||||
See [CONTRIBUTING.md](../blob/dev/CONTRIBUTING.md#issue-first-policy) for details.`);
|
||||
return;
|
||||
}
|
||||
|
||||
await removeLabel('needs:issue');
|
||||
console.log('PR meets all standards');
|
||||
28
.github/workflows/publish.yml
vendored
28
.github/workflows/publish.yml
vendored
@@ -177,22 +177,8 @@ jobs:
|
||||
cargo tauri --version
|
||||
|
||||
- name: Build and upload artifacts
|
||||
uses: Wandalen/wretry.action@v3
|
||||
timeout-minutes: 60
|
||||
with:
|
||||
attempt_limit: 3
|
||||
attempt_delay: 10000
|
||||
action: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
|
||||
with: |
|
||||
projectPath: packages/desktop
|
||||
uploadWorkflowArtifacts: true
|
||||
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
|
||||
args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
|
||||
updaterJsonPreferNsis: true
|
||||
releaseId: ${{ needs.publish.outputs.release }}
|
||||
tagName: ${{ needs.publish.outputs.tag }}
|
||||
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
||||
releaseDraft: true
|
||||
timeout-minutes: 20
|
||||
uses: tauri-apps/tauri-action@390cbe447412ced1303d35abe75287949e43437a
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_BUNDLER_NEW_APPIMAGE_FORMAT: true
|
||||
@@ -204,6 +190,16 @@ jobs:
|
||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||
APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
|
||||
with:
|
||||
projectPath: packages/desktop
|
||||
uploadWorkflowArtifacts: true
|
||||
tauriScript: ${{ (contains(matrix.settings.host, 'ubuntu') && 'cargo tauri') || '' }}
|
||||
args: --target ${{ matrix.settings.target }} --config ./src-tauri/tauri.prod.conf.json --verbose
|
||||
updaterJsonPreferNsis: true
|
||||
releaseId: ${{ needs.publish.outputs.release }}
|
||||
tagName: ${{ needs.publish.outputs.tag }}
|
||||
releaseAssetNamePattern: opencode-desktop-[platform]-[arch][ext]
|
||||
releaseDraft: true
|
||||
|
||||
publish-release:
|
||||
needs:
|
||||
|
||||
@@ -149,63 +149,11 @@ With that said, you may want to try these methods, as they might work for you.
|
||||
|
||||
## Pull Request Expectations
|
||||
|
||||
### Issue First Policy
|
||||
|
||||
**All PRs must reference an existing issue.** Before opening a PR, open an issue describing the bug or feature. This helps maintainers triage and prevents duplicate work. PRs without a linked issue may be closed without review.
|
||||
|
||||
- Use `Fixes #123` or `Closes #123` in your PR description to link the issue
|
||||
- For small fixes, a brief issue is fine - just enough context for maintainers to understand the problem
|
||||
|
||||
### General Requirements
|
||||
|
||||
- Keep pull requests small and focused
|
||||
- Try to keep pull requests small and focused.
|
||||
- Link relevant issue(s) in the description
|
||||
- Explain the issue and why your change fixes it
|
||||
- Before adding new functionality, ensure it doesn't already exist elsewhere in the codebase
|
||||
|
||||
### UI Changes
|
||||
|
||||
If your PR includes UI changes, please include screenshots or videos showing the before and after. This helps maintainers review faster and gives you quicker feedback.
|
||||
|
||||
### Logic Changes
|
||||
|
||||
For non-UI changes (bug fixes, new features, refactors), explain **how you verified it works**:
|
||||
|
||||
- What did you test?
|
||||
- How can a reviewer reproduce/confirm the fix?
|
||||
|
||||
### No AI-Generated Walls of Text
|
||||
|
||||
Long, AI-generated PR descriptions and issues are not acceptable and may be ignored. Respect the maintainers' time:
|
||||
|
||||
- Write short, focused descriptions
|
||||
- Explain what changed and why in your own words
|
||||
- If you can't explain it briefly, your PR might be too large
|
||||
|
||||
### PR Titles
|
||||
|
||||
PR titles should follow conventional commit standards:
|
||||
|
||||
- `feat:` new feature or functionality
|
||||
- `fix:` bug fix
|
||||
- `docs:` documentation or README changes
|
||||
- `chore:` maintenance tasks, dependency updates, etc.
|
||||
- `refactor:` code refactoring without changing behavior
|
||||
- `test:` adding or updating tests
|
||||
|
||||
You can optionally include a scope to indicate which package is affected:
|
||||
|
||||
- `feat(app):` feature in the app package
|
||||
- `fix(desktop):` bug fix in the desktop package
|
||||
- `chore(opencode):` maintenance in the opencode package
|
||||
|
||||
Examples:
|
||||
|
||||
- `docs: update contributing guidelines`
|
||||
- `fix: resolve crash on startup`
|
||||
- `feat: add dark mode support`
|
||||
- `feat(app): add dark mode support`
|
||||
- `fix(desktop): resolve crash on startup`
|
||||
- `chore: bump dependency versions`
|
||||
- Avoid having verbose LLM generated PR descriptions
|
||||
- Before adding new functions or functionality, ensure that such behavior doesn't already exist elsewhere in the codebase.
|
||||
|
||||
### Style Preferences
|
||||
|
||||
|
||||
1
STATS.md
1
STATS.md
@@ -194,4 +194,3 @@
|
||||
| 2026-01-05 | 1,738,171 (+65,515) | 1,353,043 (+13,160) | 3,091,214 (+78,675) |
|
||||
| 2026-01-06 | 1,960,988 (+222,817) | 1,377,377 (+24,334) | 3,338,365 (+247,151) |
|
||||
| 2026-01-07 | 2,123,239 (+162,251) | 1,398,648 (+21,271) | 3,521,887 (+183,522) |
|
||||
| 2026-01-08 | 2,272,630 (+149,391) | 1,432,480 (+33,832) | 3,705,110 (+183,223) |
|
||||
|
||||
50
bun.lock
50
bun.lock
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -70,7 +70,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -98,7 +98,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -125,7 +125,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.0",
|
||||
"@ai-sdk/openai": "2.0.2",
|
||||
@@ -149,7 +149,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -173,7 +173,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -202,7 +202,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -231,7 +231,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -247,7 +247,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -286,8 +286,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.5.2",
|
||||
"@opentui/core": "0.1.70",
|
||||
"@opentui/solid": "0.1.70",
|
||||
"@opentui/core": "0.1.69",
|
||||
"@opentui/solid": "0.1.69",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -350,7 +350,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -370,7 +370,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.88.1",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -381,7 +381,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -394,7 +394,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -433,7 +433,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
@@ -444,7 +444,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -1201,21 +1201,21 @@
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
|
||||
|
||||
"@opentui/core": ["@opentui/core@0.1.70", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.70", "@opentui/core-darwin-x64": "0.1.70", "@opentui/core-linux-arm64": "0.1.70", "@opentui/core-linux-x64": "0.1.70", "@opentui/core-win32-arm64": "0.1.70", "@opentui/core-win32-x64": "0.1.70", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-6cPAlbCnaiUUtQtvZNpkr0Xv8AdVAgJuy2VAwIsDN1pIv0zMpa0ZG+mr7afCGygw1eeDRveefrjfgFAB1r0SVw=="],
|
||||
"@opentui/core": ["@opentui/core@0.1.69", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.69", "@opentui/core-darwin-x64": "0.1.69", "@opentui/core-linux-arm64": "0.1.69", "@opentui/core-linux-x64": "0.1.69", "@opentui/core-win32-arm64": "0.1.69", "@opentui/core-win32-x64": "0.1.69", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-BcEFnAuMq4vgfb+zxOP/l+NO1AS3fVHkYjn+E8Wpmaxr0AzWNTi2NPAMtQf+Wqufxo0NYh0gY4c9B6n8OxTjGw=="],
|
||||
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.70", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rM8EnvW1tOAXWnp2Iy2M82I+ViSmRwUagx3v1/ni6N8GCcw/3mE0C6eB3sVlYNXVMwBEgiKpWFn85RCe4+qXQw=="],
|
||||
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.69", "", { "os": "darwin", "cpu": "arm64" }, "sha512-d9RPAh84O2XIyMw+7+X0fEyi+4KH5sPk9AxLze8GHRBGOzkRunqagFCLBrN5VFs2e2nbhIYtjMszo7gcpWyh7g=="],
|
||||
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.70", "", { "os": "darwin", "cpu": "x64" }, "sha512-XdBgW+em8J+YGSUpaKF8/NxPjikJygK3dIkeMAw5xQ2lt7jXKxeM5MMmN/V4MfK3pLMtO56rLJlXaLH/h50uQA=="],
|
||||
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.69", "", { "os": "darwin", "cpu": "x64" }, "sha512-41K9zkL2IG0ahL+8Gd+e9ulMrnJF6lArPzG7grjWzo+FWEZwvw0WLCO1/Gn5K85G8Yx7gQXkZOUaw1BmHjxoRw=="],
|
||||
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.70", "", { "os": "linux", "cpu": "arm64" }, "sha512-oSVWNMSOx0Na0M0LCqtWCxeh4SuLSK5lg8ZwVzsEoimIAxh0snp9nRUo/Qi8yD9BP0DSDmXuM/B3ONtzFaf0dw=="],
|
||||
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.69", "", { "os": "linux", "cpu": "arm64" }, "sha512-IcUjwjuIpX3BBG1a9kjMqWrHYCFHAVfjh5nIRozWZZoqaczLzJb3nJeF2eg8aDeIoGhXvERWB1r1gmqPW8u3vQ=="],
|
||||
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.70", "", { "os": "linux", "cpu": "x64" }, "sha512-WUrhukefMghcZ7sAjkxEy50vA6ii0X21xh7m8c4omXyYYfQXyDs25pNExB8cwoCrZEaC8RTlF4lRSNPIXsZKhA=="],
|
||||
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.69", "", { "os": "linux", "cpu": "x64" }, "sha512-5S9vqEIq7q+MEdp4cT0HLegBWu0pWLcletHZL80bsLbJt9OT8en3sQmL5bvas9sIuyeBFru9bfCmrQ/gnVTTiA=="],
|
||||
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.70", "", { "os": "win32", "cpu": "arm64" }, "sha512-p1K2VJXGmZqSV7mR61v7KJpT1Zth7DS99wEtaqqfK68OWt33K2XxLmGO0KD142R2JLfXu32NnRmBHxmVx8IjBA=="],
|
||||
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.69", "", { "os": "win32", "cpu": "arm64" }, "sha512-eSKcGwbcnJJPtrTFJI7STZ7inSYeedHS0swwjZhh9SADAruEz08intamunOslffv5+mnlvRp7UBGK35cMjbv/w=="],
|
||||
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.70", "", { "os": "win32", "cpu": "x64" }, "sha512-G6b8te1twMeDhjg1oZa0IcUjhOJZFCSdlQt+q5gu5vVtjCrIwAn9o7m5EwNMPakc31pDWUZ7v0ktgv0Xw1AQVA=="],
|
||||
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.69", "", { "os": "win32", "cpu": "x64" }, "sha512-OjG/0jqYXURqbbUwNgSPrBA6yuKF3OOFh8JSG7VvzoYHJFJRmwVWY0fztWv/hgGHe354ti37c7JDJBQ44HOCdA=="],
|
||||
|
||||
"@opentui/solid": ["@opentui/solid@0.1.70", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.70", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-8Cw/w4Of2OJhsFhcp/Wdj8cJRVaGvVsIiUoYiFtyToM01J4en0bg/vnbeZteyuZWeEtA4iz1/rSEQf7Dp+2FIQ=="],
|
||||
"@opentui/solid": ["@opentui/solid@0.1.69", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.69", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-ls589N8P9gvcNW8uF+Il4xisF5Uouk0RRmSaLdzmItNJSW5J9Y0nPtMELta6hBp0yIRAurWUO1wtkKXVF+eaxg=="],
|
||||
|
||||
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"nodeModules": "sha256-KjBAaI9Kv6huOmPvUbtyYsMhbScI91w1lOZyXpIWqI0="
|
||||
"nodeModules": "sha256-rNGq0yjL5ZHYVg+zyV4nFPug4gqhKhyOnfebaufyd34="
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -276,13 +276,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
visible(model: ModelKey) {
|
||||
const key = `${model.providerID}:${model.modelID}`
|
||||
const visibility = userVisibilityMap().get(key)
|
||||
if (visibility === "hide") return false
|
||||
if (visibility === "show") return true
|
||||
if (latestSet().has(key)) return true
|
||||
// For models without valid release_date (e.g. custom models), show by default
|
||||
const m = find(model)
|
||||
if (!m?.release_date || !DateTime.fromISO(m.release_date).isValid) return true
|
||||
return false
|
||||
return visibility !== "hide" && (latestSet().has(key) || visibility === "show")
|
||||
},
|
||||
setVisibility(model: ModelKey, visible: boolean) {
|
||||
updateVisibility(model, visible ? "show" : "hide")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.1.7"
|
||||
version = "1.1.6"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/anomalyco/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.7/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.6/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.7/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.6/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.7/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.6/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.7/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.6/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.7/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.1.6/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -81,8 +81,8 @@
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "1.5.2",
|
||||
"@opentui/core": "0.1.70",
|
||||
"@opentui/solid": "0.1.70",
|
||||
"@opentui/core": "0.1.69",
|
||||
"@opentui/solid": "0.1.69",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
|
||||
@@ -167,8 +167,6 @@ export namespace ACP {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: part.callID,
|
||||
status: "in_progress",
|
||||
kind: toToolKind(part.tool),
|
||||
title: part.tool,
|
||||
locations: toLocations(part.tool, part.state.input),
|
||||
rawInput: part.state.input,
|
||||
},
|
||||
@@ -244,7 +242,6 @@ export namespace ACP {
|
||||
kind,
|
||||
content,
|
||||
title: part.state.title,
|
||||
rawInput: part.state.input,
|
||||
rawOutput: {
|
||||
output: part.state.output,
|
||||
metadata: part.state.metadata,
|
||||
@@ -263,9 +260,6 @@ export namespace ACP {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: part.callID,
|
||||
status: "failed",
|
||||
kind: toToolKind(part.tool),
|
||||
title: part.tool,
|
||||
rawInput: part.state.input,
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
@@ -497,8 +491,6 @@ export namespace ACP {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: part.callID,
|
||||
status: "in_progress",
|
||||
kind: toToolKind(part.tool),
|
||||
title: part.tool,
|
||||
locations: toLocations(part.tool, part.state.input),
|
||||
rawInput: part.state.input,
|
||||
},
|
||||
@@ -574,7 +566,6 @@ export namespace ACP {
|
||||
kind,
|
||||
content,
|
||||
title: part.state.title,
|
||||
rawInput: part.state.input,
|
||||
rawOutput: {
|
||||
output: part.state.output,
|
||||
metadata: part.state.metadata,
|
||||
@@ -593,9 +584,6 @@ export namespace ACP {
|
||||
sessionUpdate: "tool_call_update",
|
||||
toolCallId: part.callID,
|
||||
status: "failed",
|
||||
kind: toToolKind(part.tool),
|
||||
title: part.tool,
|
||||
rawInput: part.state.input,
|
||||
content: [
|
||||
{
|
||||
type: "content",
|
||||
|
||||
@@ -209,21 +209,6 @@ export namespace Agent {
|
||||
item.options = mergeDeep(item.options, value.options ?? {})
|
||||
item.permission = PermissionNext.merge(item.permission, PermissionNext.fromConfig(value.permission ?? {}))
|
||||
}
|
||||
|
||||
// Ensure Truncate.DIR is allowed unless explicitly configured
|
||||
for (const name in result) {
|
||||
const agent = result[name]
|
||||
const explicit = agent.permission.some(
|
||||
(r) => r.permission === "external_directory" && r.pattern === Truncate.DIR && r.action === "deny",
|
||||
)
|
||||
if (explicit) continue
|
||||
|
||||
result[name].permission = PermissionNext.merge(
|
||||
result[name].permission,
|
||||
PermissionNext.fromConfig({ external_directory: { [Truncate.DIR]: "allow" } }),
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { cmd } from "../cmd"
|
||||
|
||||
export const AgentCommand = cmd({
|
||||
command: "agent <name>",
|
||||
describe: "show agent configuration details",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("name", {
|
||||
type: "string",
|
||||
|
||||
@@ -5,7 +5,6 @@ import { cmd } from "../cmd"
|
||||
|
||||
export const ConfigCommand = cmd({
|
||||
command: "config",
|
||||
describe: "show resolved configuration",
|
||||
builder: (yargs) => yargs,
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Ripgrep } from "@/file/ripgrep"
|
||||
|
||||
const FileSearchCommand = cmd({
|
||||
command: "search <query>",
|
||||
describe: "search files by query",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("query", {
|
||||
type: "string",
|
||||
@@ -23,7 +22,6 @@ const FileSearchCommand = cmd({
|
||||
|
||||
const FileReadCommand = cmd({
|
||||
command: "read <path>",
|
||||
describe: "read file contents as JSON",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("path", {
|
||||
type: "string",
|
||||
@@ -40,7 +38,6 @@ const FileReadCommand = cmd({
|
||||
|
||||
const FileStatusCommand = cmd({
|
||||
command: "status",
|
||||
describe: "show file status information",
|
||||
builder: (yargs) => yargs,
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
@@ -52,7 +49,6 @@ const FileStatusCommand = cmd({
|
||||
|
||||
const FileListCommand = cmd({
|
||||
command: "list <path>",
|
||||
describe: "list files in a directory",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("path", {
|
||||
type: "string",
|
||||
@@ -69,7 +65,6 @@ const FileListCommand = cmd({
|
||||
|
||||
const FileTreeCommand = cmd({
|
||||
command: "tree [dir]",
|
||||
describe: "show directory tree",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("dir", {
|
||||
type: "string",
|
||||
@@ -84,7 +79,6 @@ const FileTreeCommand = cmd({
|
||||
|
||||
export const FileCommand = cmd({
|
||||
command: "file",
|
||||
describe: "file system debugging utilities",
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.command(FileReadCommand)
|
||||
|
||||
@@ -12,7 +12,6 @@ import { AgentCommand } from "./agent"
|
||||
|
||||
export const DebugCommand = cmd({
|
||||
command: "debug",
|
||||
describe: "debugging and troubleshooting tools",
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.command(ConfigCommand)
|
||||
@@ -26,7 +25,6 @@ export const DebugCommand = cmd({
|
||||
.command(PathsCommand)
|
||||
.command({
|
||||
command: "wait",
|
||||
describe: "wait indefinitely (for debugging)",
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_000 * 60 * 60 * 24))
|
||||
@@ -39,7 +37,6 @@ export const DebugCommand = cmd({
|
||||
|
||||
const PathsCommand = cmd({
|
||||
command: "paths",
|
||||
describe: "show global paths (data, config, cache, state)",
|
||||
handler() {
|
||||
for (const [key, value] of Object.entries(Global.Path)) {
|
||||
console.log(key.padEnd(10), value)
|
||||
|
||||
@@ -6,7 +6,6 @@ import { EOL } from "os"
|
||||
|
||||
export const LSPCommand = cmd({
|
||||
command: "lsp",
|
||||
describe: "LSP debugging utilities",
|
||||
builder: (yargs) =>
|
||||
yargs.command(DiagnosticsCommand).command(SymbolsCommand).command(DocumentSymbolsCommand).demandCommand(),
|
||||
async handler() {},
|
||||
@@ -14,7 +13,6 @@ export const LSPCommand = cmd({
|
||||
|
||||
const DiagnosticsCommand = cmd({
|
||||
command: "diagnostics <file>",
|
||||
describe: "get diagnostics for a file",
|
||||
builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
@@ -27,7 +25,6 @@ const DiagnosticsCommand = cmd({
|
||||
|
||||
export const SymbolsCommand = cmd({
|
||||
command: "symbols <query>",
|
||||
describe: "search workspace symbols",
|
||||
builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
@@ -40,7 +37,6 @@ export const SymbolsCommand = cmd({
|
||||
|
||||
export const DocumentSymbolsCommand = cmd({
|
||||
command: "document-symbols <uri>",
|
||||
describe: "get symbols from a document",
|
||||
builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }),
|
||||
async handler(args) {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
|
||||
@@ -6,14 +6,12 @@ import { cmd } from "../cmd"
|
||||
|
||||
export const RipgrepCommand = cmd({
|
||||
command: "rg",
|
||||
describe: "ripgrep debugging utilities",
|
||||
builder: (yargs) => yargs.command(TreeCommand).command(FilesCommand).command(SearchCommand).demandCommand(),
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
const TreeCommand = cmd({
|
||||
command: "tree",
|
||||
describe: "show file tree using ripgrep",
|
||||
builder: (yargs) =>
|
||||
yargs.option("limit", {
|
||||
type: "number",
|
||||
@@ -27,7 +25,6 @@ const TreeCommand = cmd({
|
||||
|
||||
const FilesCommand = cmd({
|
||||
command: "files",
|
||||
describe: "list files using ripgrep",
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.option("query", {
|
||||
@@ -59,7 +56,6 @@ const FilesCommand = cmd({
|
||||
|
||||
const SearchCommand = cmd({
|
||||
command: "search <pattern>",
|
||||
describe: "search file contents using ripgrep",
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional("pattern", {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { cmd } from "../cmd"
|
||||
|
||||
export const ScrapCommand = cmd({
|
||||
command: "scrap",
|
||||
describe: "list all known projects",
|
||||
builder: (yargs) => yargs,
|
||||
async handler() {
|
||||
const timer = Log.Default.time("scrap")
|
||||
|
||||
@@ -5,7 +5,6 @@ import { cmd } from "../cmd"
|
||||
|
||||
export const SkillCommand = cmd({
|
||||
command: "skill",
|
||||
describe: "list all available skills",
|
||||
builder: (yargs) => yargs,
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
|
||||
@@ -4,14 +4,12 @@ import { cmd } from "../cmd"
|
||||
|
||||
export const SnapshotCommand = cmd({
|
||||
command: "snapshot",
|
||||
describe: "snapshot debugging utilities",
|
||||
builder: (yargs) => yargs.command(TrackCommand).command(PatchCommand).command(DiffCommand).demandCommand(),
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
const TrackCommand = cmd({
|
||||
command: "track",
|
||||
describe: "track current snapshot state",
|
||||
async handler() {
|
||||
await bootstrap(process.cwd(), async () => {
|
||||
console.log(await Snapshot.track())
|
||||
@@ -21,7 +19,6 @@ const TrackCommand = cmd({
|
||||
|
||||
const PatchCommand = cmd({
|
||||
command: "patch <hash>",
|
||||
describe: "show patch for a snapshot hash",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("hash", {
|
||||
type: "string",
|
||||
@@ -37,7 +34,6 @@ const PatchCommand = cmd({
|
||||
|
||||
const DiffCommand = cmd({
|
||||
command: "diff <hash>",
|
||||
describe: "show diff for a snapshot hash",
|
||||
builder: (yargs) =>
|
||||
yargs.positional("hash", {
|
||||
type: "string",
|
||||
|
||||
@@ -653,17 +653,9 @@ function ErrorComponent(props: {
|
||||
mode?: "dark" | "light"
|
||||
}) {
|
||||
const term = useTerminalDimensions()
|
||||
const renderer = useRenderer()
|
||||
|
||||
const handleExit = async () => {
|
||||
renderer.setTerminalTitle("")
|
||||
renderer.destroy()
|
||||
props.onExit()
|
||||
}
|
||||
|
||||
useKeyboard((evt) => {
|
||||
if (evt.ctrl && evt.name === "c") {
|
||||
handleExit()
|
||||
props.onExit()
|
||||
}
|
||||
})
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
@@ -716,7 +708,7 @@ function ErrorComponent(props: {
|
||||
<box onMouseUp={props.reset} backgroundColor={colors.primary} padding={1}>
|
||||
<text fg={colors.bg}>Reset TUI</text>
|
||||
</box>
|
||||
<box onMouseUp={handleExit} backgroundColor={colors.primary} padding={1}>
|
||||
<box onMouseUp={props.onExit} backgroundColor={colors.primary} padding={1}>
|
||||
<text fg={colors.bg}>Exit</text>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
@@ -53,7 +53,6 @@ export type AutocompleteRef = {
|
||||
|
||||
export type AutocompleteOption = {
|
||||
display: string
|
||||
value?: string
|
||||
aliases?: string[]
|
||||
disabled?: boolean
|
||||
description?: string
|
||||
@@ -222,7 +221,6 @@ export function Autocomplete(props: {
|
||||
const isDir = item.endsWith("/")
|
||||
return {
|
||||
display: Locale.truncateMiddle(filename, width),
|
||||
value: filename,
|
||||
isDirectory: isDir,
|
||||
path: item,
|
||||
onSelect: () => {
|
||||
@@ -261,10 +259,8 @@ export function Autocomplete(props: {
|
||||
const width = props.anchor().width - 4
|
||||
|
||||
for (const res of Object.values(sync.data.mcp_resource)) {
|
||||
const text = `${res.name} (${res.uri})`
|
||||
options.push({
|
||||
display: Locale.truncateMiddle(text, width),
|
||||
value: text,
|
||||
display: Locale.truncateMiddle(`${res.name} (${res.uri})`, width),
|
||||
description: res.description,
|
||||
onSelect: () => {
|
||||
insertPart(res.name, {
|
||||
@@ -489,11 +485,7 @@ export function Autocomplete(props: {
|
||||
}
|
||||
|
||||
const result = fuzzysort.go(removeLineRange(currentFilter), mixed, {
|
||||
keys: [
|
||||
(obj) => removeLineRange((obj.value ?? obj.display).trimEnd()),
|
||||
"description",
|
||||
(obj) => obj.aliases?.join(" ") ?? "",
|
||||
],
|
||||
keys: [(obj) => removeLineRange(obj.display.trimEnd()), "description", (obj) => obj.aliases?.join(" ") ?? ""],
|
||||
limit: 10,
|
||||
scoreFn: (objResults) => {
|
||||
const displayResult = objResults[0]
|
||||
|
||||
@@ -26,7 +26,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
|
||||
return ready()
|
||||
},
|
||||
signal<T>(name: string, defaultValue: T) {
|
||||
if (kvStore[name] === undefined) setKvStore(name, defaultValue)
|
||||
if (!kvStore[name]) setKvStore(name, defaultValue)
|
||||
return [
|
||||
function () {
|
||||
return result.get(name)
|
||||
|
||||
@@ -3,8 +3,9 @@ import { useRouteData } from "@tui/context/route"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { pipe, sumBy } from "remeda"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { SplitBorder, EmptyBorder } from "@tui/component/border"
|
||||
import type { AssistantMessage, Session } from "@opencode-ai/sdk/v2"
|
||||
import { useDirectory } from "../../context/directory"
|
||||
import { useKeybind } from "../../context/keybind"
|
||||
|
||||
const Title = (props: { session: Accessor<Session> }) => {
|
||||
@@ -32,6 +33,7 @@ export function Header() {
|
||||
const sync = useSync()
|
||||
const session = createMemo(() => sync.session.get(route.sessionID)!)
|
||||
const messages = createMemo(() => sync.data.message[route.sessionID] ?? [])
|
||||
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
|
||||
|
||||
const cost = createMemo(() => {
|
||||
const total = pipe(
|
||||
@@ -97,6 +99,24 @@ export function Header() {
|
||||
<Title session={session} />
|
||||
<ContextInfo context={context} cost={cost} />
|
||||
</box>
|
||||
<Show when={shareEnabled()}>
|
||||
<box flexDirection="row" justifyContent="space-between" gap={1}>
|
||||
<box flexGrow={1} flexShrink={1}>
|
||||
<Switch>
|
||||
<Match when={session().share?.url}>
|
||||
<text fg={theme.textMuted} wrapMode="word">
|
||||
{session().share!.url}
|
||||
</text>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<text fg={theme.text} wrapMode="word">
|
||||
/share <span style={{ fg: theme.textMuted }}>copy link</span>
|
||||
</text>
|
||||
</Match>
|
||||
</Switch>
|
||||
</box>
|
||||
</box>
|
||||
</Show>
|
||||
</Match>
|
||||
</Switch>
|
||||
</box>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
batch,
|
||||
createContext,
|
||||
createEffect,
|
||||
createMemo,
|
||||
@@ -53,6 +52,7 @@ import { useDialog } from "../../ui/dialog"
|
||||
import { TodoItem } from "../../component/todo-item"
|
||||
import { DialogMessage } from "./dialog-message"
|
||||
import type { PromptInfo } from "../../component/prompt/history"
|
||||
import { iife } from "@/util/iife"
|
||||
import { DialogConfirm } from "@tui/ui/dialog-confirm"
|
||||
import { DialogTimeline } from "./dialog-timeline"
|
||||
import { DialogForkFromTimeline } from "./dialog-fork-from-timeline"
|
||||
@@ -92,6 +92,7 @@ const context = createContext<{
|
||||
conceal: () => boolean
|
||||
showThinking: () => boolean
|
||||
showTimestamps: () => boolean
|
||||
usernameVisible: () => boolean
|
||||
showDetails: () => boolean
|
||||
diffWrapMode: () => "word" | "none"
|
||||
sync: ReturnType<typeof useSync>
|
||||
@@ -136,25 +137,24 @@ export function Session() {
|
||||
})
|
||||
|
||||
const dimensions = useTerminalDimensions()
|
||||
const [sidebar, setSidebar] = kv.signal<"auto" | "hide">("sidebar", "hide")
|
||||
const [sidebarOpen, setSidebarOpen] = createSignal(false)
|
||||
const [sidebar, setSidebar] = createSignal<"show" | "hide" | "auto">(kv.get("sidebar", "auto"))
|
||||
const [conceal, setConceal] = createSignal(true)
|
||||
const [showThinking, setShowThinking] = kv.signal("thinking_visibility", true)
|
||||
const [timestamps, setTimestamps] = kv.signal<"hide" | "show">("timestamps", "hide")
|
||||
const [showDetails, setShowDetails] = kv.signal("tool_details_visibility", true)
|
||||
const [showAssistantMetadata, setShowAssistantMetadata] = kv.signal("assistant_metadata_visibility", true)
|
||||
const [showScrollbar, setShowScrollbar] = kv.signal("scrollbar_visible", false)
|
||||
const [showThinking, setShowThinking] = createSignal(kv.get("thinking_visibility", true))
|
||||
const [showTimestamps, setShowTimestamps] = createSignal(kv.get("timestamps", "hide") === "show")
|
||||
const [usernameVisible, setUsernameVisible] = createSignal(kv.get("username_visible", true))
|
||||
const [showDetails, setShowDetails] = createSignal(kv.get("tool_details_visibility", true))
|
||||
const [showAssistantMetadata, setShowAssistantMetadata] = createSignal(kv.get("assistant_metadata_visibility", true))
|
||||
const [showScrollbar, setShowScrollbar] = createSignal(kv.get("scrollbar_visible", false))
|
||||
const [diffWrapMode, setDiffWrapMode] = createSignal<"word" | "none">("word")
|
||||
const [animationsEnabled, setAnimationsEnabled] = kv.signal("animations_enabled", true)
|
||||
const [animationsEnabled, setAnimationsEnabled] = createSignal(kv.get("animations_enabled", true))
|
||||
|
||||
const wide = createMemo(() => dimensions().width > 120)
|
||||
const sidebarVisible = createMemo(() => {
|
||||
if (session()?.parentID) return false
|
||||
if (sidebarOpen()) return true
|
||||
if (sidebar() === "show") return true
|
||||
if (sidebar() === "auto" && wide()) return true
|
||||
return false
|
||||
})
|
||||
const showTimestamps = createMemo(() => timestamps() === "show")
|
||||
const contentWidth = createMemo(() => dimensions().width - (sidebarVisible() ? 42 : 0) - 4)
|
||||
|
||||
const scrollAcceleration = createMemo(() => {
|
||||
@@ -455,10 +455,26 @@ export function Session() {
|
||||
keybind: "sidebar_toggle",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
batch(() => {
|
||||
const isVisible = sidebarVisible()
|
||||
setSidebar(() => (isVisible ? "hide" : "auto"))
|
||||
setSidebarOpen(!isVisible)
|
||||
setSidebar((prev) => {
|
||||
if (prev === "auto") return sidebarVisible() ? "hide" : "show"
|
||||
if (prev === "show") return "hide"
|
||||
return "show"
|
||||
})
|
||||
if (sidebar() === "show") kv.set("sidebar", "auto")
|
||||
if (sidebar() === "hide") kv.set("sidebar", "hide")
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
{
|
||||
title: usernameVisible() ? "Hide username" : "Show username",
|
||||
value: "session.username_visible.toggle",
|
||||
keybind: "username_toggle",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
setUsernameVisible((prev) => {
|
||||
const next = !prev
|
||||
kv.set("username_visible", next)
|
||||
return next
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
@@ -478,7 +494,11 @@ export function Session() {
|
||||
value: "session.toggle.timestamps",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
setTimestamps((prev) => (prev === "show" ? "hide" : "show"))
|
||||
setShowTimestamps((prev) => {
|
||||
const next = !prev
|
||||
kv.set("timestamps", next ? "show" : "hide")
|
||||
return next
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
@@ -487,7 +507,11 @@ export function Session() {
|
||||
value: "session.toggle.thinking",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
setShowThinking((prev) => !prev)
|
||||
setShowThinking((prev) => {
|
||||
const next = !prev
|
||||
kv.set("thinking_visibility", next)
|
||||
return next
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
@@ -506,7 +530,9 @@ export function Session() {
|
||||
keybind: "tool_details",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
setShowDetails((prev) => !prev)
|
||||
const newValue = !showDetails()
|
||||
setShowDetails(newValue)
|
||||
kv.set("tool_details_visibility", newValue)
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
@@ -516,7 +542,11 @@ export function Session() {
|
||||
keybind: "scrollbar_toggle",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
setShowScrollbar((prev) => !prev)
|
||||
setShowScrollbar((prev) => {
|
||||
const next = !prev
|
||||
kv.set("scrollbar_visible", next)
|
||||
return next
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
@@ -525,7 +555,11 @@ export function Session() {
|
||||
value: "session.toggle.animations",
|
||||
category: "Session",
|
||||
onSelect: (dialog) => {
|
||||
setAnimationsEnabled((prev) => !prev)
|
||||
setAnimationsEnabled((prev) => {
|
||||
const next = !prev
|
||||
kv.set("animations_enabled", next)
|
||||
return next
|
||||
})
|
||||
dialog.clear()
|
||||
},
|
||||
},
|
||||
@@ -879,6 +913,7 @@ export function Session() {
|
||||
conceal,
|
||||
showThinking,
|
||||
showTimestamps,
|
||||
usernameVisible,
|
||||
showDetails,
|
||||
diffWrapMode,
|
||||
sync,
|
||||
@@ -887,7 +922,7 @@ export function Session() {
|
||||
<box flexDirection="row">
|
||||
<box flexGrow={1} paddingBottom={1} paddingTop={1} paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<Show when={session()}>
|
||||
<Show when={!sidebarVisible() || !wide()}>
|
||||
<Show when={!sidebarVisible()}>
|
||||
<Header />
|
||||
</Show>
|
||||
<scrollbox
|
||||
@@ -1028,28 +1063,14 @@ export function Session() {
|
||||
sessionID={route.sessionID}
|
||||
/>
|
||||
</box>
|
||||
<Show when={!sidebarVisible()}>
|
||||
<Footer />
|
||||
</Show>
|
||||
</Show>
|
||||
<Toast />
|
||||
</box>
|
||||
<Show when={sidebarVisible()}>
|
||||
<Switch>
|
||||
<Match when={wide()}>
|
||||
<Sidebar sessionID={route.sessionID} />
|
||||
</Match>
|
||||
<Match when={!wide()}>
|
||||
<box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
alignItems="flex-end"
|
||||
backgroundColor={RGBA.fromInts(0, 0, 0, 70)}
|
||||
>
|
||||
<Sidebar sessionID={route.sessionID} />
|
||||
</box>
|
||||
</Match>
|
||||
</Switch>
|
||||
<Sidebar sessionID={route.sessionID} />
|
||||
</Show>
|
||||
</box>
|
||||
</context.Provider>
|
||||
@@ -1082,7 +1103,6 @@ function UserMessage(props: {
|
||||
const [hover, setHover] = createSignal(false)
|
||||
const queued = createMemo(() => props.pending && props.message.id > props.pending)
|
||||
const color = createMemo(() => (queued() ? theme.accent : local.agent.color(props.message.agent)))
|
||||
const metadataVisible = createMemo(() => queued() || ctx.showTimestamps())
|
||||
|
||||
const compaction = createMemo(() => props.parts.find((x) => x.type === "compaction"))
|
||||
|
||||
@@ -1112,7 +1132,7 @@ function UserMessage(props: {
|
||||
>
|
||||
<text fg={theme.text}>{text()?.text}</text>
|
||||
<Show when={files().length}>
|
||||
<box flexDirection="row" paddingBottom={metadataVisible() ? 1 : 0} paddingTop={1} gap={1} flexWrap="wrap">
|
||||
<box flexDirection="row" paddingBottom={1} paddingTop={1} gap={1} flexWrap="wrap">
|
||||
<For each={files()}>
|
||||
{(file) => {
|
||||
const bg = createMemo(() => {
|
||||
@@ -1130,22 +1150,23 @@ function UserMessage(props: {
|
||||
</For>
|
||||
</box>
|
||||
</Show>
|
||||
<Show
|
||||
when={queued()}
|
||||
fallback={
|
||||
<Show when={ctx.showTimestamps()}>
|
||||
<text fg={theme.textMuted}>
|
||||
<text fg={theme.textMuted}>
|
||||
{ctx.usernameVisible() ? `${sync.data.config.username ?? "You "}` : "You "}
|
||||
<Show
|
||||
when={queued()}
|
||||
fallback={
|
||||
<Show when={ctx.showTimestamps()}>
|
||||
<span style={{ fg: theme.textMuted }}>
|
||||
{ctx.usernameVisible() ? " · " : " "}
|
||||
{Locale.todayTimeOrDateTime(props.message.time.created)}
|
||||
</span>
|
||||
</text>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<text fg={theme.textMuted}>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<span> </span>
|
||||
<span style={{ bg: theme.accent, fg: theme.backgroundPanel, bold: true }}> QUEUED </span>
|
||||
</text>
|
||||
</Show>
|
||||
</Show>
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
</Show>
|
||||
@@ -1303,16 +1324,8 @@ function TextPart(props: { last: boolean; part: TextPart; message: AssistantMess
|
||||
// Pending messages moved to individual tool pending functions
|
||||
|
||||
function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMessage }) {
|
||||
const ctx = use()
|
||||
const sync = useSync()
|
||||
|
||||
// Hide tool if showDetails is false and tool completed successfully
|
||||
const shouldHide = createMemo(() => {
|
||||
if (ctx.showDetails()) return false
|
||||
if (props.part.state.status !== "completed") return false
|
||||
return true
|
||||
})
|
||||
|
||||
const toolprops = {
|
||||
get metadata() {
|
||||
return props.part.state.status === "pending" ? {} : (props.part.state.metadata ?? {})
|
||||
@@ -1337,55 +1350,53 @@ function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMess
|
||||
}
|
||||
|
||||
return (
|
||||
<Show when={!shouldHide()}>
|
||||
<Switch>
|
||||
<Match when={props.part.tool === "bash"}>
|
||||
<Bash {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "glob"}>
|
||||
<Glob {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "read"}>
|
||||
<Read {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "grep"}>
|
||||
<Grep {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "list"}>
|
||||
<List {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "webfetch"}>
|
||||
<WebFetch {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "codesearch"}>
|
||||
<CodeSearch {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "websearch"}>
|
||||
<WebSearch {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "write"}>
|
||||
<Write {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "edit"}>
|
||||
<Edit {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "task"}>
|
||||
<Task {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "patch"}>
|
||||
<Patch {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "todowrite"}>
|
||||
<TodoWrite {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "question"}>
|
||||
<Question {...toolprops} />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<GenericTool {...toolprops} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
<Switch>
|
||||
<Match when={props.part.tool === "bash"}>
|
||||
<Bash {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "glob"}>
|
||||
<Glob {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "read"}>
|
||||
<Read {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "grep"}>
|
||||
<Grep {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "list"}>
|
||||
<List {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "webfetch"}>
|
||||
<WebFetch {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "codesearch"}>
|
||||
<CodeSearch {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "websearch"}>
|
||||
<WebSearch {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "write"}>
|
||||
<Write {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "edit"}>
|
||||
<Edit {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "task"}>
|
||||
<Task {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "patch"}>
|
||||
<Patch {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "todowrite"}>
|
||||
<TodoWrite {...toolprops} />
|
||||
</Match>
|
||||
<Match when={props.part.tool === "question"}>
|
||||
<Question {...toolprops} />
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<GenericTool {...toolprops} />
|
||||
</Match>
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1524,30 +1535,15 @@ function BlockTool(props: { title: string; children: JSX.Element; onClick?: () =
|
||||
}
|
||||
|
||||
function Bash(props: ToolProps<typeof BashTool>) {
|
||||
const { theme } = useTheme()
|
||||
const output = createMemo(() => stripAnsi(props.metadata.output?.trim() ?? ""))
|
||||
const [expanded, setExpanded] = createSignal(false)
|
||||
const lines = createMemo(() => output().split("\n"))
|
||||
const overflow = createMemo(() => lines().length > 10)
|
||||
const limited = createMemo(() => {
|
||||
if (expanded() || !overflow()) return output()
|
||||
return [...lines().slice(0, 10), "…"].join("\n")
|
||||
})
|
||||
|
||||
const { theme } = useTheme()
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={props.metadata.output !== undefined}>
|
||||
<BlockTool
|
||||
title={"# " + (props.input.description ?? "Shell")}
|
||||
part={props.part}
|
||||
onClick={overflow() ? () => setExpanded((prev) => !prev) : undefined}
|
||||
>
|
||||
<BlockTool title={"# " + (props.input.description ?? "Shell")} part={props.part}>
|
||||
<box gap={1}>
|
||||
<text fg={theme.text}>$ {props.input.command}</text>
|
||||
<text fg={theme.text}>{limited()}</text>
|
||||
<Show when={overflow()}>
|
||||
<text fg={theme.textMuted}>{expanded() ? "Click to collapse" : "Click to expand"}</text>
|
||||
</Show>
|
||||
<text fg={theme.text}>{output()}</text>
|
||||
</box>
|
||||
</BlockTool>
|
||||
</Match>
|
||||
@@ -1840,12 +1836,6 @@ function TodoWrite(props: ToolProps<typeof TodoWriteTool>) {
|
||||
function Question(props: ToolProps<typeof QuestionTool>) {
|
||||
const { theme } = useTheme()
|
||||
const count = createMemo(() => props.input.questions?.length ?? 0)
|
||||
|
||||
function format(answer?: string[]) {
|
||||
if (!answer?.length) return "(no answer)"
|
||||
return answer.join(", ")
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={props.metadata.answers}>
|
||||
@@ -1855,7 +1845,7 @@ function Question(props: ToolProps<typeof QuestionTool>) {
|
||||
{(q, i) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text fg={theme.textMuted}>{q.question}</text>
|
||||
<text fg={theme.text}>{format(props.metadata.answers?.[i()])}</text>
|
||||
<text fg={theme.text}>{props.metadata.answers?.[i()] || "(no answer)"}</text>
|
||||
</box>
|
||||
)}
|
||||
</For>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useKeyboard } from "@opentui/solid"
|
||||
import type { TextareaRenderable } from "@opentui/core"
|
||||
import { useKeybind } from "../../context/keybind"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/sdk/v2"
|
||||
import type { QuestionRequest } from "@opencode-ai/sdk/v2"
|
||||
import { useSDK } from "../../context/sdk"
|
||||
import { SplitBorder } from "../../component/border"
|
||||
import { useTextareaKeybindings } from "../../component/textarea-keybindings"
|
||||
@@ -17,11 +17,11 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
const bindings = useTextareaKeybindings()
|
||||
|
||||
const questions = createMemo(() => props.request.questions)
|
||||
const single = createMemo(() => questions().length === 1 && questions()[0]?.multiple !== true)
|
||||
const tabs = createMemo(() => (single() ? 1 : questions().length + 1)) // questions + confirm tab (no confirm for single select)
|
||||
const single = createMemo(() => questions().length === 1)
|
||||
const tabs = createMemo(() => (single() ? 1 : questions().length + 1)) // questions + confirm tab (no confirm for single)
|
||||
const [store, setStore] = createStore({
|
||||
tab: 0,
|
||||
answers: [] as QuestionAnswer[],
|
||||
answers: [] as string[],
|
||||
custom: [] as string[],
|
||||
selected: 0,
|
||||
editing: false,
|
||||
@@ -34,15 +34,10 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
const options = createMemo(() => question()?.options ?? [])
|
||||
const other = createMemo(() => store.selected === options().length)
|
||||
const input = createMemo(() => store.custom[store.tab] ?? "")
|
||||
const multi = createMemo(() => question()?.multiple === true)
|
||||
const customPicked = createMemo(() => {
|
||||
const value = input()
|
||||
if (!value) return false
|
||||
return store.answers[store.tab]?.includes(value) ?? false
|
||||
})
|
||||
|
||||
function submit() {
|
||||
const answers = questions().map((_, i) => store.answers[i] ?? [])
|
||||
// Fill in empty answers with empty strings
|
||||
const answers = questions().map((_, i) => store.answers[i] ?? "")
|
||||
sdk.client.question.reply({
|
||||
requestID: props.request.id,
|
||||
answers,
|
||||
@@ -57,7 +52,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
|
||||
function pick(answer: string, custom: boolean = false) {
|
||||
const answers = [...store.answers]
|
||||
answers[store.tab] = [answer]
|
||||
answers[store.tab] = answer
|
||||
setStore("answers", answers)
|
||||
if (custom) {
|
||||
const inputs = [...store.custom]
|
||||
@@ -67,7 +62,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
if (single()) {
|
||||
sdk.client.question.reply({
|
||||
requestID: props.request.id,
|
||||
answers: [[answer]],
|
||||
answers: [answer],
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -75,17 +70,6 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
setStore("selected", 0)
|
||||
}
|
||||
|
||||
function toggle(answer: string) {
|
||||
const existing = store.answers[store.tab] ?? []
|
||||
const next = [...existing]
|
||||
const index = next.indexOf(answer)
|
||||
if (index === -1) next.push(answer)
|
||||
if (index !== -1) next.splice(index, 1)
|
||||
const answers = [...store.answers]
|
||||
answers[store.tab] = next
|
||||
setStore("answers", answers)
|
||||
}
|
||||
|
||||
const dialog = useDialog()
|
||||
|
||||
useKeyboard((evt) => {
|
||||
@@ -98,49 +82,11 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
}
|
||||
if (evt.name === "return") {
|
||||
evt.preventDefault()
|
||||
const text = textarea?.plainText?.trim() ?? ""
|
||||
const prev = store.custom[store.tab]
|
||||
|
||||
if (!text) {
|
||||
if (prev) {
|
||||
const inputs = [...store.custom]
|
||||
inputs[store.tab] = ""
|
||||
setStore("custom", inputs)
|
||||
}
|
||||
|
||||
const answers = [...store.answers]
|
||||
if (prev) {
|
||||
answers[store.tab] = (answers[store.tab] ?? []).filter((x) => x !== prev)
|
||||
}
|
||||
if (!prev) {
|
||||
answers[store.tab] = []
|
||||
}
|
||||
setStore("answers", answers)
|
||||
const text = textarea?.plainText?.trim()
|
||||
if (text) {
|
||||
pick(text, true)
|
||||
setStore("editing", false)
|
||||
return
|
||||
}
|
||||
|
||||
if (multi()) {
|
||||
const inputs = [...store.custom]
|
||||
inputs[store.tab] = text
|
||||
setStore("custom", inputs)
|
||||
|
||||
const existing = store.answers[store.tab] ?? []
|
||||
const next = [...existing]
|
||||
if (prev) {
|
||||
const index = next.indexOf(prev)
|
||||
if (index !== -1) next.splice(index, 1)
|
||||
}
|
||||
if (!next.includes(text)) next.push(text)
|
||||
const answers = [...store.answers]
|
||||
answers[store.tab] = next
|
||||
setStore("answers", answers)
|
||||
setStore("editing", false)
|
||||
return
|
||||
}
|
||||
|
||||
pick(text, true)
|
||||
setStore("editing", false)
|
||||
return
|
||||
}
|
||||
// Let textarea handle all other keys
|
||||
@@ -187,25 +133,13 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
if (evt.name === "return") {
|
||||
evt.preventDefault()
|
||||
if (other()) {
|
||||
if (!multi()) {
|
||||
setStore("editing", true)
|
||||
return
|
||||
}
|
||||
const value = input()
|
||||
if (value && customPicked()) {
|
||||
toggle(value)
|
||||
return
|
||||
}
|
||||
setStore("editing", true)
|
||||
return
|
||||
} else {
|
||||
const opt = opts[store.selected]
|
||||
if (opt) {
|
||||
pick(opt.label)
|
||||
}
|
||||
}
|
||||
const opt = opts[store.selected]
|
||||
if (!opt) return
|
||||
if (multi()) {
|
||||
toggle(opt.label)
|
||||
return
|
||||
}
|
||||
pick(opt.label)
|
||||
}
|
||||
|
||||
if (evt.name === "escape" || keybind.match("app_exit", evt)) {
|
||||
@@ -228,9 +162,7 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
<For each={questions()}>
|
||||
{(q, index) => {
|
||||
const isActive = () => index() === store.tab
|
||||
const isAnswered = () => {
|
||||
return (store.answers[index()]?.length ?? 0) > 0
|
||||
}
|
||||
const isAnswered = () => store.answers[index()] !== undefined
|
||||
return (
|
||||
<box
|
||||
paddingLeft={1}
|
||||
@@ -253,16 +185,13 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
<Show when={!confirm()}>
|
||||
<box paddingLeft={1} gap={1}>
|
||||
<box>
|
||||
<text fg={theme.text}>
|
||||
{question()?.question}
|
||||
{multi() ? " (select all that apply)" : ""}
|
||||
</text>
|
||||
<text fg={theme.text}>{question()?.question}</text>
|
||||
</box>
|
||||
<box>
|
||||
<For each={options()}>
|
||||
{(opt, i) => {
|
||||
const active = () => i() === store.selected
|
||||
const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
|
||||
const picked = () => store.answers[store.tab] === opt.label
|
||||
return (
|
||||
<box>
|
||||
<box flexDirection="row" gap={1}>
|
||||
@@ -283,30 +212,25 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
<box>
|
||||
<box flexDirection="row" gap={1}>
|
||||
<box backgroundColor={other() ? theme.backgroundElement : undefined}>
|
||||
<text fg={other() ? theme.secondary : customPicked() ? theme.success : theme.text}>
|
||||
{options().length + 1}. Type your own answer
|
||||
<text fg={other() ? theme.secondary : input() ? theme.success : theme.text}>
|
||||
{options().length + 1}. Other
|
||||
</text>
|
||||
</box>
|
||||
<text fg={theme.success}>{customPicked() ? "✓" : ""}</text>
|
||||
<text fg={theme.success}>{input() ? "✓" : ""}</text>
|
||||
</box>
|
||||
<Show when={store.editing}>
|
||||
<box paddingLeft={3}>
|
||||
<textarea
|
||||
ref={(val: TextareaRenderable) => (textarea = val)}
|
||||
focused
|
||||
initialValue={input()}
|
||||
placeholder="Type your own answer"
|
||||
textColor={theme.text}
|
||||
focusedTextColor={theme.text}
|
||||
cursorColor={theme.primary}
|
||||
keyBindings={bindings()}
|
||||
/>
|
||||
</box>
|
||||
<textarea
|
||||
ref={(val: TextareaRenderable) => (textarea = val)}
|
||||
focused
|
||||
placeholder="Type your own answer"
|
||||
textColor={theme.text}
|
||||
focusedTextColor={theme.text}
|
||||
cursorColor={theme.primary}
|
||||
keyBindings={bindings()}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={!store.editing && input()}>
|
||||
<box paddingLeft={3}>
|
||||
<text fg={theme.textMuted}>{input()}</text>
|
||||
</box>
|
||||
<text fg={theme.textMuted}>{input()}</text>
|
||||
</Show>
|
||||
</box>
|
||||
</box>
|
||||
@@ -319,12 +243,11 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
</box>
|
||||
<For each={questions()}>
|
||||
{(q, index) => {
|
||||
const value = () => store.answers[index()]?.join(", ") ?? ""
|
||||
const answered = () => Boolean(value())
|
||||
const answer = () => store.answers[index()]
|
||||
return (
|
||||
<box flexDirection="row" gap={1} paddingLeft={1}>
|
||||
<text fg={theme.textMuted}>{q.header}:</text>
|
||||
<text fg={answered() ? theme.text : theme.error}>{answered() ? value() : "(not answered)"}</text>
|
||||
<text fg={answer() ? theme.text : theme.error}>{answer() ?? "(not answered)"}</text>
|
||||
</box>
|
||||
)
|
||||
}}
|
||||
@@ -352,12 +275,8 @@ export function QuestionPrompt(props: { request: QuestionRequest }) {
|
||||
</text>
|
||||
</Show>
|
||||
<text fg={theme.text}>
|
||||
enter{" "}
|
||||
<span style={{ fg: theme.textMuted }}>
|
||||
{confirm() ? "submit" : multi() ? "toggle" : single() ? "submit" : "confirm"}
|
||||
</span>
|
||||
enter <span style={{ fg: theme.textMuted }}>{confirm() ? "submit" : single() ? "submit" : "confirm"}</span>
|
||||
</text>
|
||||
|
||||
<text fg={theme.text}>
|
||||
esc <span style={{ fg: theme.textMuted }}>dismiss</span>
|
||||
</text>
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useDirectory } from "../../context/directory"
|
||||
import { useKV } from "../../context/kv"
|
||||
import { TodoItem } from "../../component/todo-item"
|
||||
|
||||
export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
||||
export function Sidebar(props: { sessionID: string }) {
|
||||
const sync = useSync()
|
||||
const { theme } = useTheme()
|
||||
const session = createMemo(() => sync.session.get(props.sessionID)!)
|
||||
@@ -77,7 +77,6 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
position={props.overlay ? "absolute" : "relative"}
|
||||
>
|
||||
<scrollbox flexGrow={1}>
|
||||
<box flexShrink={0} gap={1} paddingRight={1}>
|
||||
|
||||
@@ -62,7 +62,6 @@ function init() {
|
||||
current.onClose?.()
|
||||
setStore("stack", store.stack.slice(0, -1))
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
refocus()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -30,7 +30,7 @@ function getNetworkIPs() {
|
||||
export const WebCommand = cmd({
|
||||
command: "web",
|
||||
builder: (yargs) => withNetworkOptions(yargs),
|
||||
describe: "start opencode server and open web interface",
|
||||
describe: "starts a headless opencode server",
|
||||
handler: async (args) => {
|
||||
const opts = await resolveNetworkOptions(args)
|
||||
const server = Server.listen(opts)
|
||||
|
||||
@@ -13,11 +13,6 @@ export namespace Flag {
|
||||
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthy("OPENCODE_ENABLE_EXPERIMENTAL_MODELS")
|
||||
export const OPENCODE_DISABLE_AUTOCOMPACT = truthy("OPENCODE_DISABLE_AUTOCOMPACT")
|
||||
export const OPENCODE_DISABLE_MODELS_FETCH = truthy("OPENCODE_DISABLE_MODELS_FETCH")
|
||||
export const OPENCODE_DISABLE_CLAUDE_CODE = truthy("OPENCODE_DISABLE_CLAUDE_CODE")
|
||||
export const OPENCODE_DISABLE_CLAUDE_CODE_PROMPT =
|
||||
OPENCODE_DISABLE_CLAUDE_CODE || truthy("OPENCODE_DISABLE_CLAUDE_CODE_PROMPT")
|
||||
export const OPENCODE_DISABLE_CLAUDE_CODE_SKILLS =
|
||||
OPENCODE_DISABLE_CLAUDE_CODE || truthy("OPENCODE_DISABLE_CLAUDE_CODE_SKILLS")
|
||||
export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"]
|
||||
export const OPENCODE_CLIENT = process.env["OPENCODE_CLIENT"] ?? "cli"
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ export const rustfmt: Info = {
|
||||
}
|
||||
|
||||
export const cargofmt: Info = {
|
||||
name: "cargofmt",
|
||||
name: "cargo fmt",
|
||||
command: ["cargo", "fmt", "--", "$FILE"],
|
||||
extensions: [".rs"],
|
||||
async enabled() {
|
||||
|
||||
@@ -101,9 +101,9 @@ const cli = yargs(hideBin(process.argv))
|
||||
.command(SessionCommand)
|
||||
.fail((msg) => {
|
||||
if (
|
||||
msg?.startsWith("Unknown argument") ||
|
||||
msg?.startsWith("Not enough non-option arguments") ||
|
||||
msg?.startsWith("Invalid values:")
|
||||
msg.startsWith("Unknown argument") ||
|
||||
msg.startsWith("Not enough non-option arguments") ||
|
||||
msg.startsWith("Invalid values:")
|
||||
) {
|
||||
cli.showHelp("log")
|
||||
}
|
||||
|
||||
210
packages/opencode/src/permission/index.ts
Normal file
210
packages/opencode/src/permission/index.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { Bus } from "@/bus"
|
||||
import z from "zod"
|
||||
import { Log } from "../util/log"
|
||||
import { Identifier } from "../id/id"
|
||||
import { Plugin } from "../plugin"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Wildcard } from "../util/wildcard"
|
||||
|
||||
export namespace Permission {
|
||||
const log = Log.create({ service: "permission" })
|
||||
|
||||
function toKeys(pattern: Info["pattern"], type: string): string[] {
|
||||
return pattern === undefined ? [type] : Array.isArray(pattern) ? pattern : [pattern]
|
||||
}
|
||||
|
||||
function covered(keys: string[], approved: Record<string, boolean>): boolean {
|
||||
const pats = Object.keys(approved)
|
||||
return keys.every((k) => pats.some((p) => Wildcard.match(k, p)))
|
||||
}
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string(),
|
||||
type: z.string(),
|
||||
pattern: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
sessionID: z.string(),
|
||||
messageID: z.string(),
|
||||
callID: z.string().optional(),
|
||||
message: z.string(),
|
||||
metadata: z.record(z.string(), z.any()),
|
||||
time: z.object({
|
||||
created: z.number(),
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
ref: "Permission",
|
||||
})
|
||||
export type Info = z.infer<typeof Info>
|
||||
|
||||
export const Event = {
|
||||
Updated: BusEvent.define("permission.updated", Info),
|
||||
Replied: BusEvent.define(
|
||||
"permission.replied",
|
||||
z.object({
|
||||
sessionID: z.string(),
|
||||
permissionID: z.string(),
|
||||
response: z.string(),
|
||||
}),
|
||||
),
|
||||
}
|
||||
|
||||
const state = Instance.state(
|
||||
() => {
|
||||
const pending: {
|
||||
[sessionID: string]: {
|
||||
[permissionID: string]: {
|
||||
info: Info
|
||||
resolve: () => void
|
||||
reject: (e: any) => void
|
||||
}
|
||||
}
|
||||
} = {}
|
||||
|
||||
const approved: {
|
||||
[sessionID: string]: {
|
||||
[permissionID: string]: boolean
|
||||
}
|
||||
} = {}
|
||||
|
||||
return {
|
||||
pending,
|
||||
approved,
|
||||
}
|
||||
},
|
||||
async (state) => {
|
||||
for (const pending of Object.values(state.pending)) {
|
||||
for (const item of Object.values(pending)) {
|
||||
item.reject(new RejectedError(item.info.sessionID, item.info.id, item.info.callID, item.info.metadata))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export function pending() {
|
||||
return state().pending
|
||||
}
|
||||
|
||||
export function list() {
|
||||
const { pending } = state()
|
||||
const result: Info[] = []
|
||||
for (const items of Object.values(pending)) {
|
||||
for (const item of Object.values(items)) {
|
||||
result.push(item.info)
|
||||
}
|
||||
}
|
||||
return result.sort((a, b) => a.id.localeCompare(b.id))
|
||||
}
|
||||
|
||||
export async function ask(input: {
|
||||
type: Info["type"]
|
||||
message: Info["message"]
|
||||
pattern?: Info["pattern"]
|
||||
callID?: Info["callID"]
|
||||
sessionID: Info["sessionID"]
|
||||
messageID: Info["messageID"]
|
||||
metadata: Info["metadata"]
|
||||
}) {
|
||||
const { pending, approved } = state()
|
||||
log.info("asking", {
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
toolCallID: input.callID,
|
||||
pattern: input.pattern,
|
||||
})
|
||||
const approvedForSession = approved[input.sessionID] || {}
|
||||
const keys = toKeys(input.pattern, input.type)
|
||||
if (covered(keys, approvedForSession)) return
|
||||
const info: Info = {
|
||||
id: Identifier.ascending("permission"),
|
||||
type: input.type,
|
||||
pattern: input.pattern,
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
callID: input.callID,
|
||||
message: input.message,
|
||||
metadata: input.metadata,
|
||||
time: {
|
||||
created: Date.now(),
|
||||
},
|
||||
}
|
||||
|
||||
switch (
|
||||
await Plugin.trigger("permission.ask", info, {
|
||||
status: "ask",
|
||||
}).then((x) => x.status)
|
||||
) {
|
||||
case "deny":
|
||||
throw new RejectedError(info.sessionID, info.id, info.callID, info.metadata)
|
||||
case "allow":
|
||||
return
|
||||
}
|
||||
|
||||
pending[input.sessionID] = pending[input.sessionID] || {}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
pending[input.sessionID][info.id] = {
|
||||
info,
|
||||
resolve,
|
||||
reject,
|
||||
}
|
||||
Bus.publish(Event.Updated, info)
|
||||
})
|
||||
}
|
||||
|
||||
export const Response = z.enum(["once", "always", "reject"])
|
||||
export type Response = z.infer<typeof Response>
|
||||
|
||||
export function respond(input: { sessionID: Info["sessionID"]; permissionID: Info["id"]; response: Response }) {
|
||||
log.info("response", input)
|
||||
const { pending, approved } = state()
|
||||
const match = pending[input.sessionID]?.[input.permissionID]
|
||||
if (!match) return
|
||||
delete pending[input.sessionID][input.permissionID]
|
||||
Bus.publish(Event.Replied, {
|
||||
sessionID: input.sessionID,
|
||||
permissionID: input.permissionID,
|
||||
response: input.response,
|
||||
})
|
||||
if (input.response === "reject") {
|
||||
match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.callID, match.info.metadata))
|
||||
return
|
||||
}
|
||||
match.resolve()
|
||||
if (input.response === "always") {
|
||||
approved[input.sessionID] = approved[input.sessionID] || {}
|
||||
const approveKeys = toKeys(match.info.pattern, match.info.type)
|
||||
for (const k of approveKeys) {
|
||||
approved[input.sessionID][k] = true
|
||||
}
|
||||
const items = pending[input.sessionID]
|
||||
if (!items) return
|
||||
for (const item of Object.values(items)) {
|
||||
const itemKeys = toKeys(item.info.pattern, item.info.type)
|
||||
if (covered(itemKeys, approved[input.sessionID])) {
|
||||
respond({
|
||||
sessionID: item.info.sessionID,
|
||||
permissionID: item.info.id,
|
||||
response: input.response,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class RejectedError extends Error {
|
||||
constructor(
|
||||
public readonly sessionID: string,
|
||||
public readonly permissionID: string,
|
||||
public readonly toolCallID?: string,
|
||||
public readonly metadata?: Record<string, any>,
|
||||
public readonly reason?: string,
|
||||
) {
|
||||
super(
|
||||
reason !== undefined
|
||||
? reason
|
||||
: `The user rejected permission to use this specific tool call. You may try again with different parameters.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,57 +120,28 @@ export namespace PermissionNext {
|
||||
async (input) => {
|
||||
const s = await state()
|
||||
const { ruleset, ...request } = input
|
||||
const ask = (request.patterns ?? []).reduce((ask, pattern) => {
|
||||
for (const pattern of request.patterns ?? []) {
|
||||
const rule = evaluate(request.permission, pattern, ruleset, s.approved)
|
||||
log.info("evaluated", { permission: request.permission, pattern, action: rule })
|
||||
if (rule.action === "deny") {
|
||||
if (rule.action === "deny")
|
||||
throw new DeniedError(ruleset.filter((r) => Wildcard.match(request.permission, r.permission)))
|
||||
if (rule.action === "ask") {
|
||||
const id = input.id ?? Identifier.ascending("permission")
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const info: Request = {
|
||||
id,
|
||||
...request,
|
||||
}
|
||||
s.pending[id] = {
|
||||
info,
|
||||
resolve,
|
||||
reject,
|
||||
}
|
||||
Bus.publish(Event.Asked, info)
|
||||
})
|
||||
}
|
||||
return ask || rule.action === "ask"
|
||||
}, false)
|
||||
|
||||
if (!ask) return
|
||||
|
||||
const id = input.id ?? Identifier.ascending("permission")
|
||||
const info: Request = {
|
||||
id,
|
||||
...request,
|
||||
if (rule.action === "allow") continue
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
s.pending[id] = {
|
||||
info,
|
||||
resolve,
|
||||
reject,
|
||||
}
|
||||
|
||||
void import("@/plugin")
|
||||
.then((plugin) =>
|
||||
plugin.Plugin.trigger("permission.ask", info, {
|
||||
status: "ask" as "ask" | "allow" | "deny",
|
||||
}),
|
||||
)
|
||||
.then((result) => {
|
||||
const existing = s.pending[id]
|
||||
if (!existing) return
|
||||
if (result.status === "deny") {
|
||||
delete s.pending[id]
|
||||
existing.reject(new RejectedError())
|
||||
return
|
||||
}
|
||||
if (result.status === "allow") {
|
||||
delete s.pending[id]
|
||||
existing.resolve()
|
||||
return
|
||||
}
|
||||
Bus.publish(Event.Asked, info)
|
||||
})
|
||||
.catch((error) => {
|
||||
log.error("permission.ask plugin failed", { error })
|
||||
if (!s.pending[id]) return
|
||||
Bus.publish(Event.Asked, info)
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ export namespace Question {
|
||||
question: z.string().describe("Complete question"),
|
||||
header: z.string().max(12).describe("Very short label (max 12 chars)"),
|
||||
options: z.array(Option).describe("Available choices"),
|
||||
multiple: z.boolean().optional().describe("Allow selecting multiple choices"),
|
||||
})
|
||||
.meta({
|
||||
ref: "QuestionInfo",
|
||||
@@ -47,15 +46,8 @@ export namespace Question {
|
||||
})
|
||||
export type Request = z.infer<typeof Request>
|
||||
|
||||
export const Answer = z.array(z.string()).meta({
|
||||
ref: "QuestionAnswer",
|
||||
})
|
||||
export type Answer = z.infer<typeof Answer>
|
||||
|
||||
export const Reply = z.object({
|
||||
answers: z
|
||||
.array(Answer)
|
||||
.describe("User answers in order of questions (each answer is an array of selected labels)"),
|
||||
answers: z.array(z.string()).describe("User answers in order of questions"),
|
||||
})
|
||||
export type Reply = z.infer<typeof Reply>
|
||||
|
||||
@@ -66,7 +58,7 @@ export namespace Question {
|
||||
z.object({
|
||||
sessionID: z.string(),
|
||||
requestID: z.string(),
|
||||
answers: z.array(Answer),
|
||||
answers: z.array(z.string()),
|
||||
}),
|
||||
),
|
||||
Rejected: BusEvent.define(
|
||||
@@ -83,7 +75,7 @@ export namespace Question {
|
||||
string,
|
||||
{
|
||||
info: Request
|
||||
resolve: (answers: Answer[]) => void
|
||||
resolve: (answers: string[]) => void
|
||||
reject: (e: any) => void
|
||||
}
|
||||
> = {}
|
||||
@@ -97,13 +89,13 @@ export namespace Question {
|
||||
sessionID: string
|
||||
questions: Info[]
|
||||
tool?: { messageID: string; callID: string }
|
||||
}): Promise<Answer[]> {
|
||||
}): Promise<string[]> {
|
||||
const s = await state()
|
||||
const id = Identifier.ascending("question")
|
||||
|
||||
log.info("asking", { id, questions: input.questions.length })
|
||||
|
||||
return new Promise<Answer[]>((resolve, reject) => {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
const info: Request = {
|
||||
id,
|
||||
sessionID: input.sessionID,
|
||||
@@ -119,7 +111,7 @@ export namespace Question {
|
||||
})
|
||||
}
|
||||
|
||||
export async function reply(input: { requestID: string; answers: Answer[] }): Promise<void> {
|
||||
export async function reply(input: { requestID: string; answers: string[] }): Promise<void> {
|
||||
const s = await state()
|
||||
const existing = s.pending[input.requestID]
|
||||
if (!existing) {
|
||||
|
||||
@@ -52,7 +52,7 @@ export const QuestionRoute = new Hono()
|
||||
requestID: z.string(),
|
||||
}),
|
||||
),
|
||||
validator("json", Question.Reply),
|
||||
validator("json", z.object({ answers: z.array(z.string()) })),
|
||||
async (c) => {
|
||||
const params = c.req.valid("param")
|
||||
const json = c.req.valid("json")
|
||||
|
||||
@@ -62,10 +62,10 @@ export namespace SystemPrompt {
|
||||
"CLAUDE.md",
|
||||
"CONTEXT.md", // deprecated
|
||||
]
|
||||
const GLOBAL_RULE_FILES = [path.join(Global.Path.config, "AGENTS.md")]
|
||||
if (!Flag.OPENCODE_DISABLE_CLAUDE_CODE_PROMPT) {
|
||||
GLOBAL_RULE_FILES.push(path.join(os.homedir(), ".claude", "CLAUDE.md"))
|
||||
}
|
||||
const GLOBAL_RULE_FILES = [
|
||||
path.join(Global.Path.config, "AGENTS.md"),
|
||||
path.join(os.homedir(), ".claude", "CLAUDE.md"),
|
||||
]
|
||||
|
||||
if (Flag.OPENCODE_CONFIG_DIR) {
|
||||
GLOBAL_RULE_FILES.push(path.join(Flag.OPENCODE_CONFIG_DIR, "AGENTS.md"))
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Log } from "../util/log"
|
||||
import { Global } from "@/global"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { exists } from "fs/promises"
|
||||
import { Flag } from "@/flag/flag"
|
||||
|
||||
export namespace Skill {
|
||||
const log = Log.create({ service: "skill" })
|
||||
@@ -81,24 +80,22 @@ export namespace Skill {
|
||||
claudeDirs.push(globalClaude)
|
||||
}
|
||||
|
||||
if (!Flag.OPENCODE_DISABLE_CLAUDE_CODE_SKILLS) {
|
||||
for (const dir of claudeDirs) {
|
||||
const matches = await Array.fromAsync(
|
||||
CLAUDE_SKILL_GLOB.scan({
|
||||
cwd: dir,
|
||||
absolute: true,
|
||||
onlyFiles: true,
|
||||
followSymlinks: true,
|
||||
dot: true,
|
||||
}),
|
||||
).catch((error) => {
|
||||
log.error("failed .claude directory scan for skills", { dir, error })
|
||||
return []
|
||||
})
|
||||
for (const dir of claudeDirs) {
|
||||
const matches = await Array.fromAsync(
|
||||
CLAUDE_SKILL_GLOB.scan({
|
||||
cwd: dir,
|
||||
absolute: true,
|
||||
onlyFiles: true,
|
||||
followSymlinks: true,
|
||||
dot: true,
|
||||
}),
|
||||
).catch((error) => {
|
||||
log.error("failed .claude directory scan for skills", { dir, error })
|
||||
return []
|
||||
})
|
||||
|
||||
for (const match of matches) {
|
||||
await addSkill(match)
|
||||
}
|
||||
for (const match of matches) {
|
||||
await addSkill(match)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,7 @@ export const QuestionTool = Tool.define("question", {
|
||||
tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined,
|
||||
})
|
||||
|
||||
function format(answer: Question.Answer | undefined) {
|
||||
if (!answer?.length) return "Unanswered"
|
||||
return answer.join(", ")
|
||||
}
|
||||
|
||||
const formatted = params.questions.map((q, i) => `"${q.question}"="${format(answers[i])}"`).join(", ")
|
||||
const formatted = params.questions.map((q, i) => `"${q.question}"="${answers[i] ?? "Unanswered"}"`).join(", ")
|
||||
|
||||
return {
|
||||
title: `Asked ${params.questions.length} question${params.questions.length > 1 ? "s" : ""}`,
|
||||
|
||||
@@ -6,5 +6,4 @@ Use this tool when you need to ask the user questions during execution. This all
|
||||
|
||||
Usage notes:
|
||||
- Users will always be able to select "Other" to provide custom text input
|
||||
- Answers are returned as arrays of labels; set `multiple: true` to allow selecting more than one
|
||||
- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label
|
||||
|
||||
@@ -446,66 +446,3 @@ test("legacy tools config maps write/edit/patch/multiedit to edit permission", a
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("Truncate.DIR is allowed even when user denies external_directory globally", async () => {
|
||||
const { Truncate } = await import("../../src/tool/truncation")
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
permission: {
|
||||
external_directory: "deny",
|
||||
},
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const build = await Agent.get("build")
|
||||
expect(PermissionNext.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("allow")
|
||||
expect(PermissionNext.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("Truncate.DIR is allowed even when user denies external_directory per-agent", async () => {
|
||||
const { Truncate } = await import("../../src/tool/truncation")
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
agent: {
|
||||
build: {
|
||||
permission: {
|
||||
external_directory: "deny",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const build = await Agent.get("build")
|
||||
expect(PermissionNext.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("allow")
|
||||
expect(PermissionNext.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("explicit Truncate.DIR deny is respected", async () => {
|
||||
const { Truncate } = await import("../../src/tool/truncation")
|
||||
await using tmp = await tmpdir({
|
||||
config: {
|
||||
permission: {
|
||||
external_directory: {
|
||||
"*": "deny",
|
||||
[Truncate.DIR]: "deny",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: async () => {
|
||||
const build = await Agent.get("build")
|
||||
expect(PermissionNext.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -82,11 +82,11 @@ test("reply - resolves the pending ask with answers", async () => {
|
||||
|
||||
await Question.reply({
|
||||
requestID,
|
||||
answers: [["Option 1"]],
|
||||
answers: ["Option 1"],
|
||||
})
|
||||
|
||||
const answers = await askPromise
|
||||
expect(answers).toEqual([["Option 1"]])
|
||||
expect(answers).toEqual(["Option 1"])
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -115,7 +115,7 @@ test("reply - removes from pending list", async () => {
|
||||
|
||||
await Question.reply({
|
||||
requestID: pending[0].id,
|
||||
answers: [["Option 1"]],
|
||||
answers: ["Option 1"],
|
||||
})
|
||||
|
||||
const pendingAfter = await Question.list()
|
||||
@@ -131,7 +131,7 @@ test("reply - does nothing for unknown requestID", async () => {
|
||||
fn: async () => {
|
||||
await Question.reply({
|
||||
requestID: "que_unknown",
|
||||
answers: [["Option 1"]],
|
||||
answers: ["Option 1"],
|
||||
})
|
||||
// Should not throw
|
||||
},
|
||||
@@ -244,11 +244,11 @@ test("ask - handles multiple questions", async () => {
|
||||
|
||||
await Question.reply({
|
||||
requestID: pending[0].id,
|
||||
answers: [["Build"], ["Dev"]],
|
||||
answers: ["Build", "Dev"],
|
||||
})
|
||||
|
||||
const answers = await askPromise
|
||||
expect(answers).toEqual([["Build"], ["Dev"]])
|
||||
expect(answers).toEqual(["Build", "Dev"])
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 MiB |
@@ -286,18 +286,4 @@ describe("tool.read truncation", () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("large image files are properly attached without error", async () => {
|
||||
await Instance.provide({
|
||||
directory: FIXTURES_DIR,
|
||||
fn: async () => {
|
||||
const read = await ReadTool.init()
|
||||
const result = await read.execute({ filePath: path.join(FIXTURES_DIR, "large-image.png") }, ctx)
|
||||
expect(result.metadata.truncated).toBe(false)
|
||||
expect(result.attachments).toBeDefined()
|
||||
expect(result.attachments?.length).toBe(1)
|
||||
expect(result.attachments?.[0].type).toBe("file")
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -84,7 +84,6 @@ import type {
|
||||
PtyRemoveResponses,
|
||||
PtyUpdateErrors,
|
||||
PtyUpdateResponses,
|
||||
QuestionAnswer,
|
||||
QuestionListResponses,
|
||||
QuestionRejectErrors,
|
||||
QuestionRejectResponses,
|
||||
@@ -1816,7 +1815,7 @@ export class Question extends HeyApiClient {
|
||||
parameters: {
|
||||
requestID: string
|
||||
directory?: string
|
||||
answers?: Array<QuestionAnswer>
|
||||
answers?: Array<string>
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
|
||||
@@ -517,6 +517,13 @@ export type EventSessionIdle = {
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionCompacted = {
|
||||
type: "session.compacted"
|
||||
properties: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type QuestionOption = {
|
||||
/**
|
||||
* Display text (1-5 words, concise)
|
||||
@@ -541,10 +548,6 @@ export type QuestionInfo = {
|
||||
* Available choices
|
||||
*/
|
||||
options: Array<QuestionOption>
|
||||
/**
|
||||
* Allow selecting multiple choices
|
||||
*/
|
||||
multiple?: boolean
|
||||
}
|
||||
|
||||
export type QuestionRequest = {
|
||||
@@ -565,14 +568,12 @@ export type EventQuestionAsked = {
|
||||
properties: QuestionRequest
|
||||
}
|
||||
|
||||
export type QuestionAnswer = Array<string>
|
||||
|
||||
export type EventQuestionReplied = {
|
||||
type: "question.replied"
|
||||
properties: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
answers: Array<QuestionAnswer>
|
||||
answers: Array<string>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,13 +585,6 @@ export type EventQuestionRejected = {
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionCompacted = {
|
||||
type: "session.compacted"
|
||||
properties: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventFileEdited = {
|
||||
type: "file.edited"
|
||||
properties: {
|
||||
@@ -855,10 +849,10 @@ export type Event =
|
||||
| EventPermissionReplied
|
||||
| EventSessionStatus
|
||||
| EventSessionIdle
|
||||
| EventSessionCompacted
|
||||
| EventQuestionAsked
|
||||
| EventQuestionReplied
|
||||
| EventQuestionRejected
|
||||
| EventSessionCompacted
|
||||
| EventFileEdited
|
||||
| EventTodoUpdated
|
||||
| EventTuiPromptAppend
|
||||
@@ -1303,7 +1297,6 @@ export type PermissionConfig =
|
||||
external_directory?: PermissionRuleConfig
|
||||
todowrite?: PermissionActionConfig
|
||||
todoread?: PermissionActionConfig
|
||||
question?: PermissionActionConfig
|
||||
webfetch?: PermissionActionConfig
|
||||
websearch?: PermissionActionConfig
|
||||
codesearch?: PermissionActionConfig
|
||||
@@ -3636,10 +3629,7 @@ export type QuestionListResponse = QuestionListResponses[keyof QuestionListRespo
|
||||
|
||||
export type QuestionReplyData = {
|
||||
body?: {
|
||||
/**
|
||||
* User answers in order of questions (each answer is an array of selected labels)
|
||||
*/
|
||||
answers: Array<QuestionAnswer>
|
||||
answers: Array<string>
|
||||
}
|
||||
path: {
|
||||
requestID: string
|
||||
|
||||
@@ -3156,186 +3156,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/question": {
|
||||
"get": {
|
||||
"operationId": "question.list",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "directory",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"summary": "List pending questions",
|
||||
"description": "Get all pending question requests across all sessions.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of pending questions",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/QuestionRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.list({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/question/{requestID}/reply": {
|
||||
"post": {
|
||||
"operationId": "question.reply",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "directory",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "requestID",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"summary": "Reply to question request",
|
||||
"description": "Provide answers to a question request from the AI assistant.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Question answered successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BadRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NotFoundError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"answers": {
|
||||
"description": "User answers in order of questions (each answer is an array of selected labels)",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/QuestionAnswer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["answers"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.reply({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/question/{requestID}/reject": {
|
||||
"post": {
|
||||
"operationId": "question.reject",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "directory",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "requestID",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"summary": "Reject question request",
|
||||
"description": "Reject a question request from the AI assistant.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Question rejected successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BadRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NotFoundError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.question.reject({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/command": {
|
||||
"get": {
|
||||
"operationId": "command.list",
|
||||
@@ -7086,148 +6906,6 @@
|
||||
},
|
||||
"required": ["type", "properties"]
|
||||
},
|
||||
"QuestionOption": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"description": "Display text (1-5 words, concise)",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Explanation of choice",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["label", "description"]
|
||||
},
|
||||
"QuestionInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"question": {
|
||||
"description": "Complete question",
|
||||
"type": "string"
|
||||
},
|
||||
"header": {
|
||||
"description": "Very short label (max 12 chars)",
|
||||
"type": "string",
|
||||
"maxLength": 12
|
||||
},
|
||||
"options": {
|
||||
"description": "Available choices",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/QuestionOption"
|
||||
}
|
||||
},
|
||||
"multiple": {
|
||||
"description": "Allow selecting multiple choices",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["question", "header", "options"]
|
||||
},
|
||||
"QuestionRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^que.*"
|
||||
},
|
||||
"sessionID": {
|
||||
"type": "string",
|
||||
"pattern": "^ses.*"
|
||||
},
|
||||
"questions": {
|
||||
"description": "Questions to ask",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/QuestionInfo"
|
||||
}
|
||||
},
|
||||
"tool": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"messageID": {
|
||||
"type": "string"
|
||||
},
|
||||
"callID": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["messageID", "callID"]
|
||||
}
|
||||
},
|
||||
"required": ["id", "sessionID", "questions"]
|
||||
},
|
||||
"Event.question.asked": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "question.asked"
|
||||
},
|
||||
"properties": {
|
||||
"$ref": "#/components/schemas/QuestionRequest"
|
||||
}
|
||||
},
|
||||
"required": ["type", "properties"]
|
||||
},
|
||||
"QuestionAnswer": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"Event.question.replied": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "question.replied"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionID": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestID": {
|
||||
"type": "string"
|
||||
},
|
||||
"answers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/QuestionAnswer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["sessionID", "requestID", "answers"]
|
||||
}
|
||||
},
|
||||
"required": ["type", "properties"]
|
||||
},
|
||||
"Event.question.rejected": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "question.rejected"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionID": {
|
||||
"type": "string"
|
||||
},
|
||||
"requestID": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["sessionID", "requestID"]
|
||||
}
|
||||
},
|
||||
"required": ["type", "properties"]
|
||||
},
|
||||
"Event.session.compacted": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7952,15 +7630,6 @@
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.session.idle"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.question.asked"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.question.replied"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.question.rejected"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.session.compacted"
|
||||
},
|
||||
@@ -8620,9 +8289,6 @@
|
||||
"todoread": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"question": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"webfetch": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 40 40" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.93433 9.57807C6.93433 8.92952 7.46008 8.40376 8.10864 8.40376C8.75719 8.40376 9.28295 8.92952 9.28295 9.57807V16.6239C9.28295 17.2725 8.75719 17.7983 8.10864 17.7983C7.46008 17.7983 6.93433 17.2725 6.93433 16.6239V9.57807Z" />
|
||||
<path d="M30.7144 25.1377C30.7144 24.4891 31.2402 23.9634 31.8887 23.9634C32.5373 23.9634 33.063 24.4891 33.063 25.1377V31.0092C33.063 31.6578 32.5373 32.1836 31.8887 32.1836C31.2402 32.1836 30.7144 31.6578 30.7144 31.0093V25.1377Z" />
|
||||
<path d="M22.7875 30.422C22.7875 29.7735 23.3132 29.2477 23.9618 29.2477C24.6103 29.2477 25.1361 29.7735 25.1361 30.422V34.8257C25.1361 35.4742 24.6103 36 23.9618 36C23.3132 36 22.7875 35.4742 22.7875 34.8257V30.422Z" />
|
||||
<path d="M6.93433 28.367C6.93433 27.7185 7.46008 27.1927 8.10864 27.1927C8.75719 27.1927 9.28295 27.7185 9.28295 28.367V31.5964C9.28295 32.245 8.75719 32.7707 8.10864 32.7707C7.46008 32.7707 6.93433 32.245 6.93433 31.5964V28.367Z" />
|
||||
<path d="M14.8617 5.46787C14.8617 4.81931 15.3874 4.29355 16.036 4.29355C16.6845 4.29355 17.2103 4.81931 17.2103 5.46787V9.87153C17.2103 10.5201 16.6845 11.0458 16.036 11.0458C15.3874 11.0458 14.8617 10.5201 14.8617 9.87153V5.46787Z" />
|
||||
<path d="M30.7144 9.28437C30.7144 8.63582 31.2402 8.11006 31.8887 8.11006C32.5373 8.11006 33.063 8.63582 33.063 9.28437V13.688C33.063 14.3366 32.5373 14.8623 31.8887 14.8623C31.2402 14.8623 30.7144 14.3366 30.7144 13.688V9.28437Z" />
|
||||
<path d="M22.7875 5.17431C22.7875 4.52576 23.3132 4 23.9618 4C24.6103 4 25.1361 4.52576 25.1361 5.17431V18.3853C25.1361 19.0339 24.6103 19.5596 23.9618 19.5596C23.3132 19.5596 22.7875 19.0339 22.7875 18.3853V5.17431Z" />
|
||||
<path d="M14.8617 21.3212C14.8617 20.6726 15.3874 20.1469 16.036 20.1469C16.6845 20.1469 17.2103 20.6726 17.2103 21.3212V34.5322C17.2103 35.1807 16.6845 35.7065 16.036 35.7065C15.3874 35.7065 14.8617 35.1807 14.8617 34.5322V21.3212Z" />
|
||||
<path d="M10.4577 21.9083C10.4577 23.2055 9.40623 24.257 8.10912 24.257C6.81201 24.257 5.7605 23.2055 5.7605 21.9083C5.7605 20.6112 6.81201 19.5597 8.10912 19.5597C9.40623 19.5597 10.4577 20.6112 10.4577 21.9083Z" />
|
||||
<path d="M18.3843 14.8624C18.3843 16.1596 17.3328 17.2111 16.0357 17.2111C14.7386 17.2111 13.6871 16.1596 13.6871 14.8624C13.6871 13.5653 14.7386 12.5138 16.0357 12.5138C17.3328 12.5138 18.3843 13.5653 18.3843 14.8624Z" />
|
||||
<path d="M26.3101 24.2569C26.3101 25.554 25.2586 26.6056 23.9615 26.6056C22.6644 26.6056 21.6128 25.554 21.6128 24.2569C21.6128 22.9598 22.6644 21.9083 23.9615 21.9083C25.2586 21.9083 26.3101 22.9598 26.3101 24.2569Z" />
|
||||
<path d="M34.2378 19.5597C34.2378 20.8568 33.1863 21.9083 31.8892 21.9083C30.5921 21.9083 29.5406 20.8568 29.5406 19.5597C29.5406 18.2626 30.5921 17.2111 31.8892 17.2111C33.1863 17.2111 34.2378 18.2626 34.2378 19.5597Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.1484 16.5153V16.5238C14.5325 17.3971 14.4945 18.6923 14.5114 19.3925V21.4091C14.5114 22.7379 13.4272 23.8221 12.1067 23.8221H9.24219C7.91327 23.8221 6.8375 22.7423 6.8375 21.4091V18.5952C6.8375 17.262 7.91327 16.182 9.24219 16.182H10.9339C11.2376 16.182 11.9717 16.1694 12.2923 16.1061C12.5666 16.0513 13.4525 15.853 14.2963 15.2201C14.3891 15.1526 14.4734 15.0852 14.5451 15.0219C14.5451 15.0219 14.7392 14.8574 14.9122 14.6759C15.3889 14.1824 15.7559 13.4778 15.7559 13.4778C15.8741 13.2584 16.1441 12.7016 16.3044 11.9295C16.3803 11.5541 16.4141 11.2039 16.4394 10.917L16.528 9.24219C16.528 7.91327 17.6079 6.83329 18.9369 6.83329H21.7339C23.0629 6.83329 24.1427 7.91327 24.1427 9.24219V12.0603C24.1427 13.3892 23.0629 14.4692 21.7339 14.4692H19.4346C18.9073 14.4819 18.4686 14.5367 18.1438 14.5958C17.5489 14.7055 17.2409 14.832 17.1481 14.87M10.6428 14.8109C8.35625 14.8109 6.5 12.9505 6.5 10.6555C6.5 8.36046 8.35625 6.5 10.6428 6.5C12.9294 6.5 14.7856 8.36046 14.7856 10.6555C14.7856 12.9505 12.9294 14.8109 10.6428 14.8109ZM10.6428 25.1891C12.9294 25.1891 14.7856 27.0496 14.7856 29.3446C14.7856 31.6396 12.9294 33.5 10.6428 33.5C8.35625 33.5 6.5 31.6396 6.5 29.3446C6.5 27.0496 8.35625 25.1891 10.6428 25.1891ZM33.5 12.1405C33.5 13.4272 32.4538 14.4734 31.1671 14.4734C30.4963 14.4355 29.9352 14.4566 29.526 14.4819C28.8341 14.5283 28.429 14.5536 27.8933 14.7181C27.29 14.9037 26.8598 15.1611 26.615 15.3087C26.5096 15.372 26.0919 15.6294 25.6067 16.0597C25.3325 16.3044 25.0709 16.5364 24.8009 16.9076C24.4254 17.4265 24.261 17.899 24.1766 18.1521C23.9487 18.8356 23.9066 19.7848 23.9192 20.2448L23.8939 21.4808C23.8939 22.7717 22.8519 23.8179 21.5652 23.8179H18.6079C17.3211 23.8179 16.2791 22.7717 16.2791 21.4808V18.515C16.2791 17.2241 17.3211 16.1778 18.6079 16.1778H20.3965C21.0294 16.1778 22.2865 16.2326 23.3539 15.8403C24.2694 15.507 25.1848 14.5789 25.5434 13.5622C25.8852 12.5286 25.8767 11.1533 25.8767 11.1533L25.8852 9.16625C25.8852 7.87952 26.9314 6.83329 28.2181 6.83329H31.1671C32.4538 6.83329 33.5 7.87952 33.5 9.16625V12.1405Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M26.7,5.25l2.14-1.53,10.49,13.29-16.23,19.43-2.65,3L0.42,16.83l6.87-8.64,5.78,2.21,1.79-4.46L23.3,0.1l3.42,5.15ZM31.13,13.98L22.91,1.78l-7.1,4.95-5.45,13.53,20.77-6.28ZM36.74,15.57l-8.03-10.1-0.14-0.1-1.14,0.89,5.03,7.58,4.28,1.73ZM9.06,20.35l3.53-8.84-4.89-1.92-5.62,7.1,6.98,3.66ZM37.62,17.19l-5.3-2.14-9.22,19.52,14.52-17.38ZM30.95,15.24l-20.63,6.22,10.1,15.93,10.53-22.15ZM15.79,32.41l-6.86-10.82c-1.61-0.84-3.2-1.73-4.81-2.57-0.1-0.05-0.21-0.12-0.32-0.13l11.99,13.52Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 597 B |
@@ -43,15 +43,14 @@
|
||||
|
||||
/* padding: 8px; */
|
||||
/* padding: 8px 8px 0 8px; */
|
||||
border: 1px solid
|
||||
light-dark(
|
||||
color-mix(in oklch, var(--border-base) 30%, transparent),
|
||||
color-mix(in oklch, var(--border-base) 50%, transparent)
|
||||
);
|
||||
border: 1px solid hsl(from var(--border-base) h s l / 0.2);
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--surface-raised-stronger-non-alpha);
|
||||
background-clip: padding-box;
|
||||
box-shadow: var(--shadow-lg);
|
||||
box-shadow:
|
||||
0 15px 45px 0 rgba(19, 16, 16, 0.35),
|
||||
0 3.35px 10.051px 0 rgba(19, 16, 16, 0.25),
|
||||
0 0.998px 2.993px 0 rgba(19, 16, 16, 0.2);
|
||||
|
||||
/* animation: contentHide 300ms ease-in forwards; */
|
||||
/**/
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
min-width: 8rem;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
|
||||
background-clip: padding-box;
|
||||
border: 1px solid var(--border-weak-base);
|
||||
background-color: var(--surface-raised-stronger-non-alpha);
|
||||
padding: 4px;
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
@@ -7,12 +7,9 @@
|
||||
min-width: 200px;
|
||||
max-width: 320px;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-weak-base);
|
||||
background-color: var(--surface-raised-stronger-non-alpha);
|
||||
|
||||
border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
|
||||
background-clip: padding-box;
|
||||
box-shadow: var(--shadow-md);
|
||||
|
||||
transform-origin: var(--kb-popover-content-transform-origin);
|
||||
|
||||
&:focus-within {
|
||||
|
||||
@@ -382,12 +382,6 @@
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</symbol>
|
||||
<symbol viewBox="0 0 40 40" id="nano-gpt">
|
||||
<path
|
||||
d="M26.7,5.25l2.14-1.53,10.49,13.29-16.23,19.43-2.65,3L0.42,16.83l6.87-8.64,5.78,2.21,1.79-4.46L23.3,0.1l3.42,5.15ZM31.13,13.98L22.91,1.78l-7.1,4.95-5.45,13.53,20.77-6.28ZM36.74,15.57l-8.03-10.1-0.14-0.1-1.14,0.89,5.03,7.58,4.28,1.73ZM9.06,20.35l3.53-8.84-4.89-1.92-5.62,7.1,6.98,3.66ZM37.62,17.19l-5.3-2.14-9.22,19.52,14.52-17.38ZM30.95,15.24l-20.63,6.22,10.1,15.93,10.53-22.15ZM15.79,32.41l-6.86-10.82c-1.61-0.84-3.2-1.73-4.81-2.57-0.1-0.05-0.21-0.12-0.32-0.13l11.99,13.52Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</symbol>
|
||||
<symbol viewBox="0 0 24 24" fill="none" id="morph">
|
||||
<path
|
||||
shape-rendering="geometricPrecision"
|
||||
@@ -638,12 +632,6 @@
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</symbol>
|
||||
<symbol viewBox="0 0 40 40" fill="none" id="friendli">
|
||||
<path
|
||||
d="M15.1484 16.5153V16.5238C14.5325 17.3971 14.4945 18.6923 14.5114 19.3925V21.4091C14.5114 22.7379 13.4272 23.8221 12.1067 23.8221H9.24219C7.91327 23.8221 6.8375 22.7423 6.8375 21.4091V18.5952C6.8375 17.262 7.91327 16.182 9.24219 16.182H10.9339C11.2376 16.182 11.9717 16.1694 12.2923 16.1061C12.5666 16.0513 13.4525 15.853 14.2963 15.2201C14.3891 15.1526 14.4734 15.0852 14.5451 15.0219C14.5451 15.0219 14.7392 14.8574 14.9122 14.6759C15.3889 14.1824 15.7559 13.4778 15.7559 13.4778C15.8741 13.2584 16.1441 12.7016 16.3044 11.9295C16.3803 11.5541 16.4141 11.2039 16.4394 10.917L16.528 9.24219C16.528 7.91327 17.6079 6.83329 18.9369 6.83329H21.7339C23.0629 6.83329 24.1427 7.91327 24.1427 9.24219V12.0603C24.1427 13.3892 23.0629 14.4692 21.7339 14.4692H19.4346C18.9073 14.4819 18.4686 14.5367 18.1438 14.5958C17.5489 14.7055 17.2409 14.832 17.1481 14.87M10.6428 14.8109C8.35625 14.8109 6.5 12.9505 6.5 10.6555C6.5 8.36046 8.35625 6.5 10.6428 6.5C12.9294 6.5 14.7856 8.36046 14.7856 10.6555C14.7856 12.9505 12.9294 14.8109 10.6428 14.8109ZM10.6428 25.1891C12.9294 25.1891 14.7856 27.0496 14.7856 29.3446C14.7856 31.6396 12.9294 33.5 10.6428 33.5C8.35625 33.5 6.5 31.6396 6.5 29.3446C6.5 27.0496 8.35625 25.1891 10.6428 25.1891ZM33.5 12.1405C33.5 13.4272 32.4538 14.4734 31.1671 14.4734C30.4963 14.4355 29.9352 14.4566 29.526 14.4819C28.8341 14.5283 28.429 14.5536 27.8933 14.7181C27.29 14.9037 26.8598 15.1611 26.615 15.3087C26.5096 15.372 26.0919 15.6294 25.6067 16.0597C25.3325 16.3044 25.0709 16.5364 24.8009 16.9076C24.4254 17.4265 24.261 17.899 24.1766 18.1521C23.9487 18.8356 23.9066 19.7848 23.9192 20.2448L23.8939 21.4808C23.8939 22.7717 22.8519 23.8179 21.5652 23.8179H18.6079C17.3211 23.8179 16.2791 22.7717 16.2791 21.4808V18.515C16.2791 17.2241 17.3211 16.1778 18.6079 16.1778H20.3965C21.0294 16.1778 22.2865 16.2326 23.3539 15.8403C24.2694 15.507 25.1848 14.5789 25.5434 13.5622C25.8852 12.5286 25.8767 11.1533 25.8767 11.1533L25.8852 9.16625C25.8852 7.87952 26.9314 6.83329 28.2181 6.83329H31.1671C32.4538 6.83329 33.5 7.87952 33.5 9.16625V12.1405Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</symbol>
|
||||
<symbol viewBox="0 0 24 24" fill="none" id="fireworks-ai">
|
||||
<path
|
||||
d="M14.45 5.74999L11.9991 11.6956L9.54562 5.74999H7.97237L10.6604 12.2495C10.7678 12.5138 10.9515 12.7402 11.1882 12.8996C11.4248 13.059 11.7036 13.1442 11.9889 13.1444C12.2742 13.1446 12.5531 13.0597 12.79 12.9006C13.0268 12.7415 13.2109 12.5154 13.3186 12.2512L16.0232 5.74999H14.45ZM15.4965 14.808L19.98 10.2195L19.3684 8.75911L14.4719 13.7807C14.272 13.9856 14.1369 14.2448 14.0835 14.5261C14.0301 14.8073 14.0608 15.098 14.1718 15.3619C14.2807 15.624 14.4648 15.848 14.7008 16.0056C14.9369 16.1632 15.2144 16.2473 15.4983 16.2474L15.5 16.25L22.5 16.2325L21.8884 14.7721L15.4983 14.808H15.4965ZM4.02 10.216L4.63162 8.75561L9.52813 13.7772C9.93763 14.1964 10.0557 14.8176 9.82825 15.3584C9.71925 15.6204 9.53511 15.8443 9.29905 16.0019C9.06299 16.1595 8.78557 16.2437 8.50175 16.2439L1.50175 16.2281L1.5 16.2299L2.11163 14.7695L8.50175 14.8062L4.02 10.216Z"
|
||||
@@ -840,43 +828,5 @@
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</symbol>
|
||||
<symbol viewBox="0 0 40 40" fill="currentColor" id="abacus">
|
||||
<path
|
||||
d="M6.93433 9.57807C6.93433 8.92952 7.46008 8.40376 8.10864 8.40376C8.75719 8.40376 9.28295 8.92952 9.28295 9.57807V16.6239C9.28295 17.2725 8.75719 17.7983 8.10864 17.7983C7.46008 17.7983 6.93433 17.2725 6.93433 16.6239V9.57807Z"
|
||||
></path>
|
||||
<path
|
||||
d="M30.7144 25.1377C30.7144 24.4891 31.2402 23.9634 31.8887 23.9634C32.5373 23.9634 33.063 24.4891 33.063 25.1377V31.0092C33.063 31.6578 32.5373 32.1836 31.8887 32.1836C31.2402 32.1836 30.7144 31.6578 30.7144 31.0093V25.1377Z"
|
||||
></path>
|
||||
<path
|
||||
d="M22.7875 30.422C22.7875 29.7735 23.3132 29.2477 23.9618 29.2477C24.6103 29.2477 25.1361 29.7735 25.1361 30.422V34.8257C25.1361 35.4742 24.6103 36 23.9618 36C23.3132 36 22.7875 35.4742 22.7875 34.8257V30.422Z"
|
||||
></path>
|
||||
<path
|
||||
d="M6.93433 28.367C6.93433 27.7185 7.46008 27.1927 8.10864 27.1927C8.75719 27.1927 9.28295 27.7185 9.28295 28.367V31.5964C9.28295 32.245 8.75719 32.7707 8.10864 32.7707C7.46008 32.7707 6.93433 32.245 6.93433 31.5964V28.367Z"
|
||||
></path>
|
||||
<path
|
||||
d="M14.8617 5.46787C14.8617 4.81931 15.3874 4.29355 16.036 4.29355C16.6845 4.29355 17.2103 4.81931 17.2103 5.46787V9.87153C17.2103 10.5201 16.6845 11.0458 16.036 11.0458C15.3874 11.0458 14.8617 10.5201 14.8617 9.87153V5.46787Z"
|
||||
></path>
|
||||
<path
|
||||
d="M30.7144 9.28437C30.7144 8.63582 31.2402 8.11006 31.8887 8.11006C32.5373 8.11006 33.063 8.63582 33.063 9.28437V13.688C33.063 14.3366 32.5373 14.8623 31.8887 14.8623C31.2402 14.8623 30.7144 14.3366 30.7144 13.688V9.28437Z"
|
||||
></path>
|
||||
<path
|
||||
d="M22.7875 5.17431C22.7875 4.52576 23.3132 4 23.9618 4C24.6103 4 25.1361 4.52576 25.1361 5.17431V18.3853C25.1361 19.0339 24.6103 19.5596 23.9618 19.5596C23.3132 19.5596 22.7875 19.0339 22.7875 18.3853V5.17431Z"
|
||||
></path>
|
||||
<path
|
||||
d="M14.8617 21.3212C14.8617 20.6726 15.3874 20.1469 16.036 20.1469C16.6845 20.1469 17.2103 20.6726 17.2103 21.3212V34.5322C17.2103 35.1807 16.6845 35.7065 16.036 35.7065C15.3874 35.7065 14.8617 35.1807 14.8617 34.5322V21.3212Z"
|
||||
></path>
|
||||
<path
|
||||
d="M10.4577 21.9083C10.4577 23.2055 9.40623 24.257 8.10912 24.257C6.81201 24.257 5.7605 23.2055 5.7605 21.9083C5.7605 20.6112 6.81201 19.5597 8.10912 19.5597C9.40623 19.5597 10.4577 20.6112 10.4577 21.9083Z"
|
||||
></path>
|
||||
<path
|
||||
d="M18.3843 14.8624C18.3843 16.1596 17.3328 17.2111 16.0357 17.2111C14.7386 17.2111 13.6871 16.1596 13.6871 14.8624C13.6871 13.5653 14.7386 12.5138 16.0357 12.5138C17.3328 12.5138 18.3843 13.5653 18.3843 14.8624Z"
|
||||
></path>
|
||||
<path
|
||||
d="M26.3101 24.2569C26.3101 25.554 25.2586 26.6056 23.9615 26.6056C22.6644 26.6056 21.6128 25.554 21.6128 24.2569C21.6128 22.9598 22.6644 21.9083 23.9615 21.9083C25.2586 21.9083 26.3101 22.9598 26.3101 24.2569Z"
|
||||
></path>
|
||||
<path
|
||||
d="M34.2378 19.5597C34.2378 20.8568 33.1863 21.9083 31.8892 21.9083C30.5921 21.9083 29.5406 20.8568 29.5406 19.5597C29.5406 18.2626 30.5921 17.2111 31.8892 17.2111C33.1863 17.2111 34.2378 18.2626 34.2378 19.5597Z"
|
||||
></path>
|
||||
</symbol>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 232 KiB |
@@ -31,7 +31,6 @@ export const iconNames = [
|
||||
"ollama-cloud",
|
||||
"nvidia",
|
||||
"nebius",
|
||||
"nano-gpt",
|
||||
"morph",
|
||||
"moonshotai",
|
||||
"moonshotai-cn",
|
||||
@@ -55,7 +54,6 @@ export const iconNames = [
|
||||
"google-vertex-anthropic",
|
||||
"github-models",
|
||||
"github-copilot",
|
||||
"friendli",
|
||||
"fireworks-ai",
|
||||
"fastrouter",
|
||||
"deepseek",
|
||||
@@ -75,7 +73,6 @@ export const iconNames = [
|
||||
"alibaba",
|
||||
"alibaba-cn",
|
||||
"aihubmix",
|
||||
"abacus",
|
||||
] as const
|
||||
|
||||
export type IconName = (typeof iconNames)[number]
|
||||
|
||||
@@ -47,17 +47,9 @@
|
||||
--radius-xl: 0.625rem;
|
||||
|
||||
--shadow-xs:
|
||||
0 1px 2px -0.5px light-dark(hsl(0 0% 0% / 0.04), hsl(0 0% 0% / 0.06)),
|
||||
0 0.5px 1.5px 0 light-dark(hsl(0 0% 0% / 0.025), hsl(0 0% 0% / 0.08)),
|
||||
0 1px 3px 0 light-dark(hsl(0 0% 0% / 0.05), hsl(0 0% 0% / 0.1));
|
||||
0 1px 2px -1px rgba(19, 16, 16, 0.04), 0 1px 2px 0 rgba(19, 16, 16, 0.06), 0 1px 3px 0 rgba(19, 16, 16, 0.08);
|
||||
--shadow-md:
|
||||
0 6px 12px -2px light-dark(hsl(0 0% 0% / 0.075), hsl(0 0% 0% / 0.1)),
|
||||
0 4px 8px -2px light-dark(hsl(0 0% 0% / 0.075), hsl(0 0% 0% / 0.15)),
|
||||
0 1px 2px light-dark(hsl(0 0% 0% / 0.1), hsl(0 0% 0% / 0.15));
|
||||
--shadow-lg:
|
||||
0 16px 48px -6px light-dark(hsl(0 0% 0% / 0.05), hsl(0 0% 0% / 0.15)),
|
||||
0 6px 12px -2px light-dark(hsl(0 0% 0% / 0.025), hsl(0 0% 0% / 0.1)),
|
||||
0 1px 2.5px light-dark(hsl(0 0% 0% / 0.025), hsl(0 0% 0% / 0.1));
|
||||
0 6px 8px -4px rgba(19, 16, 16, 0.12), 0 4px 3px -2px rgba(19, 16, 16, 0.12), 0 1px 2px -1px rgba(19, 16, 16, 0.12);
|
||||
--shadow-xs-border:
|
||||
0 0 0 1px var(--border-base, rgba(11, 6, 0, 0.2)), 0 1px 2px -1px rgba(19, 16, 16, 0.04),
|
||||
0 1px 2px 0 rgba(19, 16, 16, 0.06), 0 1px 3px 0 rgba(19, 16, 16, 0.08);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -551,26 +551,23 @@ The opencode CLI takes the following global flags.
|
||||
|
||||
OpenCode can be configured using environment variables.
|
||||
|
||||
| Variable | Type | Description |
|
||||
| ------------------------------------- | ------- | ------------------------------------------------ |
|
||||
| `OPENCODE_AUTO_SHARE` | boolean | Automatically share sessions |
|
||||
| `OPENCODE_GIT_BASH_PATH` | string | Path to Git Bash executable on Windows |
|
||||
| `OPENCODE_CONFIG` | string | Path to config file |
|
||||
| `OPENCODE_CONFIG_DIR` | string | Path to config directory |
|
||||
| `OPENCODE_CONFIG_CONTENT` | string | Inline json config content |
|
||||
| `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Disable automatic update checks |
|
||||
| `OPENCODE_DISABLE_PRUNE` | boolean | Disable pruning of old data |
|
||||
| `OPENCODE_DISABLE_TERMINAL_TITLE` | boolean | Disable automatic terminal title updates |
|
||||
| `OPENCODE_PERMISSION` | string | Inlined json permissions config |
|
||||
| `OPENCODE_DISABLE_DEFAULT_PLUGINS` | boolean | Disable default plugins |
|
||||
| `OPENCODE_DISABLE_LSP_DOWNLOAD` | boolean | Disable automatic LSP server downloads |
|
||||
| `OPENCODE_ENABLE_EXPERIMENTAL_MODELS` | boolean | Enable experimental models |
|
||||
| `OPENCODE_DISABLE_AUTOCOMPACT` | boolean | Disable automatic context compaction |
|
||||
| `OPENCODE_DISABLE_CLAUDE_CODE` | boolean | Disable reading from `.claude` (prompt + skills) |
|
||||
| `OPENCODE_DISABLE_CLAUDE_CODE_PROMPT` | boolean | Disable reading `~/.claude/CLAUDE.md` |
|
||||
| `OPENCODE_DISABLE_CLAUDE_CODE_SKILLS` | boolean | Disable loading `.claude/skills` |
|
||||
| `OPENCODE_CLIENT` | string | Client identifier (defaults to `cli`) |
|
||||
| `OPENCODE_ENABLE_EXA` | boolean | Enable Exa web search tools |
|
||||
| Variable | Type | Description |
|
||||
| ------------------------------------- | ------- | ---------------------------------------- |
|
||||
| `OPENCODE_AUTO_SHARE` | boolean | Automatically share sessions |
|
||||
| `OPENCODE_GIT_BASH_PATH` | string | Path to Git Bash executable on Windows |
|
||||
| `OPENCODE_CONFIG` | string | Path to config file |
|
||||
| `OPENCODE_CONFIG_DIR` | string | Path to config directory |
|
||||
| `OPENCODE_CONFIG_CONTENT` | string | Inline json config content |
|
||||
| `OPENCODE_DISABLE_AUTOUPDATE` | boolean | Disable automatic update checks |
|
||||
| `OPENCODE_DISABLE_PRUNE` | boolean | Disable pruning of old data |
|
||||
| `OPENCODE_DISABLE_TERMINAL_TITLE` | boolean | Disable automatic terminal title updates |
|
||||
| `OPENCODE_PERMISSION` | string | Inlined json permissions config |
|
||||
| `OPENCODE_DISABLE_DEFAULT_PLUGINS` | boolean | Disable default plugins |
|
||||
| `OPENCODE_DISABLE_LSP_DOWNLOAD` | boolean | Disable automatic LSP server downloads |
|
||||
| `OPENCODE_ENABLE_EXPERIMENTAL_MODELS` | boolean | Enable experimental models |
|
||||
| `OPENCODE_DISABLE_AUTOCOMPACT` | boolean | Disable automatic context compaction |
|
||||
| `OPENCODE_CLIENT` | string | Client identifier (defaults to `cli`) |
|
||||
| `OPENCODE_ENABLE_EXA` | boolean | Enable Exa web search tools |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ OpenCode comes with several built-in formatters for popular languages and framew
|
||||
| ktlint | .kt, .kts | `ktlint` command available |
|
||||
| ruff | .py, .pyi | `ruff` command available with config |
|
||||
| rustfmt | .rs | `rustfmt` command available |
|
||||
| cargofmt | .rs | `cargo fmt` command available |
|
||||
| uv | .py, .pyi | `uv` command available |
|
||||
| rubocop | .rb, .rake, .gemspec, .ru | `rubocop` command available |
|
||||
| standardrb | .rb, .rake, .gemspec, .ru | `standardrb` command available |
|
||||
|
||||
@@ -5,37 +5,86 @@ description: Manage the tools an LLM can use.
|
||||
|
||||
Tools allow the LLM to perform actions in your codebase. OpenCode comes with a set of built-in tools, but you can extend it with [custom tools](/docs/custom-tools) or [MCP servers](/docs/mcp-servers).
|
||||
|
||||
By default, all tools are **enabled** and don't need permission to run. You can control tool behavior through [permissions](/docs/permissions).
|
||||
By default, all tools are **enabled** and don't need permission to run. But you can configure this and control the [permissions](/docs/permissions) through your config.
|
||||
|
||||
---
|
||||
|
||||
## Configure
|
||||
|
||||
Use the `permission` field to control tool behavior. You can allow, deny, or require approval for each tool.
|
||||
You can configure tools globally or per agent. Agent-specific configs override global settings.
|
||||
|
||||
By default, all tools are set to `true`. To disable a tool, set it to `false`.
|
||||
|
||||
---
|
||||
|
||||
### Global
|
||||
|
||||
Disable or enable tools globally using the `tools` option.
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"edit": "deny",
|
||||
"bash": "ask",
|
||||
"webfetch": "allow"
|
||||
"tools": {
|
||||
"write": false,
|
||||
"bash": false,
|
||||
"webfetch": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also use wildcards to control multiple tools at once. For example, to require approval for all tools from an MCP server:
|
||||
You can also use wildcards to control multiple tools at once. For example, to disable all tools from an MCP server:
|
||||
|
||||
```json title="opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"mymcp_*": "ask"
|
||||
"tools": {
|
||||
"mymcp_*": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[Learn more](/docs/permissions) about configuring permissions.
|
||||
---
|
||||
|
||||
### Per agent
|
||||
|
||||
Override global tool settings for specific agents using the `tools` config in the agent definition.
|
||||
|
||||
```json title="opencode.json" {3-6,9-12}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"tools": {
|
||||
"write": true,
|
||||
"bash": true
|
||||
},
|
||||
"agent": {
|
||||
"plan": {
|
||||
"tools": {
|
||||
"write": false,
|
||||
"bash": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For example, here the `plan` agent overrides the global config to disable `write` and `bash` tools.
|
||||
|
||||
You can also configure tools for agents in Markdown.
|
||||
|
||||
```markdown title="~/.config/opencode/agent/readonly.md"
|
||||
---
|
||||
description: Read-only analysis agent
|
||||
mode: subagent
|
||||
tools:
|
||||
write: false
|
||||
edit: false
|
||||
bash: false
|
||||
---
|
||||
|
||||
Analyze code without making any modifications.
|
||||
```
|
||||
|
||||
[Learn more](/docs/agents#tools) about configuring tools per agent.
|
||||
|
||||
---
|
||||
|
||||
@@ -52,8 +101,8 @@ Execute shell commands in your project environment.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"bash": "allow"
|
||||
"tools": {
|
||||
"bash": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -69,8 +118,8 @@ Modify existing files using exact string replacements.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"edit": "allow"
|
||||
"tools": {
|
||||
"edit": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -86,18 +135,14 @@ Create new files or overwrite existing ones.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"edit": "allow"
|
||||
"tools": {
|
||||
"write": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use this to allow the LLM to create new files. It will overwrite existing files if they already exist.
|
||||
|
||||
:::note
|
||||
The `write` tool is controlled by the `edit` permission, which covers all file modifications (`edit`, `write`, `patch`, `multiedit`).
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
### read
|
||||
@@ -107,8 +152,8 @@ Read file contents from your codebase.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"read": "allow"
|
||||
"tools": {
|
||||
"read": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -124,8 +169,8 @@ Search file contents using regular expressions.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"grep": "allow"
|
||||
"tools": {
|
||||
"grep": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -141,8 +186,8 @@ Find files by pattern matching.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"glob": "allow"
|
||||
"tools": {
|
||||
"glob": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -158,8 +203,8 @@ List files and directories in a given path.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"list": "allow"
|
||||
"tools": {
|
||||
"list": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -179,8 +224,8 @@ This tool is only available when `OPENCODE_EXPERIMENTAL_LSP_TOOL=true` (or `OPEN
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"lsp": "allow"
|
||||
"tools": {
|
||||
"lsp": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -198,18 +243,14 @@ Apply patches to files.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"edit": "allow"
|
||||
"tools": {
|
||||
"patch": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This tool applies patch files to your codebase. Useful for applying diffs and patches from various sources.
|
||||
|
||||
:::note
|
||||
The `patch` tool is controlled by the `edit` permission, which covers all file modifications (`edit`, `write`, `patch`, `multiedit`).
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
### skill
|
||||
@@ -219,12 +260,14 @@ Load a [skill](/docs/skills) (a `SKILL.md` file) and return its content in the c
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"skill": "allow"
|
||||
"tools": {
|
||||
"skill": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can control approval prompts for loading skills via [permissions](/docs/permissions) using `permission.skill`.
|
||||
|
||||
---
|
||||
|
||||
### todowrite
|
||||
@@ -234,8 +277,8 @@ Manage todo lists during coding sessions.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"todowrite": "allow"
|
||||
"tools": {
|
||||
"todowrite": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -243,7 +286,7 @@ Manage todo lists during coding sessions.
|
||||
Creates and updates task lists to track progress during complex operations. The LLM uses this to organize multi-step tasks.
|
||||
|
||||
:::note
|
||||
This tool is disabled for subagents by default, but you can enable it manually. [Learn more](/docs/agents/#permissions)
|
||||
This tool is disabled for subagents by default, but you can enable it manually. [Learn more](/docs/agents/#tools)
|
||||
:::
|
||||
|
||||
---
|
||||
@@ -255,8 +298,8 @@ Read existing todo lists.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"todoread": "allow"
|
||||
"tools": {
|
||||
"todoread": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -264,7 +307,7 @@ Read existing todo lists.
|
||||
Reads the current todo list state. Used by the LLM to track what tasks are pending or completed.
|
||||
|
||||
:::note
|
||||
This tool is disabled for subagents by default, but you can enable it manually. [Learn more](/docs/agents/#permissions)
|
||||
This tool is disabled for subagents by default, but you can enable it manually. [Learn more](/docs/agents/#tools)
|
||||
:::
|
||||
|
||||
---
|
||||
@@ -276,8 +319,8 @@ Fetch web content.
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"webfetch": "allow"
|
||||
"tools": {
|
||||
"webfetch": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -286,30 +329,6 @@ Allows the LLM to fetch and read web pages. Useful for looking up documentation
|
||||
|
||||
---
|
||||
|
||||
### question
|
||||
|
||||
Ask the user questions during execution.
|
||||
|
||||
```json title="opencode.json" {4}
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"permission": {
|
||||
"question": "allow"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This tool allows the LLM to ask the user questions during a task. It's useful for:
|
||||
|
||||
- Gathering user preferences or requirements
|
||||
- Clarifying ambiguous instructions
|
||||
- Getting decisions on implementation choices
|
||||
- Offering choices about what direction to take
|
||||
|
||||
Each question includes a header, the question text, and a list of options. Users can select from the provided options or type a custom answer. When there are multiple questions, users can navigate between them before submitting all answers.
|
||||
|
||||
---
|
||||
|
||||
## Custom tools
|
||||
|
||||
Custom tools let you define your own functions that the LLM can call. These are defined in your config file and can execute arbitrary code.
|
||||
|
||||
@@ -4,7 +4,7 @@ import { $ } from "bun"
|
||||
import { tmpdir } from "os"
|
||||
import { join } from "path"
|
||||
|
||||
const FORK_REPO = "anomalyco/zed-extensions"
|
||||
const FORK_REPO = "sst/zed-extensions"
|
||||
const UPSTREAM_REPO = "zed-industries/extensions"
|
||||
const EXTENSION_NAME = "opencode"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.1.7",
|
||||
"version": "1.1.6",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user