mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-04 09:16:20 -04:00
Compare commits
341 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b41b10faeb | |||
| 9644a008a4 | |||
| ccb6b7c3ea | |||
| f95f877e5f | |||
| fe96d4aebd | |||
| 3a95d56144 | |||
| ecd8be7593 | |||
| a72c3880bb | |||
| ccd8676bee | |||
| 5a533e366f | |||
| 77429f5982 | |||
| 6cfe4bc34f | |||
| 95013d2936 | |||
| 14a5529793 | |||
| 0b2e8707e3 | |||
| adf178a6b9 | |||
| 78f85b1cd6 | |||
| 888dc67cc3 | |||
| fd9ee435ec | |||
| 1c25b2f298 | |||
| ea9fffce2e | |||
| 516f0266b8 | |||
| 39397deb8d | |||
| 65fd2e5c91 | |||
| c5fe32fbb1 | |||
| c48b77d6f9 | |||
| 64fd7a2027 | |||
| ae259d87f0 | |||
| 50374abab8 | |||
| 0497e8badf | |||
| 2a08f8f5f4 | |||
| 9353559088 | |||
| 6aff2ee169 | |||
| 254a481e5d | |||
| 26885e7118 | |||
| dba08013d1 | |||
| fc09646973 | |||
| c3b9c9d888 | |||
| 203885d241 | |||
| 728e86b287 | |||
| d5aa79c73a | |||
| eb6ff0c1e0 | |||
| 0df44c4e91 | |||
| 561070fbc2 | |||
| b0e41ff2c4 | |||
| 977a40af68 | |||
| dffecb6478 | |||
| 7f57d2a9ac | |||
| 377d5d2287 | |||
| 38bb38ecb2 | |||
| 3a149ba71c | |||
| 14df88eab5 | |||
| e12cb7fb6b | |||
| d4f7039932 | |||
| e0ec9be238 | |||
| 2b34df94fa | |||
| 68f225a11d | |||
| e9f5d34096 | |||
| d3459eb740 | |||
| be73f465df | |||
| f14eafe9db | |||
| b7e4f1ef74 | |||
| efd5f0a80f | |||
| 7135bc4fb5 | |||
| 7cf8d1ca6d | |||
| 78bca5c707 | |||
| 82db86e2a1 | |||
| 476e3fc382 | |||
| a12d50e15a | |||
| 1b9b260458 | |||
| a8983bd2c7 | |||
| 709af58612 | |||
| bcbbf32569 | |||
| 7a8e7c88f4 | |||
| a22676720d | |||
| f63a451b6f | |||
| 8b6a2b173b | |||
| 933dbfdb36 | |||
| b44bc0ad07 | |||
| 436cd39d14 | |||
| ed6dc879be | |||
| 96d53c6716 | |||
| 911fb7063a | |||
| 27e8e2e22b | |||
| 39c6dd1c32 | |||
| a09447bc9b | |||
| 16815473a4 | |||
| 1f47bbfce2 | |||
| 33ed95f09c | |||
| 2152624f37 | |||
| 7d84cb05ef | |||
| eba0bd0397 | |||
| 9aab24e95e | |||
| a4fed69a82 | |||
| 41a3cfcdd9 | |||
| 72845e09fc | |||
| abaab29cb3 | |||
| 30936a9bca | |||
| a9144eccf8 | |||
| 458ec7b372 | |||
| d46c02ba73 | |||
| caedf36844 | |||
| 4c6e2a92ce | |||
| bf58fae51f | |||
| eb3476660f | |||
| 7d48a22a63 | |||
| 3f0fc22b6e | |||
| 2409c7a3d5 | |||
| 379adee35c | |||
| 2ef1a5991c | |||
| 83c638eaac | |||
| cb93114424 | |||
| 04d236ceed | |||
| 5455fed78e | |||
| 6dba0cc498 | |||
| c07ac0db2f | |||
| 20b090e493 | |||
| cb54824f2f | |||
| 3adfb970bf | |||
| 5ecd19db9f | |||
| 299daa2815 | |||
| 7457139849 | |||
| 373cd08b98 | |||
| f52424e05f | |||
| 4a42caef2c | |||
| 7d2618637f | |||
| fbb95a6ee3 | |||
| 40233595b0 | |||
| 403164a6ac | |||
| 1fb2ecc721 | |||
| 27c9b130c7 | |||
| 5fecf7ae9e | |||
| a0d64b12c9 | |||
| d6b897188a | |||
| 39dfbb53d0 | |||
| a39db9c6f6 | |||
| 6ca60d9204 | |||
| b3934992d1 | |||
| 4de7e90d8d | |||
| 917eca4f55 | |||
| 3cf71808c4 | |||
| f266e829cf | |||
| 3df5556223 | |||
| fc29f99190 | |||
| beb586a383 | |||
| 5668d36477 | |||
| 3706b33ee0 | |||
| 85e8ac3b36 | |||
| 91ca75c4dc | |||
| 262ef4fdc2 | |||
| 74e5644646 | |||
| 7de5aa08ff | |||
| 9495e3e903 | |||
| 1e73b76ea6 | |||
| 6697cf3fd8 | |||
| 87aa62d1e2 | |||
| 3136b1ba97 | |||
| 246dea3a58 | |||
| 4b1c7f28a0 | |||
| 9410eda4e3 | |||
| f014686b07 | |||
| 8b68dc0d7f | |||
| 2f4d36d64e | |||
| 6f2ff35656 | |||
| cd00422298 | |||
| 34ee94232f | |||
| 4d91f0cf28 | |||
| bf18cc971f | |||
| 5e1a1ed2e2 | |||
| bd7f7ad98b | |||
| af72cec799 | |||
| 55552c521f | |||
| 2b611a5b14 | |||
| 45a437cbb2 | |||
| 439ec9b76f | |||
| ae3fefb42a | |||
| f1407e41c4 | |||
| c88ca972cf | |||
| 928c37bc02 | |||
| afff74eb2c | |||
| 3a669d528b | |||
| 6789214b31 | |||
| 6636683323 | |||
| 3af9f64265 | |||
| 5a23bdcb69 | |||
| e78726854a | |||
| 472d0f376e | |||
| c8fde60ad4 | |||
| a4b6047e64 | |||
| 6387f955cf | |||
| 5691f36e98 | |||
| 74e7a378bb | |||
| fced9c5a2f | |||
| 20445ca031 | |||
| 6fc9405618 | |||
| c86066efef | |||
| fa9ba2938e | |||
| 63e24880e5 | |||
| eaf42c4ab0 | |||
| 8e10ab0aa7 | |||
| b329fcceb2 | |||
| bd83177c48 | |||
| 06cb0de0b1 | |||
| 3ca89ac796 | |||
| 3aa2860058 | |||
| ad63cf1590 | |||
| aa56750f8b | |||
| 4aaed42640 | |||
| f69f677a32 | |||
| 8289883de8 | |||
| 003c22b4a3 | |||
| 1982d9832b | |||
| 90f0576222 | |||
| e34822db6b | |||
| b19ae6547f | |||
| 6b074ed718 | |||
| e687eb936b | |||
| 797cb530e5 | |||
| 0a5bed2bc2 | |||
| ba41dadc91 | |||
| 451876b0bd | |||
| 91561bbdfb | |||
| 018ce34b39 | |||
| ce82b21073 | |||
| 1f37c26574 | |||
| cb3e28d207 | |||
| 8f1e13f299 | |||
| 2fe68b5e91 | |||
| 2ace237938 | |||
| 5b62211167 | |||
| 60b6229c4e | |||
| 4caec80e67 | |||
| e9d4ca7e0f | |||
| d71454c701 | |||
| 884c256033 | |||
| 78235385dd | |||
| fd213e6df6 | |||
| 3726052307 | |||
| 7d33a6f7c9 | |||
| 7a035d7fc0 | |||
| 9151af7045 | |||
| 15bcbb1d7a | |||
| 4d3294727c | |||
| aae0e89519 | |||
| 93a7b4ab76 | |||
| 0ebe74b625 | |||
| b9dae8593c | |||
| d8f9388610 | |||
| be14739cce | |||
| 762588c251 | |||
| d6e54e9042 | |||
| d4fd528152 | |||
| de185559dd | |||
| bc5ce5eab1 | |||
| b10d617c80 | |||
| 18466b8020 | |||
| 71ec022b47 | |||
| f7eeb08942 | |||
| 9205dfe724 | |||
| a3776429aa | |||
| c0e43c0c65 | |||
| 08c5a2a5e8 | |||
| 7077c70d60 | |||
| 1fd8bf526d | |||
| 6d9539f469 | |||
| e5101d9651 | |||
| b0151e1d02 | |||
| c3637753bc | |||
| 48fc9e3cc3 | |||
| 82a482b36d | |||
| 7fac84319d | |||
| 0a5e617da8 | |||
| b5f92c9f48 | |||
| 2070fd9bc2 | |||
| 48dc05eee7 | |||
| 84bb706537 | |||
| be8cfa7e08 | |||
| 56a789d926 | |||
| 5409151ad4 | |||
| f90d154465 | |||
| 078385d386 | |||
| beaaa174ea | |||
| fb59606bb4 | |||
| 846d548154 | |||
| 84336e4f91 | |||
| 01a5c69244 | |||
| d78f91afeb | |||
| 33762292f7 | |||
| 5d6aa3b41a | |||
| 683aca5dbe | |||
| 92025e9a47 | |||
| 411e053572 | |||
| b862d178bf | |||
| bda0ddc207 | |||
| 58ba99e505 | |||
| 6ee817d041 | |||
| dfeb1b5051 | |||
| 61a7f6db35 | |||
| 6446b8ae3b | |||
| ae53163cad | |||
| 41202819a4 | |||
| a31698f99b | |||
| 6248542c49 | |||
| d25c91e5eb | |||
| 5d63020dcd | |||
| 062f54590e | |||
| a76c6918d2 | |||
| 2b91a6f210 | |||
| 10579cceb2 | |||
| 25702e01ce | |||
| ecc5c44d9a | |||
| f5a0b920a2 | |||
| 2caa016fe1 | |||
| 6861fedd09 | |||
| 3d072112ce | |||
| bdfea046db | |||
| a2d08fb63b | |||
| 5a55135d89 | |||
| 8870d36e0f | |||
| eb923c27ca | |||
| 9903abc704 | |||
| 225a1fbf35 | |||
| 93159bccbf | |||
| fab8ec4f54 | |||
| a3035c53ea | |||
| 36c416e143 | |||
| e1e0304a96 | |||
| 71c3a7c8f2 | |||
| ecdfff5a42 | |||
| 4b948c5d74 | |||
| cd56c51e2d | |||
| 43e39d7f68 | |||
| 7a17925495 | |||
| 5acb2530b4 | |||
| 44a6787359 | |||
| 42e6b7db32 | |||
| 2c02f8bace | |||
| 2ec20e576b | |||
| 20f47fec7a | |||
| 65210f2d97 | |||
| af0b7ffae7 |
@@ -6,7 +6,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- ci
|
- ci
|
||||||
- dev
|
- dev
|
||||||
- v2
|
|
||||||
- beta
|
- beta
|
||||||
- fix/npm-native-binary-install
|
- fix/npm-native-binary-install
|
||||||
- snapshot-*
|
- snapshot-*
|
||||||
@@ -32,9 +31,6 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
env:
|
|
||||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'v2' && 'next') || '' }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
version:
|
version:
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
@@ -126,7 +122,7 @@ jobs:
|
|||||||
- build-cli
|
- build-cli
|
||||||
- version
|
- version
|
||||||
runs-on: blacksmith-4vcpu-windows-2025
|
runs-on: blacksmith-4vcpu-windows-2025
|
||||||
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2'
|
if: github.repository == 'anomalyco/opencode'
|
||||||
env:
|
env:
|
||||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
@@ -225,7 +221,7 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- build-cli
|
- build-cli
|
||||||
- version
|
- version
|
||||||
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'v2'
|
if: github.repository == 'anomalyco/opencode'
|
||||||
continue-on-error: false
|
continue-on-error: false
|
||||||
env:
|
env:
|
||||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
@@ -451,7 +447,6 @@ jobs:
|
|||||||
path: packages/opencode/dist
|
path: packages/opencode/dist
|
||||||
|
|
||||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||||
if: github.ref_name != 'v2'
|
|
||||||
with:
|
with:
|
||||||
name: opencode-cli-signed-windows
|
name: opencode-cli-signed-windows
|
||||||
path: packages/opencode/dist
|
path: packages/opencode/dist
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
- v2
|
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
@@ -75,9 +74,13 @@ jobs:
|
|||||||
working-directory: packages/client
|
working-directory: packages/client
|
||||||
run: bun run check:generated
|
run: bun run check:generated
|
||||||
|
|
||||||
|
- name: Run HttpApi exerciser gates
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
working-directory: packages/opencode
|
||||||
|
run: bun run test:httpapi
|
||||||
|
|
||||||
e2e:
|
e2e:
|
||||||
name: e2e (${{ matrix.settings.name }})
|
name: e2e (${{ matrix.settings.name }})
|
||||||
if: github.ref_name != 'v2' && github.head_ref != 'v2'
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ name: typecheck
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [dev, v2]
|
branches: [dev]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [dev, v2]
|
branches: [dev]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ tmp
|
|||||||
dist
|
dist
|
||||||
ts-dist
|
ts-dist
|
||||||
.turbo
|
.turbo
|
||||||
.typecheck-profiles
|
|
||||||
**/.serena
|
**/.serena
|
||||||
.serena/
|
.serena/
|
||||||
**/.omo
|
**/.omo
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
name: sample-skill
|
|
||||||
description: Use when the user says sample skill, skill demo, or asks how an opencode SKILL.md should be structured; demonstrates a tiny project-local skill with practical assistant workflow guidance.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Sample Skill
|
|
||||||
|
|
||||||
This is a minimal project-local opencode skill. It exists as a reference for how a skill is structured and as a tiny reusable workflow the assistant can load when the user asks for a skill example.
|
|
||||||
|
|
||||||
## When To Use
|
|
||||||
|
|
||||||
- Use when the user asks for a sample skill or skill template.
|
|
||||||
- Use when demonstrating the required `SKILL.md` frontmatter and body format.
|
|
||||||
- Do not use for unrelated coding tasks just because a skill exists.
|
|
||||||
|
|
||||||
## Workflow
|
|
||||||
|
|
||||||
- Confirm the specific outcome the user wants if the request is ambiguous.
|
|
||||||
- Inspect the relevant files before changing anything.
|
|
||||||
- Make the smallest correct change.
|
|
||||||
- Verify the result with a focused read, typecheck, test, or other lightweight check when available.
|
|
||||||
- Summarize the changed files and any required restart or reload step.
|
|
||||||
|
|
||||||
## Example Response Style
|
|
||||||
|
|
||||||
When this skill is relevant, keep responses direct and actionable:
|
|
||||||
|
|
||||||
```text
|
|
||||||
I created a project-local skill at .opencode/skills/sample-skill/SKILL.md.
|
|
||||||
Restart opencode for the new skill to be discovered by future sessions.
|
|
||||||
```
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
- To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
|
- To regenerate the legacy JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
|
||||||
- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly.
|
- After changing the public Protocol or Server `HttpApi`, run `bun run generate` from `packages/client`. Do not edit `src/generated` or `src/generated-effect` directly.
|
||||||
- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
|
- Keep runtime dependencies directed from Schema to Core and Protocol, then from Core and Protocol to Server. Client runtime code may depend on Schema and Protocol but never Core or Server; `sdk-next` composes Client, Core, and Server.
|
||||||
- Do not modify `packages/opencode` unless the user explicitly asks for V1 work. `packages/opencode` is the V1 implementation and is present for reference only. New implementation changes should land in the V2 package set: `packages/core`, `packages/cli`, `packages/server`, `packages/protocol`, `packages/schema`, and related generated client surfaces when required.
|
|
||||||
- The default branch in this repo is `dev`.
|
- The default branch in this repo is `dev`.
|
||||||
- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
|
- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
|
||||||
|
|
||||||
@@ -151,15 +150,12 @@ const table = sqliteTable("session", {
|
|||||||
|
|
||||||
## V2 Session Core
|
## V2 Session Core
|
||||||
|
|
||||||
- Keep durable events minimal: record irreducible new facts and do not repeat state derivable by folding the ordered aggregate history. Enrich projections and read models with previous or derived state when consumers need self-contained views.
|
|
||||||
- Keep durable prompt admission separate from model execution. `SessionV2.prompt(...)` admits one durable `session_input` row before scheduling advisory `SessionExecution.wake(sessionID)` unless `resume: false` requests admit-only behavior. The serialized runner promotes admitted inputs into visible user messages at safe boundaries.
|
- Keep durable prompt admission separate from model execution. `SessionV2.prompt(...)` admits one durable `session_input` row before scheduling advisory `SessionExecution.wake(sessionID)` unless `resume: false` requests admit-only behavior. The serialized runner promotes admitted inputs into visible user messages at safe boundaries.
|
||||||
- Reusing a Session ID adopts the existing Session. Reusing a prompt message ID reconciles an exact retry only when Session, prompt, and delivery mode match; conflicting reuse fails. Historical projected prompts lazily synthesize promoted inbox records during exact retry.
|
- Reusing a Session ID adopts the existing Session. Reusing a prompt message ID reconciles an exact retry only when Session, prompt, and delivery mode match; conflicting reuse fails. Historical projected prompts lazily synthesize promoted inbox records during exact retry.
|
||||||
- Keep `SessionExecution` process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through `SessionStore` plus `LocationServiceMap.get(session.location)` only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; idle or missing interruption is a no-op.
|
- Keep `SessionExecution` process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through `SessionStore` plus `LocationServiceMap.get(session.location)` only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; idle or missing interruption is a no-op.
|
||||||
- Keep `SessionRunner`, model resolution, tool registry, permissions, and filesystem Location-scoped. Omitted `Location.workspaceID` means implicit-local placement; explicit workspace identity remains reserved for future placement semantics.
|
- Keep `SessionRunner`, model resolution, tool registry, permissions, and filesystem Location-scoped. Omitted `Location.workspaceID` means implicit-local placement; explicit workspace identity remains reserved for future placement semantics.
|
||||||
- Preserve one explicit `llm.stream(request)` call per step and reload projected history before durable continuation. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop.
|
- Preserve one explicit `llm.stream(request)` call per provider turn and reload projected history before durable continuation. Do not bridge through legacy `SessionPrompt.loop(...)` or delegate orchestration to an in-memory tool loop.
|
||||||
- Keep local Session drains process-local until clustering is implemented. `SessionRunCoordinator` joins explicit same-Session resumes, coalesces prompt wakeups, and allows different Sessions to run concurrently. Advisory wakes drain eligible durable inbox rows only; post-crash continuation recovery requires a separate explicit design before it may retry provider work. A drain has no durable identity or transcript boundary.
|
- Keep local Session drains process-local until clustering is implemented. `SessionRunCoordinator` joins explicit same-Session resumes, coalesces prompt wakeups, and allows different Sessions to run concurrently. Advisory wakes drain eligible durable inbox rows only; post-crash continuation recovery requires a separate explicit design before it may retry provider work. A drain has no durable identity or transcript boundary.
|
||||||
- Keep delivery vocabulary explicit. Prompts steer by default and promote at the next safe step boundary while the current drain requires continuation. An explicit `queue` input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's step allowance; a batch of steers resets it once.
|
- Keep delivery vocabulary explicit. Prompts steer by default and promote at the next safe provider-turn boundary while the current drain requires continuation. An explicit `queue` input remains pending until the Session would otherwise become idle; promote one queued input at that boundary, then reevaluate continuation before promoting another. Promoting any new user input resets the selected agent's provider-turn allowance; a batch of steers resets it once.
|
||||||
- One step is one logical LLM call; its durable record covers only the model-visible span. Do not write "provider turn", and do not use bare "turn" for a single call: "turn" is reserved for the future assistant-turn unit containing all steps from prompt promotion until the session would go idle.
|
|
||||||
- Keep EventV2 replay owner claims separate from clustered Session execution ownership.
|
- Keep EventV2 replay owner claims separate from clustered Session execution ownership.
|
||||||
- Keep the Instructions algebra and built-ins in `src/instructions`; keep instruction producers with their observed domains, and keep Session History selection plus `InstructionCheckpoint` and `InstructionEntry` persistence Session-owned. `InstructionDiscovery` observes ambient global and upward-project instructions. The runner composes built-ins, discovery, guidance, and entries explicitly in `loadInstructions`; there is no instruction registry.
|
- Keep the System Context algebra, registry, and built-ins in `src/system-context`; keep Context Source producers with their observed domains, and keep Session History selection plus Context Epoch persistence Session-owned.
|
||||||
- The durable `Instructions.Applied` record is what the model was last told, per instruction source. Reconciliation narrates drift through `session.instructions.updated` and never rewrites the baseline; only completed compaction rebaselines, while Session movement or committed revert resets the `InstructionCheckpoint`. Unavailable sources keep the model's prior belief, blocking only a Session's first instruction baseline.
|
|
||||||
|
|||||||
+74
-94
@@ -4,47 +4,40 @@ OpenCode sessions preserve durable conversational history while assembling the r
|
|||||||
|
|
||||||
## Language
|
## Language
|
||||||
|
|
||||||
**Model Context**:
|
**System Context**:
|
||||||
The complete model-visible input assembled for one **Step**, including system instructions, **Session History**, tool definitions, and step-local additions. **Instructions** are one component of Model Context, not a synonym for it.
|
The structured collection of contextual facts presented to the model as initial instructions and chronological updates.
|
||||||
_Avoid_: System Context
|
_Avoid_: System prompt
|
||||||
|
|
||||||
**Instructions**:
|
|
||||||
The opaque algebra of independently refreshable typed instruction sources that render the durable instruction baseline and chronological updates shown to the model.
|
|
||||||
_Avoid_: Model Context, System Context
|
|
||||||
|
|
||||||
**Session History**:
|
**Session History**:
|
||||||
The projected chronological conversation selected for a **Step** after applying the active compaction and **InstructionCheckpoint** baseline cutoffs.
|
The projected chronological conversation selected for a provider turn after applying the active compaction and **Context Epoch** cutoffs.
|
||||||
_Avoid_: Session Context
|
_Avoid_: Session Context
|
||||||
|
|
||||||
**Instruction Source**:
|
**Context Source**:
|
||||||
One independently observed typed value within **Instructions**, represented by a stable namespaced key, JSON codec, loader, pure baseline/update renderers, and an optional removal renderer.
|
One independently observed typed value within the **System Context**, represented by a stable key, JSON codec, infallible loader, pure baseline/update renderers, and an optional removal renderer for dynamic sources.
|
||||||
_Avoid_: Prompt fragment
|
_Avoid_: Prompt fragment
|
||||||
|
|
||||||
**InstructionEntry**:
|
**System Context Registry**:
|
||||||
One API-managed, durable, per-Session instruction value. Its slash-free client key maps to the `api/<key>` **Instruction Source** key. Entries deliberately render to the model as mechanism-neutral `<context>` blocks: the model sees session context, not how it was attached.
|
The Location-scoped registry of ordered, scoped producers that contribute to the current **System Context**.
|
||||||
|
|
||||||
**InstructionDiscovery**:
|
**Mid-Conversation System Message**:
|
||||||
The Location-scoped service that observes ambient global and upward-project `AGENTS.md` files as one ordered aggregate **Instruction Source**.
|
A durable chronological instruction that tells the model the newly effective state of a changed **Context Source**.
|
||||||
|
_Avoid_: System update, system notification, raw text diff
|
||||||
|
|
||||||
**InstructionCheckpoint**:
|
**Context Epoch**:
|
||||||
The Session-owned durable instruction baseline, baseline sequence, and `Instructions.Applied` record used to prepare later Steps.
|
The span during which one initially rendered **System Context** remains the immutable provider-cache baseline, ending at completed compaction, Session movement, or an incompatible context transition that requires a fresh baseline.
|
||||||
|
|
||||||
**Instruction Update**:
|
**Baseline System Context**:
|
||||||
A durable chronological System message published as `session.instructions.updated` that tells the model the newly effective state of one or more changed **Instruction Sources**.
|
The full **System Context** rendered at the start of a **Context Epoch**.
|
||||||
_Avoid_: System notification, raw text diff
|
|
||||||
|
|
||||||
**Instruction Baseline**:
|
|
||||||
The exact joined instruction text stored by **InstructionCheckpoint** and sent as immutable provider-cache prefix state until completed compaction rebaselines it or Session movement or committed revert resets it.
|
|
||||||
_Avoid_: Live system prompt
|
_Avoid_: Live system prompt
|
||||||
|
|
||||||
**Applied Instructions**:
|
**Context Snapshot**:
|
||||||
The overwriteable model-hidden `Instructions.Applied` record in **InstructionCheckpoint**, containing what the model was last told per **Instruction Source**.
|
The overwriteable model-hidden JSON state used to compare each **Context Source** with the value last admitted to a provider turn.
|
||||||
|
|
||||||
**Unavailable Instruction Source**:
|
**Unavailable Context**:
|
||||||
An expected temporary inability to observe an **Instruction Source** value; the runtime retains its prior effective state and emits no update, while an unavailable source blocks creation of the first complete **Instruction Baseline**.
|
An expected temporary inability to observe a **Context Source** value; the runtime retains its prior effective state and emits no update, or omits it until first successfully loaded.
|
||||||
|
|
||||||
**Safe Step Boundary**:
|
**Safe Provider-Turn Boundary**:
|
||||||
The point during Step preparation, after prior tool settlement and before durable input promotion, where instruction changes may be admitted chronologically.
|
The point immediately before a provider call, after durable input promotion and any required tool settlement, where context changes may be admitted chronologically.
|
||||||
|
|
||||||
**Admitted Prompt**:
|
**Admitted Prompt**:
|
||||||
A durable user input accepted into the Session inbox but not yet included in **Session History**.
|
A durable user input accepted into the Session inbox but not yet included in **Session History**.
|
||||||
@@ -52,24 +45,11 @@ A durable user input accepted into the Session inbox but not yet included in **S
|
|||||||
**Prompt Promotion**:
|
**Prompt Promotion**:
|
||||||
The durable transition that removes an **Admitted Prompt** from pending input and appends its user message to **Session History**.
|
The durable transition that removes an **Admitted Prompt** from pending input and appends its user message to **Session History**.
|
||||||
|
|
||||||
**Step**:
|
**Provider Turn**:
|
||||||
One logical LLM call spanning pre-flight instruction checkpoint preparation, input promotion, request build, and compaction check; the provider stream; and tool settlement.
|
One request to a model provider and the response projected from that request.
|
||||||
_Avoid_: provider turn, turn (unqualified)
|
|
||||||
|
|
||||||
**Physical Attempt**:
|
|
||||||
One actual provider request on the wire in service of a **Step**; most Steps have one Physical Attempt, while overflow-triggered compaction recovery may give one Step two.
|
|
||||||
|
|
||||||
**Assistant Turn**:
|
|
||||||
A reserved name for the not-yet-modeled unit containing all **Steps** from prompt promotion until the assistant yields the floor; do not reify it until something durable needs it.
|
|
||||||
|
|
||||||
**Settlement**:
|
|
||||||
The terminal transition for a unit of work: Step and tool settlement are durable, while drain and execution settlement are coordinator-observed.
|
|
||||||
|
|
||||||
**Execution**:
|
|
||||||
One session-scoped coordinator busy period from first wake until idle. An Execution is process-local coordination rather than a durable domain entity.
|
|
||||||
|
|
||||||
**Session Drain**:
|
**Session Drain**:
|
||||||
One process-local execution span that promotes eligible input and runs required **Steps** until no immediate continuation remains. A Session Drain has no durable identity or transcript boundary.
|
One process-local execution span that promotes eligible input and runs required **Provider Turns** until no immediate continuation remains. A Session Drain has no durable identity or transcript boundary.
|
||||||
|
|
||||||
**Model Tool Output**:
|
**Model Tool Output**:
|
||||||
The bounded projection of a Core-executed tool result persisted in Session history and replayed to the model. A tool may shape this projection semantically, but the Tool Registry enforces the final size limit.
|
The bounded projection of a Core-executed tool result persisted in Session history and replayed to the model. A tool may shape this projection semantically, but the Tool Registry enforces the final size limit.
|
||||||
@@ -107,52 +87,52 @@ _Avoid_: Response envelope
|
|||||||
|
|
||||||
## Relationships
|
## Relationships
|
||||||
|
|
||||||
- **Instructions** is an opaque carrier composed from zero or more **Instruction Sources**.
|
- A **System Context** is an opaque carrier composed from zero or more **Context Sources**.
|
||||||
- **Model Context** is broader than **Instructions**. For each **Step**, the runner assembles the selected agent or provider system text, the **Instruction Baseline**, **Session History**, available tools, and step-local additions into one model request.
|
- **Session History** contains projected conversational messages and admitted **Mid-Conversation System Messages**; the active **Baseline System Context** remains separate provider-request state.
|
||||||
- **Session History** contains projected conversational messages and admitted **Instruction Updates**; the active **Instruction Baseline** remains separate provider-request state.
|
- The **System Context Registry** uses stable-keyed scoped contributions to assemble the current **System Context**; contributor removal naturally removes its sources at the next **Safe Provider-Turn Boundary**.
|
||||||
- The runner explicitly loads and combines instruction built-ins, **InstructionDiscovery**, selected-agent skill guidance, reference guidance, MCP guidance, and **InstructionEntry** values. There is no instruction registry.
|
- A changed **Context Source** may produce one **Mid-Conversation System Message** containing its newly effective state.
|
||||||
- `Instructions.combine(...)` preserves caller order and rejects duplicate stable namespaced source keys. The runner loads its producers concurrently, then combines them in its fixed declared order.
|
- A **Mid-Conversation System Message** persists the exact combined rendered text sent to the model.
|
||||||
- Each **Instruction Source** loader returns one coherent typed value or explicitly reports unavailability. `Instructions.make(...)` hides the value type so differently typed sources compose uniformly; its codec compares and stores the value, while pure renderers produce baseline, update, and optional removal text.
|
- The current **Context Snapshot** advances atomically with the corresponding durable **Mid-Conversation System Message**.
|
||||||
- `Instructions.initialize(...)` observes composed **Instructions** once and produces a complete **Instruction Baseline** with **Applied Instructions**.
|
- A **Context Snapshot** stores one codec-encoded JSON value and, for removable dynamic sources, a pre-rendered removal message per stable **Context Source** key.
|
||||||
- `Instructions.reconcile(...)` observes composed **Instructions** once and returns either unchanged or one combined chronological update. It never rewrites the baseline.
|
- Changes from multiple **Context Sources** admitted at one safe boundary combine into one **Mid-Conversation System Message**.
|
||||||
- `Instructions.rebaseline(...)` renders a fresh baseline after completed compaction, recalling previously applied values for sources that are temporarily unavailable.
|
- Context changes are sampled and admitted lazily at a **Safe Provider-Turn Boundary**, never pushed asynchronously when their source changes.
|
||||||
- A changed **Instruction Source** may contribute text to one **Instruction Update** containing the newly effective state.
|
- At a **Safe Provider-Turn Boundary**, newly promoted user input or settled tool results precede any combined **Mid-Conversation System Message**.
|
||||||
- An **Instruction Update** persists the exact combined rendered text sent to the model through `session.instructions.updated`.
|
|
||||||
- **Applied Instructions** advances atomically with the corresponding durable **Instruction Update**.
|
|
||||||
- **Applied Instructions** stores one codec-encoded JSON value and, for removable sources, a pre-rendered removal message per stable **Instruction Source** key.
|
|
||||||
- Changes from multiple **Instruction Sources** admitted at one safe boundary combine into one **Instruction Update**.
|
|
||||||
- Instruction changes are sampled and admitted lazily at a **Safe Step Boundary**, never pushed asynchronously when their source changes.
|
|
||||||
- At a **Safe Step Boundary**, prior tool results are already settled; instruction preparation completes before newly admitted user input promotes.
|
|
||||||
- An **Admitted Prompt** is replayable pending input, not yet model-visible **Session History**.
|
- An **Admitted Prompt** is replayable pending input, not yet model-visible **Session History**.
|
||||||
- **Prompt Promotion** atomically consumes the pending inbox entry and appends its model-visible user message.
|
- **Prompt Promotion** atomically consumes the pending inbox entry and appends its model-visible user message.
|
||||||
- Steering prompts promote at the next **Safe Step Boundary** while the current **Session Drain** still requires continuation. Promoting any newly admitted user input resets the selected agent's step allowance; multiple prompts promoted at one boundary reset it once.
|
- Steering prompts promote at the next **Safe Provider-Turn Boundary** while the current **Session Drain** still requires continuation. Promoting any newly admitted user input resets the selected agent's provider-turn allowance; multiple prompts promoted at one boundary reset it once.
|
||||||
- A queued prompt does not promote while the current **Session Drain** requires continuation. The runner promotes one queued prompt when the Session would otherwise become idle, then reevaluates continuation before promoting another.
|
- A queued prompt does not promote while the current **Session Drain** requires continuation. The runner promotes one queued prompt when the Session would otherwise become idle, then reevaluates continuation before promoting another.
|
||||||
- A **Session Drain** is process-local coordination rather than a durable domain entity. Durable recovery must reason from prompts, projected history, physical attempts, and tool state rather than inventing an enclosing execution identity.
|
- A **Session Drain** is process-local coordination rather than a durable domain entity. Durable recovery must reason from prompts, projected history, provider attempts, and tool state rather than inventing an enclosing execution identity.
|
||||||
- An **Execution** contains one or more **Session Drains**; a **Session Drain** contains one reserved assistant-turn span at a time; that span contains **Steps**; and each **Step** contains one or more **Physical Attempts** plus any tool calls it requires.
|
- The first provider turn renders the latest complete **Baseline System Context** and initializes its **Context Snapshot** without emitting a redundant **Mid-Conversation System Message**; unavailable initial context blocks the turn instead of persisting an incomplete baseline.
|
||||||
- A **Step** record covers only the model-visible span from first assistant output through tool settlement; pre-flight leaves no record, and one Step settles at most one record.
|
- Initial **System Context** preparation precedes the first durable input promotion so an unavailable baseline leaves that input pending and retryable; ordinary reconciliation remains after promotion.
|
||||||
- The first **Step** renders the latest complete **Instruction Baseline** and creates its **InstructionCheckpoint** without emitting a redundant **Instruction Update**; an unavailable initial source blocks the Step instead of persisting an incomplete baseline.
|
- Compaction starts a new **Context Epoch** with a freshly rendered **Baseline System Context** and **Context Snapshot**; prior **Mid-Conversation System Messages** remain durable audit history but leave projected model history.
|
||||||
- Instruction preparation precedes durable input promotion on every Step so an unavailable first baseline leaves pending input untouched and later updates enter history before newly promoted input.
|
- A newly registered core or plugin-defined **Context Source** absent from the current snapshot emits its baseline rendering once at the next **Safe Provider-Turn Boundary**.
|
||||||
- Completed compaction rebaselines the **InstructionCheckpoint** from current **Instructions** and removes earlier **Instruction Updates** from active projected model history while preserving durable audit history.
|
- **Context Source** keys are stable and namespaced; duplicate keys fail composition. `SystemContext.combine(...)` preserves caller order; the **System Context Registry** evaluates producers concurrently and combines them in stable contribution-key order so rendered context remains deterministic.
|
||||||
- A newly composed **Instruction Source** absent from **Applied Instructions** emits its baseline rendering once at the next **Safe Step Boundary**.
|
- Each **Context Source** loader returns one coherent typed value. `SystemContext.make(...)` hides that value type so differently typed sources compose uniformly. Its codec compares and stores that value; its pure renderers produce model-visible baseline, update, and removal text only when needed.
|
||||||
- **Unavailable Instruction Source** uses stale-while-revalidate semantics and is distinct from a successfully loaded absence, which may emit removal text.
|
- `SystemContext.initialize(...)` observes a composed **System Context** once and produces a fresh **Baseline System Context** with its **Context Snapshot**.
|
||||||
- **InstructionDiscovery** observes ambient instructions as one ordered aggregate **Instruction Source**.
|
- `SystemContext.reconcile(...)` observes a composed **System Context** once and returns exactly one next action: unchanged, updated, replacement ready, or replacement blocked.
|
||||||
- Ambient discovery reads global and upward-project `AGENTS.md` files and honors `OPENCODE_DISABLE_PROJECT_CONFIG` for project files.
|
- `SystemContext.replace(...)` renders a fresh generation after completed compaction or another baseline-replacing transition; it reports replacement blocked while previously admitted context is unavailable.
|
||||||
- After a successful internal file or directory read, nearby `AGENTS.md` files toward the Location root are injected once per Session as durable synthetic instruction messages.
|
- **Unavailable Context** uses stale-while-revalidate semantics and is distinct from a successfully loaded absence, which may emit removal text.
|
||||||
- **InstructionEntry** stores API-managed per-Session JSON values. Each entry contributes one `api/<key>` **Instruction Source**, so adding, replacing, or removing an entry is reconciled at the next **Safe Step Boundary**.
|
- Ordinary **Context Source** loaders return values directly; loaders that intentionally use stale-while-revalidate may explicitly return **Unavailable Context**.
|
||||||
- Location-scoped instruction producers naturally re-resolve when a moved Session next runs in its destination Location.
|
- Nested project instruction discovery after successful reads remains a follow-up; when implemented, discovered instructions must be admitted durably at the next **Safe Provider-Turn Boundary**.
|
||||||
- Moving a Session resets its **InstructionCheckpoint**, so the destination must initialize a complete baseline before another prompt can promote. Committed revert also resets the checkpoint.
|
- Location-scoped services naturally re-resolve effective context when a moved session next runs in its destination location.
|
||||||
- Selected-agent available-skill guidance is an **Instruction Source** composed explicitly by the runner. It lists only names and descriptions permitted for that agent; skill bodies and locations are exposed only through the permission-checked `skill` tool.
|
- Moving a Session clears its active **Context Epoch**, so the destination must initialize a complete baseline before another prompt can promote.
|
||||||
- The selected agent and model are sampled when a **Step** starts. Changes admitted after that boundary apply to the next Step and do not restart the current Step.
|
- Instruction discovery, source identity, persistence, and file loading belong to the instruction service; the **System Context** abstraction only composes effectful producers and renders loaded values.
|
||||||
- An agent switch that changes selected-agent guidance produces an **Instruction Update** while preserving the current baseline.
|
- The first instruction-service slice observes global and upward project `AGENTS.md` files as one ordered aggregate **Context Source** at each **Safe Provider-Turn Boundary**.
|
||||||
- Local tool authorization and pending permission requests retain the effective agent of the **Step** that issued the call; a later agent switch cannot change that call's policy.
|
- Built-in and instruction context producers register through the **System Context Registry** with stable contribution keys. Plugin-defined context registration and hot-reload lifecycle remain a follow-up built on the same scoped registry seam.
|
||||||
- Instruction source changes never wake idle Sessions; the next naturally scheduled **Safe Step Boundary** loads and compares current values lazily.
|
- Selected-agent available-skill guidance is a **Context Source** composed with Location-wide registry sources immediately before Context Epoch admission. It lists only names and descriptions permitted for that agent; skill bodies and locations are exposed only through the permission-checked `skill` tool.
|
||||||
- Once admitted, an **Instruction Update** remains durable even if the following **Physical Attempt** fails and is replayed unchanged on retry.
|
- The selected agent and model are sampled when a provider turn starts. Changes admitted after that boundary apply to the next provider turn and do not restart the current turn.
|
||||||
- **Instruction Updates** remain durable Session-message history; normal user-facing transcript surfaces may hide them.
|
- Selected-agent available-skill guidance remains a **Context Source**. An agent switch that changes that guidance produces a **Mid-Conversation System Message** while preserving the current baseline.
|
||||||
- The date **Instruction Source** initially preserves host-local calendar-date behavior; a configured user timezone may replace that default later.
|
- Local tool authorization and pending permission requests retain the effective agent of the provider turn that issued the call; a later agent switch cannot change that call's policy.
|
||||||
- An **Instruction Baseline** is stored durably and reused verbatim across process restarts until rebaseline or reset.
|
- Context source changes never wake idle sessions; the next naturally scheduled **Safe Provider-Turn Boundary** loads and compares current values lazily.
|
||||||
- An **Instruction Baseline** durably preserves the exact joined text used for its part of the active provider-cache prefix.
|
- Once admitted, a **Mid-Conversation System Message** remains durable even if the following provider attempt fails and is replayed unchanged on retry.
|
||||||
- A model/provider switch preserves the current **InstructionCheckpoint** and chronological conversation history; the new selection applies to the next **Step**.
|
- **Mid-Conversation System Messages** remain durable Session-message history; normal user-facing transcript surfaces may hide them.
|
||||||
- **Native Continuation Metadata** remains in durable history. Step projection includes it only for a successful exact originating provider/model match; failed Steps and incompatible models omit opaque metadata, while non-empty visible reasoning lowers to ordinary assistant text after a model switch. This conservative relation may widen only when recorded provider tests establish compatibility.
|
- The date **Context Source** initially preserves host-local calendar-date behavior; a configured user timezone may replace that default later.
|
||||||
|
- A **Context Epoch** begins with one immutable **Baseline System Context**.
|
||||||
|
- A **Baseline System Context** is stored durably and reused verbatim across process restarts within its **Context Epoch**.
|
||||||
|
- A **Baseline System Context** durably preserves the exact joined text used for the active provider-cache prefix.
|
||||||
|
- Completed compaction starts a new **Context Epoch** on the next provider attempt, folding the current complete **System Context** into a fresh baseline and removing earlier **Mid-Conversation System Messages** from active model history.
|
||||||
|
- A model/provider switch preserves the current **Context Epoch** and chronological conversation history; the new selection applies to the next provider turn.
|
||||||
|
- **Native Continuation Metadata** remains in durable history. Provider-turn projection includes it only for a successful exact originating provider/model match; failed turns and incompatible models omit opaque metadata, while non-empty visible reasoning lowers to ordinary assistant text after a model switch. This conservative relation may widen only when recorded provider tests establish compatibility.
|
||||||
- **Model Request Options** remain provider-semantic through Catalog resolution. The Session runner maps them into the LLM package's provider-option namespace; the selected protocol adapter alone owns provider wire encoding.
|
- **Model Request Options** remain provider-semantic through Catalog resolution. The Session runner maps them into the LLM package's provider-option namespace; the selected protocol adapter alone owns provider wire encoding.
|
||||||
- **Generation Controls**, protocol-semantic **Model Request Options**, and compatibility request body fields are separate Catalog domains. A shared ingestion adapter partitions legacy and models.dev AI-SDK-shaped options before routing.
|
- **Generation Controls**, protocol-semantic **Model Request Options**, and compatibility request body fields are separate Catalog domains. A shared ingestion adapter partitions legacy and models.dev AI-SDK-shaped options before routing.
|
||||||
- The **PTY Environment** is a server concern rather than a Core PTY concern. PTY creation merges caller values, then the host overlay, then Core-forced terminal invariants such as `TERM` and `OPENCODE_TERMINAL`.
|
- The **PTY Environment** is a server concern rather than a Core PTY concern. PTY creation merges caller values, then the host overlay, then Core-forced terminal invariants such as `TERM` and `OPENCODE_TERMINAL`.
|
||||||
@@ -195,16 +175,16 @@ _Avoid_: Response envelope
|
|||||||
- `sessions.message({ sessionID, messageID })` is a required resource lookup. An unknown Session fails with `SessionNotFoundError`; a known Session with an absent or differently owned message fails with `MessageNotFoundError` without disclosing cross-Session ownership. Absence is not represented as `undefined` across the public HTTP boundary.
|
- `sessions.message({ sessionID, messageID })` is a required resource lookup. An unknown Session fails with `SessionNotFoundError`; a known Session with an absent or differently owned message fails with `MessageNotFoundError` without disclosing cross-Session ownership. Absence is not represented as `undefined` across the public HTTP boundary.
|
||||||
- `sessions.interrupt({ sessionID })` first verifies that the durable Session exists, failing with `SessionNotFoundError` otherwise. For a known Session, interruption is idempotent: idle, already-settled, or locally unowned execution is a no-op.
|
- `sessions.interrupt({ sessionID })` first verifies that the durable Session exists, failing with `SessionNotFoundError` otherwise. For a known Session, interruption is idempotent: idle, already-settled, or locally unowned execution is a no-op.
|
||||||
- `sessions.active()` snapshots the current process's foreground Session drain registry as a record of Session IDs to `{ type: "running" }`. Missing IDs are inactive; background subagents and tasks do not make their parent Session active, and process restart clears the registry.
|
- `sessions.active()` snapshots the current process's foreground Session drain registry as a record of Session IDs to `{ type: "running" }`. Missing IDs are inactive; background subagents and tasks do not make their parent Session active, and process restart clears the registry.
|
||||||
- `sessions.context({ sessionID })` preserves the existing message-only operation. It returns projected **Session History**; it does not include or represent the complete **Model Context**, whose system text, **Instruction Baseline**, tools, and step-local additions remain separate.
|
- `sessions.context({ sessionID })` preserves the existing message-only operation. It returns projected conversational messages selected as Session context; it does not include or represent the complete provider request context, whose baseline system context and other contributions remain separate.
|
||||||
- **Open question**: Should a future, separately named operation expose complete **Model Context**, including the instruction baseline, applied instruction metadata, tools, and step-local additions?
|
- **Open question**: Should a future, separately named operation expose the complete provider request context, including baseline system context, selected source contributions, and context-epoch metadata?
|
||||||
- `sessions.prompt(...)` exposes `resume?: boolean`. Omitting it preserves durable admission followed by an advisory execution wake; `resume: false` requests durable admit-only behavior.
|
- `sessions.prompt(...)` exposes `resume?: boolean`. Omitting it preserves durable admission followed by an advisory execution wake; `resume: false` requests durable admit-only behavior.
|
||||||
- The public operation remains `sessions.prompt(...)`; `SessionInput.admit` is the internal primitive, while the public `Admission` result and `resume` option express its durable admission semantics.
|
- The public operation remains `sessions.prompt(...)`; `SessionInput.admit` is the internal primitive, while the public `Admission` result and `resume` option express its durable admission semantics.
|
||||||
- `sessions.create(...)` accepts an optional `location`. Omission resolves through the connected OpenCode instance's default or current location; an explicit value selects a known location. Networked and embedded transports use the same handler semantics.
|
- `sessions.create(...)` accepts an optional `location`. Omission resolves through the connected OpenCode instance's default or current location; an explicit value selects a known location. Networked and embedded transports use the same handler semantics.
|
||||||
- `sessions.switchAgent({ sessionID, agent })` is part of the common client alongside `sessions.switchModel(...)`. It affects subsequent Session activity and fails with `SessionNotFoundError` for an unknown Session.
|
- `sessions.switchAgent({ sessionID, agent })` is part of the common client alongside `sessions.switchModel(...)`. It affects subsequent Session activity and fails with `SessionNotFoundError` for an unknown Session.
|
||||||
- The **Embedded OpenCode** Layer delegates to the same scoped creation path; it does not define a second implementation.
|
- The **Embedded OpenCode** Layer delegates to the same scoped creation path; it does not define a second implementation.
|
||||||
- A **PTY Environment** adapter observes plugins in the request Location while passing the resolved PTY working directory to the hook; standalone servers use an empty adapter.
|
- A **PTY Environment** adapter observes plugins in the request Location while passing the resolved PTY working directory to the hook; standalone servers use an empty adapter.
|
||||||
- An **Instruction Update** lowers to the provider's native chronological instruction role when supported and to a wrapped chronological fallback otherwise.
|
- A **Mid-Conversation System Message** lowers to the provider's native chronological instruction role when supported and to a wrapped chronological fallback otherwise.
|
||||||
- When the aggregate discovered instruction set changes, its **Instruction Update** includes the complete current ordered set and supersedes the prior aggregate value; when no discovered instructions remain, the message states that previously loaded instructions no longer apply.
|
- When the effective aggregate instruction set changes, its **Mid-Conversation System Message** includes the complete current ordered set and supersedes the prior aggregate value; when no ambient instructions remain, the message states that previously loaded instructions no longer apply.
|
||||||
- Ambient project instruction discovery honors `OPENCODE_DISABLE_PROJECT_CONFIG`; global instructions remain eligible.
|
- Ambient project instruction discovery honors `OPENCODE_DISABLE_PROJECT_CONFIG`; global instructions remain eligible.
|
||||||
- Oversized textual **Model Tool Output** retains a bounded preview in Session history while its complete text moves to managed tool-output storage. Arbitrary structured-result size is a separate concern.
|
- Oversized textual **Model Tool Output** retains a bounded preview in Session history while its complete text moves to managed tool-output storage. Arbitrary structured-result size is a separate concern.
|
||||||
- One tool settlement receives one aggregate textual limit, using the configured maximum lines or UTF-8 bytes, whichever is reached first. The limit is provider-independent; token pressure belongs to context assembly and compaction.
|
- One tool settlement receives one aggregate textual limit, using the configured maximum lines or UTF-8 bytes, whichever is reached first. The limit is provider-independent; token pressure belongs to context assembly and compaction.
|
||||||
@@ -237,9 +217,9 @@ Before stabilizing the client API:
|
|||||||
|
|
||||||
## Example dialogue
|
## Example dialogue
|
||||||
|
|
||||||
> **Dev:** "The date changed while the session was active. Should the **Instruction Update** say what the old date was?"
|
> **Dev:** "The date changed while the session was active. Should the **Mid-Conversation System Message** say what the old date was?"
|
||||||
> **Domain expert:** "No. Emit the newly effective date so the agent can act on the current instructions."
|
> **Domain expert:** "No. Emit the newly effective date so the agent can act on the current **System Context**."
|
||||||
|
|
||||||
## Flagged ambiguities
|
## Flagged ambiguities
|
||||||
|
|
||||||
- Legacy `experimental.chat.system.transform` can mutate assembled system text arbitrarily, but V2 plugins do not yet expose an equivalent hook. Decide separately whether to port it, model dynamic uses as explicit **Instruction Sources**, or narrow its semantics.
|
- Legacy `experimental.chat.system.transform` can mutate the assembled baseline system prompt arbitrarily, but V2 plugins do not yet expose an equivalent hook. Decide separately whether to port it, replace dynamic uses with plugin-defined **Context Sources**, or narrow its semantics.
|
||||||
|
|||||||
+4
-9
@@ -2,28 +2,24 @@
|
|||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"description": "AI-powered development tool",
|
"description": "AI-powered development tool",
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "bun@1.3.14",
|
"packageManager": "bun@1.3.14",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --cwd packages/cli --conditions=browser src/index.ts",
|
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||||
"dev:desktop": "bun --cwd packages/desktop dev",
|
"dev:desktop": "bun --cwd packages/desktop dev",
|
||||||
"dev:web": "bun --cwd packages/app dev",
|
"dev:web": "bun --cwd packages/app dev",
|
||||||
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
|
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
|
||||||
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
|
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
|
||||||
"dev:storybook": "bun --cwd packages/storybook storybook",
|
"dev:storybook": "bun --cwd packages/storybook storybook",
|
||||||
"lint": "oxlint",
|
"lint": "oxlint",
|
||||||
"lint:effect-patterns": "ast-grep scan -c script/ast-grep/sgconfig.yml packages/core/src packages/server/src packages/protocol/src packages/cli/src",
|
"typecheck": "bun turbo typecheck",
|
||||||
"test:lint-rules": "ast-grep test -c script/ast-grep/sgconfig.yml",
|
|
||||||
"typecheck": "bun turbo typecheck --concurrency=3",
|
|
||||||
"typecheck:profile": "bun script/profile-typecheck.ts",
|
|
||||||
"typecheck:profile:packages": "bun script/profile-typecheck-packages.ts",
|
|
||||||
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
|
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
|
||||||
"postinstall": "bun run --cwd packages/core fix-node-pty",
|
"postinstall": "bun run --cwd packages/core fix-node-pty",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"random": "echo 'Random script'",
|
"random": "echo 'Random script'",
|
||||||
"sso": "aws sso login --sso-session=opencode --no-browser",
|
"sso": "aws sso login --sso-session=opencode --no-browser",
|
||||||
|
"translate:app": "bun run script/translate-app.ts",
|
||||||
"test": "echo 'do not run tests from root' && exit 1"
|
"test": "echo 'do not run tests from root' && exit 1"
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
@@ -99,7 +95,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/artifact": "5.0.1",
|
"@actions/artifact": "5.0.1",
|
||||||
"@ast-grep/cli": "0.44.0",
|
|
||||||
"@tsconfig/bun": "catalog:",
|
"@tsconfig/bun": "catalog:",
|
||||||
"@types/mime-types": "3.0.1",
|
"@types/mime-types": "3.0.1",
|
||||||
"@typescript/native-preview": "catalog:",
|
"@typescript/native-preview": "catalog:",
|
||||||
@@ -152,7 +147,7 @@
|
|||||||
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
||||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
||||||
"@ai-sdk/xai@3.0.82": "patches/@ai-sdk%2Fxai@3.0.82.patch",
|
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
|
||||||
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
|
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
|
||||||
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
||||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.17.14",
|
"version": "1.17.15",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
+61
-16
@@ -9,7 +9,7 @@ import { Font } from "@opencode-ai/ui/font"
|
|||||||
import { Splash } from "@opencode-ai/ui/logo"
|
import { Splash } from "@opencode-ai/ui/logo"
|
||||||
import { ThemeProvider } from "@opencode-ai/ui/theme/context"
|
import { ThemeProvider } from "@opencode-ai/ui/theme/context"
|
||||||
import { MetaProvider } from "@solidjs/meta"
|
import { MetaProvider } from "@solidjs/meta"
|
||||||
import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router"
|
import { type BaseRouterProps, Navigate, Route, Router, useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
|
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import {
|
import {
|
||||||
@@ -32,7 +32,7 @@ import { CommandProvider, useCommand, type CommandOption } from "@/context/comma
|
|||||||
import { CommentsProvider } from "@/context/comments"
|
import { CommentsProvider } from "@/context/comments"
|
||||||
import { FileProvider } from "@/context/file"
|
import { FileProvider } from "@/context/file"
|
||||||
import { ServerSDKProvider } from "@/context/server-sdk"
|
import { ServerSDKProvider } from "@/context/server-sdk"
|
||||||
import { ServerSyncProvider } from "@/context/server-sync"
|
import { ServerSyncProvider, useServerSync } from "@/context/server-sync"
|
||||||
import { GlobalProvider, useGlobal } from "@/context/global"
|
import { GlobalProvider, useGlobal } from "@/context/global"
|
||||||
import { HighlightsProvider } from "@/context/highlights"
|
import { HighlightsProvider } from "@/context/highlights"
|
||||||
import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
|
import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
|
||||||
@@ -52,9 +52,10 @@ import LegacyLayout from "@/pages/layout"
|
|||||||
import NewLayout from "@/pages/layout-new"
|
import NewLayout from "@/pages/layout-new"
|
||||||
import { ErrorPage } from "./pages/error"
|
import { ErrorPage } from "./pages/error"
|
||||||
import { useCheckServerHealth } from "./utils/server-health"
|
import { useCheckServerHealth } from "./utils/server-health"
|
||||||
import { legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route"
|
import { legacySessionHref, legacySessionServer, requireServerKey, sessionHref } from "./utils/session-route"
|
||||||
|
import { createSessionLineage } from "@/pages/session/session-lineage"
|
||||||
|
|
||||||
import { SessionPage, TargetSessionRouteContent } from "@/pages/session"
|
import { SessionPage, SessionRouteErrorBoundary, TargetSessionRouteContent } from "@/pages/session"
|
||||||
import { NewHome, LegacyHome } from "@/pages/home"
|
import { NewHome, LegacyHome } from "@/pages/home"
|
||||||
|
|
||||||
const NewSession = lazy(() => import("@/pages/new-session"))
|
const NewSession = lazy(() => import("@/pages/new-session"))
|
||||||
@@ -88,10 +89,14 @@ const SessionRoute = () => {
|
|||||||
tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt)
|
tabs.newDraft({ server: server.key, directory: sdk().directory }, search.prompt)
|
||||||
})
|
})
|
||||||
|
|
||||||
return <SessionPage />
|
return (
|
||||||
|
<SessionRouteErrorBoundary sessionID={params.id}>
|
||||||
|
<SessionPage />
|
||||||
|
</SessionRouteErrorBoundary>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const TargetSessionRoute = () => {
|
function TargetServerRoute(props: ParentProps) {
|
||||||
const params = useParams<{ serverKey: string; id: string }>()
|
const params = useParams<{ serverKey: string; id: string }>()
|
||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const conn = createMemo(() => {
|
const conn = createMemo(() => {
|
||||||
@@ -105,14 +110,47 @@ const TargetSessionRoute = () => {
|
|||||||
// re-resolves reactively instead); both rely on this key for server changes.
|
// re-resolves reactively instead); both rely on this key for server changes.
|
||||||
<Show when={requireServerKey(params.serverKey)} keyed>
|
<Show when={requireServerKey(params.serverKey)} keyed>
|
||||||
<ServerSDKProvider server={conn}>
|
<ServerSDKProvider server={conn}>
|
||||||
<ServerSyncProvider server={conn}>
|
<ServerSyncProvider server={conn}>{props.children}</ServerSyncProvider>
|
||||||
<TargetSessionRouteContent />
|
|
||||||
</ServerSyncProvider>
|
|
||||||
</ServerSDKProvider>
|
</ServerSDKProvider>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TargetSessionRoute = () => (
|
||||||
|
<TargetServerRoute>
|
||||||
|
<TargetSessionRouteContent />
|
||||||
|
</TargetServerRoute>
|
||||||
|
)
|
||||||
|
|
||||||
|
function LegacyTargetSessionRoute() {
|
||||||
|
const params = useParams<{ serverKey: string; id: string }>()
|
||||||
|
return (
|
||||||
|
<TargetServerRoute>
|
||||||
|
<SessionRouteErrorBoundary sessionID={params.id} serverKey={requireServerKey(params.serverKey)}>
|
||||||
|
<LegacyTargetSessionRedirect />
|
||||||
|
</SessionRouteErrorBoundary>
|
||||||
|
</TargetServerRoute>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LegacyTargetSessionRedirect() {
|
||||||
|
const params = useParams<{ id: string }>()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const sync = useServerSync()
|
||||||
|
const current = createSessionLineage(
|
||||||
|
() => params.id,
|
||||||
|
() => sync().session.lineage,
|
||||||
|
)
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const directory = current()?.session.directory
|
||||||
|
if (!directory) return
|
||||||
|
navigate(legacySessionHref(directory, params.id), { replace: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// Wraps the non-draft routes. They are gated on (and keyed to) the globally selected
|
// Wraps the non-draft routes. They are gated on (and keyed to) the globally selected
|
||||||
// server via ServerKey, then provide the server-scoped shell (Permission/Layout/
|
// server via ServerKey, then provide the server-scoped shell (Permission/Layout/
|
||||||
// Notification/Models + the visual Layout) for that server.
|
// Notification/Models + the visual Layout) for that server.
|
||||||
@@ -186,7 +224,7 @@ declare global {
|
|||||||
deepLinks?: string[]
|
deepLinks?: string[]
|
||||||
}
|
}
|
||||||
api?: {
|
api?: {
|
||||||
setTitlebar?: (theme: { mode: "light" | "dark" }) => Promise<void>
|
setTitlebar?: (theme: { mode: "light" | "dark"; scheme?: "system" | "light" | "dark" }) => Promise<void>
|
||||||
exportDebugLogs?: () => Promise<string>
|
exportDebugLogs?: () => Promise<string>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,8 +358,8 @@ export function AppBaseProviders(props: ParentProps<{ locale?: Locale }>) {
|
|||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<Font />
|
<Font />
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
onThemeApplied={(_, mode) => {
|
onThemeApplied={(_, mode, scheme) => {
|
||||||
void window.api?.setTitlebar?.({ mode })
|
void window.api?.setTitlebar?.({ mode, scheme })
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LanguageProvider locale={props.locale}>
|
<LanguageProvider locale={props.locale}>
|
||||||
@@ -542,7 +580,14 @@ function Routes(props: { serverScoped?: JSX.Element }) {
|
|||||||
<LegacyServerLayout serverScoped={props.serverScoped}>{routeProps.children}</LegacyServerLayout>
|
<LegacyServerLayout serverScoped={props.serverScoped}>{routeProps.children}</LegacyServerLayout>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Show when={!settings.general.newLayoutDesigns()}>{<Route path="/" component={LegacyHome} />}</Show>
|
<Show when={!settings.general.newLayoutDesigns()}>
|
||||||
|
{
|
||||||
|
<>
|
||||||
|
<Route path="/" component={LegacyHome} />
|
||||||
|
<Route path="/server/:serverKey/session/:id" component={LegacyTargetSessionRoute} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</Show>
|
||||||
<Route path="/:dir" component={DirectoryLayout}>
|
<Route path="/:dir" component={DirectoryLayout}>
|
||||||
<Route path="/" component={() => <Navigate href="session" />} />
|
<Route path="/" component={() => <Navigate href="session" />} />
|
||||||
<Route path="/session/:id?" component={SessionRoute} />
|
<Route path="/session/:id?" component={SessionRoute} />
|
||||||
@@ -550,15 +595,15 @@ function Routes(props: { serverScoped?: JSX.Element }) {
|
|||||||
</Route>
|
</Route>
|
||||||
<Show when={settings.general.newLayoutDesigns()}>
|
<Show when={settings.general.newLayoutDesigns()}>
|
||||||
<Route path="/" component={NewHome} />
|
<Route path="/" component={NewHome} />
|
||||||
<Route path="/:dir/session/:id" component={LegacyTargetSessionRoute} />
|
<Route path="/:dir/session/:id" component={NewLayoutLegacySessionRedirect} />
|
||||||
|
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
|
||||||
</Show>
|
</Show>
|
||||||
<Route path="/new-session" component={DraftRoute} />
|
<Route path="/new-session" component={DraftRoute} />
|
||||||
<Route path="/server/:serverKey/session/:id" component={TargetSessionRoute} />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function LegacyTargetSessionRoute() {
|
function NewLayoutLegacySessionRedirect() {
|
||||||
const server = useServer()
|
const server = useServer()
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const params = useParams<{ id: string }>()
|
const params = useParams<{ id: string }>()
|
||||||
|
|||||||
@@ -0,0 +1,325 @@
|
|||||||
|
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||||
|
import { getFilename } from "@opencode-ai/core/util/path"
|
||||||
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
|
import { useNavigate } from "@solidjs/router"
|
||||||
|
import { createMemo, onCleanup } from "solid-js"
|
||||||
|
import { useCommand, type CommandOption } from "@/context/command"
|
||||||
|
import { useFile } from "@/context/file"
|
||||||
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { useLayout } from "@/context/layout"
|
||||||
|
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
|
||||||
|
import { useServerSync } from "@/context/server-sync"
|
||||||
|
import { createSessionTabs } from "@/pages/session/helpers"
|
||||||
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
|
import { decode64 } from "@/utils/base64"
|
||||||
|
|
||||||
|
export type CommandPaletteEntry = {
|
||||||
|
id: string
|
||||||
|
type: "command" | "file" | "session"
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
keybind?: string
|
||||||
|
category: string
|
||||||
|
option?: CommandOption
|
||||||
|
path?: string
|
||||||
|
directory?: string
|
||||||
|
sessionID?: string
|
||||||
|
archived?: number
|
||||||
|
updated?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const ENTRY_LIMIT = 5
|
||||||
|
const COMMON_COMMAND_IDS = [
|
||||||
|
"session.new",
|
||||||
|
"workspace.new",
|
||||||
|
"session.previous",
|
||||||
|
"session.next",
|
||||||
|
"terminal.toggle",
|
||||||
|
"review.toggle",
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export function uniqueCommandPaletteEntries(items: CommandPaletteEntry[]) {
|
||||||
|
const seen = new Set<string>()
|
||||||
|
return items.filter((item) => {
|
||||||
|
if (seen.has(item.id)) return false
|
||||||
|
seen.add(item.id)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCommandPaletteFileEntry(path: string, category: string): CommandPaletteEntry {
|
||||||
|
return {
|
||||||
|
id: "file:" + path,
|
||||||
|
type: "file",
|
||||||
|
title: path,
|
||||||
|
category,
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCommandPaletteFileOpener(onOpenFile?: (path: string) => void) {
|
||||||
|
const file = useFile()
|
||||||
|
const layout = useLayout()
|
||||||
|
const { tabs, view } = useSessionLayout()
|
||||||
|
|
||||||
|
return (path: string) => {
|
||||||
|
const value = file.tab(path)
|
||||||
|
void tabs().open(value)
|
||||||
|
void file.load(path)
|
||||||
|
if (!view().reviewPanel.opened()) view().reviewPanel.open()
|
||||||
|
layout.fileTree.setTab("all")
|
||||||
|
onOpenFile?.(path)
|
||||||
|
tabs().setActive(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createCommandPaletteModel(props: { filesOnly?: () => boolean; onOpenFile?: (path: string) => void }) {
|
||||||
|
const command = useCommand()
|
||||||
|
const language = useLanguage()
|
||||||
|
const layout = useLayout()
|
||||||
|
const file = useFile()
|
||||||
|
const dialog = useDialog()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const serverSDK = useServerSDK()()
|
||||||
|
const serverSync = useServerSync()
|
||||||
|
const { params, tabs } = useSessionLayout()
|
||||||
|
const openFile = createCommandPaletteFileOpener(props.onOpenFile)
|
||||||
|
const state = { cleanup: undefined as (() => void) | void, committed: false }
|
||||||
|
const filesOnly = () => props.filesOnly?.() ?? false
|
||||||
|
|
||||||
|
const allowedCommands = createMemo(() => {
|
||||||
|
if (filesOnly()) return []
|
||||||
|
return command.options.filter(
|
||||||
|
(option) =>
|
||||||
|
!option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const commandEntries = createMemo(() => {
|
||||||
|
const category = language.t("palette.group.commands")
|
||||||
|
return allowedCommands().map((option) => createCommandEntry(option, category))
|
||||||
|
})
|
||||||
|
const preferredCommandEntries = createMemo(() => {
|
||||||
|
const all = allowedCommands()
|
||||||
|
const order = new Map<string, number>(COMMON_COMMAND_IDS.map((id, index) => [id, index]))
|
||||||
|
const picked = all.filter((option) => order.has(option.id))
|
||||||
|
const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT)
|
||||||
|
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
|
||||||
|
const category = language.t("palette.group.commands")
|
||||||
|
return sorted.map((option) => createCommandEntry(option, category))
|
||||||
|
})
|
||||||
|
|
||||||
|
const tabState = createSessionTabs({
|
||||||
|
tabs,
|
||||||
|
pathFromTab: file.pathFromTab,
|
||||||
|
normalizeTab: (tab) => (tab.startsWith("file://") ? file.tab(tab) : tab),
|
||||||
|
})
|
||||||
|
const recentFileEntries = createMemo(() => {
|
||||||
|
const all = tabState.openedTabs()
|
||||||
|
const active = tabState.activeFileTab()
|
||||||
|
const order = active ? [active, ...all.filter((item) => item !== active)] : all
|
||||||
|
const seen = new Set<string>()
|
||||||
|
const category = language.t("palette.group.files")
|
||||||
|
return order
|
||||||
|
.map((item) => file.pathFromTab(item))
|
||||||
|
.filter((path): path is string => {
|
||||||
|
if (!path || seen.has(path)) return false
|
||||||
|
seen.add(path)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.slice(0, ENTRY_LIMIT)
|
||||||
|
.map((path) => createCommandPaletteFileEntry(path, category))
|
||||||
|
})
|
||||||
|
const rootFileEntries = createMemo(() => {
|
||||||
|
const category = language.t("palette.group.files")
|
||||||
|
return file.tree
|
||||||
|
.children("")
|
||||||
|
.filter((node) => node.type === "file")
|
||||||
|
.map((node) => node.path)
|
||||||
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
.slice(0, ENTRY_LIMIT)
|
||||||
|
.map((path) => createCommandPaletteFileEntry(path, category))
|
||||||
|
})
|
||||||
|
|
||||||
|
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
|
||||||
|
const project = createMemo(() => {
|
||||||
|
const directory = projectDirectory()
|
||||||
|
if (!directory) return undefined
|
||||||
|
return layout.projects.list().find((item) => item.worktree === directory || item.sandboxes?.includes(directory))
|
||||||
|
})
|
||||||
|
const workspaces = createMemo(() => {
|
||||||
|
const directory = projectDirectory()
|
||||||
|
const current = project()
|
||||||
|
if (!current) return directory ? [directory] : []
|
||||||
|
const dirs = [current.worktree, ...(current.sandboxes ?? [])]
|
||||||
|
if (directory && !dirs.includes(directory)) return [...dirs, directory]
|
||||||
|
return dirs
|
||||||
|
})
|
||||||
|
const homedir = createMemo(() => serverSync().data.path.home)
|
||||||
|
const sessions = createSessionEntries({
|
||||||
|
workspaces,
|
||||||
|
label: (directory) => {
|
||||||
|
const current = project()
|
||||||
|
const kind =
|
||||||
|
current && directory === current.worktree
|
||||||
|
? language.t("workspace.type.local")
|
||||||
|
: language.t("workspace.type.sandbox")
|
||||||
|
const [store] = serverSync().child(directory, { bootstrap: false })
|
||||||
|
const home = homedir()
|
||||||
|
const path = home ? directory.replace(home, "~") : directory
|
||||||
|
const name = store.vcs?.branch ?? getFilename(directory)
|
||||||
|
return `${kind} : ${name || path}`
|
||||||
|
},
|
||||||
|
load: (directory) => serverSDK.client.session.list({ directory, roots: true }),
|
||||||
|
untitled: () => language.t("command.session.new"),
|
||||||
|
category: () => language.t("command.category.session"),
|
||||||
|
})
|
||||||
|
|
||||||
|
const highlight = (item: CommandPaletteEntry | undefined) => {
|
||||||
|
state.cleanup?.()
|
||||||
|
state.cleanup = undefined
|
||||||
|
if (item?.type !== "command") return
|
||||||
|
state.cleanup = item.option?.onHighlight?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
const select = (item: CommandPaletteEntry | undefined) => {
|
||||||
|
if (!item) return
|
||||||
|
state.committed = true
|
||||||
|
state.cleanup = undefined
|
||||||
|
dialog.close()
|
||||||
|
if (item.type === "command") {
|
||||||
|
item.option?.onSelect?.("palette")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (item.type === "session") {
|
||||||
|
if (!item.directory || !item.sessionID) return
|
||||||
|
navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!item.path) return
|
||||||
|
openFile(item.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
if (state.committed) return
|
||||||
|
state.cleanup?.()
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
language,
|
||||||
|
file,
|
||||||
|
commandEntries,
|
||||||
|
preferredCommandEntries,
|
||||||
|
recentFileEntries,
|
||||||
|
rootFileEntries,
|
||||||
|
sessions,
|
||||||
|
highlight,
|
||||||
|
select,
|
||||||
|
close: () => dialog.close(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCommandEntry(option: CommandOption, category: string): CommandPaletteEntry {
|
||||||
|
return {
|
||||||
|
id: "command:" + option.id,
|
||||||
|
type: "command",
|
||||||
|
title: option.title,
|
||||||
|
description: option.description,
|
||||||
|
keybind: option.keybind,
|
||||||
|
category,
|
||||||
|
option,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSessionEntries(props: {
|
||||||
|
workspaces: () => string[]
|
||||||
|
label: (directory: string) => string
|
||||||
|
load: (directory: string) => ReturnType<ServerSDK["client"]["session"]["list"]>
|
||||||
|
untitled: () => string
|
||||||
|
category: () => string
|
||||||
|
}) {
|
||||||
|
const state: {
|
||||||
|
token: number
|
||||||
|
inflight: Promise<CommandPaletteEntry[]> | undefined
|
||||||
|
cached: CommandPaletteEntry[] | undefined
|
||||||
|
} = { token: 0, inflight: undefined, cached: undefined }
|
||||||
|
|
||||||
|
return (text: string) => {
|
||||||
|
if (!text.trim()) {
|
||||||
|
state.token += 1
|
||||||
|
state.inflight = undefined
|
||||||
|
state.cached = undefined
|
||||||
|
return [] as CommandPaletteEntry[]
|
||||||
|
}
|
||||||
|
if (state.cached) return state.cached
|
||||||
|
if (state.inflight) return state.inflight
|
||||||
|
|
||||||
|
const current = state.token
|
||||||
|
const dirs = props.workspaces()
|
||||||
|
if (dirs.length === 0) return [] as CommandPaletteEntry[]
|
||||||
|
|
||||||
|
state.inflight = Promise.all(
|
||||||
|
dirs.map((directory) => {
|
||||||
|
const description = props.label(directory)
|
||||||
|
return props
|
||||||
|
.load(directory)
|
||||||
|
.then((result) =>
|
||||||
|
(result.data ?? [])
|
||||||
|
.filter((session) => !!session?.id)
|
||||||
|
.map((session) => ({
|
||||||
|
id: session.id,
|
||||||
|
title: session.title ?? props.untitled(),
|
||||||
|
description,
|
||||||
|
directory,
|
||||||
|
archived: session.time?.archived,
|
||||||
|
updated: session.time?.updated,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.catch(() => [] as SessionEntryInput[])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then((results) => {
|
||||||
|
if (state.token !== current) return [] as CommandPaletteEntry[]
|
||||||
|
const seen = new Set<string>()
|
||||||
|
const next = results
|
||||||
|
.flat()
|
||||||
|
.filter((item) => {
|
||||||
|
const key = `${item.directory}:${item.id}`
|
||||||
|
if (seen.has(key)) return false
|
||||||
|
seen.add(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.map((item) => createSessionEntry(item, props.category()))
|
||||||
|
state.cached = next
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
.catch(() => [] as CommandPaletteEntry[])
|
||||||
|
.finally(() => {
|
||||||
|
state.inflight = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
return state.inflight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionEntryInput = {
|
||||||
|
directory: string
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
archived?: number
|
||||||
|
updated?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSessionEntry(input: SessionEntryInput, category: string): CommandPaletteEntry {
|
||||||
|
return {
|
||||||
|
id: `session:${input.directory}:${input.id}`,
|
||||||
|
type: "session",
|
||||||
|
title: input.title,
|
||||||
|
description: input.description,
|
||||||
|
category,
|
||||||
|
directory: input.directory,
|
||||||
|
sessionID: input.id,
|
||||||
|
archived: input.archived,
|
||||||
|
updated: input.updated,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
.command-palette-v2 {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anchor to the top edge of where a centered 480px-tall dialog would sit, so the
|
||||||
|
top stays put while the content-driven height grows and shrinks. */
|
||||||
|
[data-component="dialog-v2"]:has(.command-palette-v2) {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-component="dialog-v2"]:has(.command-palette-v2) [data-slot="dialog-container"] {
|
||||||
|
width: min(calc(100vw - 24px), 640px);
|
||||||
|
height: auto;
|
||||||
|
min-height: 280px;
|
||||||
|
max-height: min(calc(100vh - 96px), 480px);
|
||||||
|
margin-top: max(48px, calc((100vh - 480px) / 2));
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--v2-background-bg-base);
|
||||||
|
box-shadow: var(--v2-elevation-floating);
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-body {
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-search {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-search [data-component="text-input-v2"] {
|
||||||
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
outline: 0;
|
||||||
|
background: color-mix(in srgb, var(--v2-background-bg-layer-02) 60%, transparent);
|
||||||
|
box-shadow: none;
|
||||||
|
transition:
|
||||||
|
background-color 120ms ease-in-out,
|
||||||
|
box-shadow 120ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-search [data-component="text-input-v2"]:where(:hover):not([data-disabled], [data-invalid]),
|
||||||
|
.command-palette-v2-search [data-component="text-input-v2"]:where(:focus-within):not([data-disabled], [data-invalid]) {
|
||||||
|
background: var(--v2-background-bg-layer-02);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-value"] {
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-leading-icon"] {
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-search [data-component="text-input-v2"] [data-slot="text-input-v2-input"] {
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-scroll {
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-results {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 6px 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-group-title {
|
||||||
|
margin: 6px 0;
|
||||||
|
padding: 0 12px;
|
||||||
|
color: var(--v2-text-text-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 440;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: -0.04px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-row {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--v2-text-text-base);
|
||||||
|
text-align: left;
|
||||||
|
cursor: default;
|
||||||
|
scroll-margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-row[data-active] {
|
||||||
|
background: var(--v2-overlay-simple-overlay-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-row:focus-visible {
|
||||||
|
background: var(--v2-overlay-simple-overlay-hover);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-row-main {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-row-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--v2-icon-icon-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-row-text {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-title {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--v2-text-text-base);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 530;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: -0.04px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-description,
|
||||||
|
.command-palette-v2-meta {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--v2-text-text-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 440;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: -0.04px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-meta {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-file-path {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: baseline;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: -0.04px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-file-dir {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--v2-text-text-muted);
|
||||||
|
font-weight: 440;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-file-name {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--v2-text-text-base);
|
||||||
|
font-weight: 530;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-state {
|
||||||
|
display: grid;
|
||||||
|
min-height: 120px;
|
||||||
|
place-items: center;
|
||||||
|
color: var(--v2-text-text-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 440;
|
||||||
|
line-height: 16px;
|
||||||
|
letter-spacing: -0.04px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.command-palette-v2-row-text {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-palette-v2-description {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
||||||
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
|
import { ScrollView } from "@opencode-ai/ui/scroll-view"
|
||||||
|
import { Dialog, DialogBody } from "@opencode-ai/ui/v2/dialog-v2"
|
||||||
|
import { Icon } from "@opencode-ai/ui/v2/icon"
|
||||||
|
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
||||||
|
import { TextInputV2 } from "@opencode-ai/ui/v2/text-input-v2"
|
||||||
|
import { createEffect, createMemo, createResource, createSignal, For, Match, Show, Switch } from "solid-js"
|
||||||
|
import { formatKeybindParts } from "@/context/command"
|
||||||
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { getRelativeTime } from "@/utils/time"
|
||||||
|
import {
|
||||||
|
createCommandPaletteFileEntry,
|
||||||
|
createCommandPaletteModel,
|
||||||
|
uniqueCommandPaletteEntries,
|
||||||
|
type CommandPaletteEntry,
|
||||||
|
} from "./command-palette"
|
||||||
|
import "./dialog-command-palette-v2.css"
|
||||||
|
|
||||||
|
function groups(entries: CommandPaletteEntry[]) {
|
||||||
|
const map = new Map<string, CommandPaletteEntry[]>()
|
||||||
|
for (const entry of entries) map.set(entry.category, [...(map.get(entry.category) ?? []), entry])
|
||||||
|
return Array.from(map.entries()).map(([category, entries]) => ({ category, entries }))
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchesEntry(entry: CommandPaletteEntry, query: string) {
|
||||||
|
const value = query.toLowerCase()
|
||||||
|
return [entry.title, entry.description, entry.category].some((text) => text?.toLowerCase().includes(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogCommandPaletteV2(props: { onOpenFile?: (path: string) => void }) {
|
||||||
|
const palette = createCommandPaletteModel(props)
|
||||||
|
const [query, setQuery] = createSignal("")
|
||||||
|
const [active, setActive] = createSignal(0)
|
||||||
|
|
||||||
|
const loadItems = async (text: string) => {
|
||||||
|
const q = text.trim()
|
||||||
|
if (!q) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
|
||||||
|
|
||||||
|
const [files, nextSessions] = await Promise.all([palette.file.searchFiles(q), Promise.resolve(palette.sessions(q))])
|
||||||
|
const category = palette.language.t("palette.group.files")
|
||||||
|
return [
|
||||||
|
...palette.commandEntries().filter((entry) => matchesEntry(entry, q)),
|
||||||
|
...nextSessions.filter((entry) => matchesEntry(entry, q)),
|
||||||
|
...files.map((path) => createCommandPaletteFileEntry(path, category)),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const [entries] = createResource(query, loadItems, { initialValue: [] as CommandPaletteEntry[] })
|
||||||
|
// Render stale results while a new query loads to avoid flashing "Loading" per keystroke.
|
||||||
|
const visibleEntries = createMemo(() => uniqueCommandPaletteEntries(entries.latest ?? []))
|
||||||
|
const groupedEntries = createMemo(() => groups(visibleEntries()))
|
||||||
|
const activeEntry = createMemo(() => visibleEntries()[active()])
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
query()
|
||||||
|
visibleEntries()
|
||||||
|
setActive(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
palette.highlight(activeEntry())
|
||||||
|
})
|
||||||
|
|
||||||
|
let resultsRef: HTMLDivElement | undefined
|
||||||
|
|
||||||
|
const move = (delta: -1 | 1) => {
|
||||||
|
const count = visibleEntries().length
|
||||||
|
if (count === 0) return
|
||||||
|
setActive((index) => (index + delta + count) % count)
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
resultsRef?.querySelector("[data-active]")?.scrollIntoView({ block: "nearest" })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === "ArrowDown") {
|
||||||
|
event.preventDefault()
|
||||||
|
move(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.key === "ArrowUp") {
|
||||||
|
event.preventDefault()
|
||||||
|
move(-1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault()
|
||||||
|
palette.select(activeEntry())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
event.preventDefault()
|
||||||
|
palette.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog class="command-palette-v2" size="large">
|
||||||
|
<DialogBody class="command-palette-v2-body">
|
||||||
|
<div class="command-palette-v2-search">
|
||||||
|
<TextInputV2
|
||||||
|
value={query()}
|
||||||
|
autofocus
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck={false}
|
||||||
|
appearance="large"
|
||||||
|
placeholder={palette.language.t("palette.search.placeholder")}
|
||||||
|
leadingIcon={<Icon name="magnifying-glass" />}
|
||||||
|
onInput={(event) => setQuery(event.currentTarget.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ScrollView class="command-palette-v2-scroll" viewportRef={(el) => (resultsRef = el)}>
|
||||||
|
<div class="command-palette-v2-results" role="listbox">
|
||||||
|
<Show
|
||||||
|
when={visibleEntries().length > 0}
|
||||||
|
fallback={
|
||||||
|
<div class="command-palette-v2-state">
|
||||||
|
{entries.loading ? palette.language.t("common.loading") : palette.language.t("palette.empty")}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<For each={groupedEntries()}>
|
||||||
|
{(group) => (
|
||||||
|
<div class="command-palette-v2-group">
|
||||||
|
<Show when={group.category}>
|
||||||
|
<div class="command-palette-v2-group-title">{group.category}</div>
|
||||||
|
</Show>
|
||||||
|
<For each={group.entries}>
|
||||||
|
{(item) => (
|
||||||
|
<PaletteRow
|
||||||
|
item={item}
|
||||||
|
active={activeEntry()?.id === item.id}
|
||||||
|
language={palette.language}
|
||||||
|
onActive={() => setActive(visibleEntries().findIndex((entry) => entry.id === item.id))}
|
||||||
|
onSelect={() => palette.select(item)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</ScrollView>
|
||||||
|
</DialogBody>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaletteRow(props: {
|
||||||
|
item: CommandPaletteEntry
|
||||||
|
active: boolean
|
||||||
|
language: ReturnType<typeof useLanguage>
|
||||||
|
onActive: () => void
|
||||||
|
onSelect: () => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="command-palette-v2-row"
|
||||||
|
role="option"
|
||||||
|
aria-selected={props.active}
|
||||||
|
data-active={props.active ? "" : undefined}
|
||||||
|
onMouseMove={(event) => {
|
||||||
|
// Ignore hover from a static cursor when keyboard scrolling moves rows underneath it.
|
||||||
|
if (event.movementX === 0 && event.movementY === 0) return
|
||||||
|
props.onActive()
|
||||||
|
}}
|
||||||
|
onMouseDown={(event) => event.preventDefault()}
|
||||||
|
onClick={props.onSelect}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
fallback={
|
||||||
|
<div class="command-palette-v2-row-main">
|
||||||
|
<FileIcon node={{ path: props.item.path ?? "", type: "file" }} class="command-palette-v2-row-icon size-4" />
|
||||||
|
<div class="command-palette-v2-file-path">
|
||||||
|
<span class="command-palette-v2-file-dir">{getDirectory(props.item.path ?? "")}</span>
|
||||||
|
<span class="command-palette-v2-file-name">{getFilename(props.item.path ?? "")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Match when={props.item.type === "command"}>
|
||||||
|
<div class="command-palette-v2-row-main">
|
||||||
|
<div class="command-palette-v2-row-text">
|
||||||
|
<span class="command-palette-v2-title">{props.item.title}</span>
|
||||||
|
<Show when={props.item.description}>
|
||||||
|
<span class="command-palette-v2-description">{props.item.description}</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Show when={props.item.keybind}>
|
||||||
|
<KeybindV2 keys={formatKeybindParts(props.item.keybind ?? "", props.language.t)} variant="neutral" />
|
||||||
|
</Show>
|
||||||
|
</Match>
|
||||||
|
<Match when={props.item.type === "session"}>
|
||||||
|
<div class="command-palette-v2-row-main">
|
||||||
|
<Icon name="status" class="command-palette-v2-row-icon" />
|
||||||
|
<div class="command-palette-v2-row-text">
|
||||||
|
<span class="command-palette-v2-title" classList={{ "opacity-70": !!props.item.archived }}>
|
||||||
|
{props.item.title}
|
||||||
|
</span>
|
||||||
|
<Show when={props.item.description}>
|
||||||
|
<span class="command-palette-v2-description" classList={{ "opacity-70": !!props.item.archived }}>
|
||||||
|
{props.item.description}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Show when={props.item.updated}>
|
||||||
|
<span class="command-palette-v2-meta">
|
||||||
|
{getRelativeTime(new Date(props.item.updated!).toISOString(), props.language.t)}
|
||||||
|
</span>
|
||||||
|
</Show>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -177,7 +177,7 @@ export function CustomProviderForm() {
|
|||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-6 px-2.5 pb-3 overflow-y-auto max-h-[60vh]">
|
<div class="flex flex-col gap-6 px-2.5 pb-3 overflow-y-auto max-h-[60vh]">
|
||||||
<div class="px-2.5 flex gap-4 items-center">
|
<div class="px-2.5 flex gap-4 items-center">
|
||||||
<ProviderIcon id="session.synthetic" class="size-5 shrink-0 icon-strong-base" />
|
<ProviderIcon id="synthetic" class="size-5 shrink-0 icon-strong-base" />
|
||||||
<div class="text-16-medium text-text-strong">{language.t("provider.custom.title")}</div>
|
<div class="text-16-medium text-text-strong">{language.t("provider.custom.title")}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,397 +1,58 @@
|
|||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
import { FileIcon } from "@opencode-ai/ui/file-icon"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Keybind } from "@opencode-ai/ui/keybind"
|
import { Keybind } from "@opencode-ai/ui/keybind"
|
||||||
import { List } from "@opencode-ai/ui/list"
|
import { List } from "@opencode-ai/ui/list"
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
|
||||||
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
import { getDirectory, getFilename } from "@opencode-ai/core/util/path"
|
||||||
import { useNavigate } from "@solidjs/router"
|
import { createMemo, createSignal, lazy, Match, Show, Switch } from "solid-js"
|
||||||
import { createMemo, createSignal, lazy, Match, onCleanup, Show, Switch } from "solid-js"
|
import { formatKeybind } from "@/context/command"
|
||||||
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
|
import { useServerSDK } from "@/context/server-sdk"
|
||||||
import { useServerSDK, type ServerSDK } from "@/context/server-sdk"
|
|
||||||
import { useServerSync } from "@/context/server-sync"
|
|
||||||
import { useLayout } from "@/context/layout"
|
|
||||||
import { useFile } from "@/context/file"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
import { createSessionTabs } from "@/pages/session/helpers"
|
|
||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
import { getRelativeTime } from "@/utils/time"
|
import { getRelativeTime } from "@/utils/time"
|
||||||
|
import {
|
||||||
|
createCommandPaletteFileEntry,
|
||||||
|
createCommandPaletteFileOpener,
|
||||||
|
createCommandPaletteModel,
|
||||||
|
uniqueCommandPaletteEntries,
|
||||||
|
type CommandPaletteEntry,
|
||||||
|
} from "./command-palette"
|
||||||
|
|
||||||
const DialogSelectFileV2 = lazy(() =>
|
const DialogSelectFileV2 = lazy(() =>
|
||||||
import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })),
|
import("./dialog-select-directory-v2").then((module) => ({ default: module.DialogSelectDirectoryV2 })),
|
||||||
)
|
)
|
||||||
|
const DialogCommandPaletteV2 = lazy(() =>
|
||||||
type EntryType = "command" | "file" | "session"
|
import("./dialog-command-palette-v2").then((module) => ({ default: module.DialogCommandPaletteV2 })),
|
||||||
|
)
|
||||||
type Entry = {
|
|
||||||
id: string
|
|
||||||
type: EntryType
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
keybind?: string
|
|
||||||
category: string
|
|
||||||
option?: CommandOption
|
|
||||||
path?: string
|
|
||||||
directory?: string
|
|
||||||
sessionID?: string
|
|
||||||
archived?: number
|
|
||||||
updated?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type DialogSelectFileMode = "all" | "files"
|
type DialogSelectFileMode = "all" | "files"
|
||||||
|
|
||||||
const ENTRY_LIMIT = 5
|
|
||||||
const COMMON_COMMAND_IDS = [
|
|
||||||
"session.new",
|
|
||||||
"workspace.new",
|
|
||||||
"session.previous",
|
|
||||||
"session.next",
|
|
||||||
"terminal.toggle",
|
|
||||||
"review.toggle",
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const uniqueEntries = (items: Entry[]) => {
|
|
||||||
const seen = new Set<string>()
|
|
||||||
const out: Entry[] = []
|
|
||||||
for (const item of items) {
|
|
||||||
if (seen.has(item.id)) continue
|
|
||||||
seen.add(item.id)
|
|
||||||
out.push(item)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
const createCommandEntry = (option: CommandOption, category: string): Entry => ({
|
|
||||||
id: "command:" + option.id,
|
|
||||||
type: "command",
|
|
||||||
title: option.title,
|
|
||||||
description: option.description,
|
|
||||||
keybind: option.keybind,
|
|
||||||
category,
|
|
||||||
option,
|
|
||||||
})
|
|
||||||
|
|
||||||
const createFileEntry = (path: string, category: string): Entry => ({
|
|
||||||
id: "file:" + path,
|
|
||||||
type: "file",
|
|
||||||
title: path,
|
|
||||||
category,
|
|
||||||
path,
|
|
||||||
})
|
|
||||||
|
|
||||||
const createSessionEntry = (
|
|
||||||
input: {
|
|
||||||
directory: string
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
archived?: number
|
|
||||||
updated?: number
|
|
||||||
},
|
|
||||||
category: string,
|
|
||||||
): Entry => ({
|
|
||||||
id: `session:${input.directory}:${input.id}`,
|
|
||||||
type: "session",
|
|
||||||
title: input.title,
|
|
||||||
description: input.description,
|
|
||||||
category,
|
|
||||||
directory: input.directory,
|
|
||||||
sessionID: input.id,
|
|
||||||
archived: input.archived,
|
|
||||||
updated: input.updated,
|
|
||||||
})
|
|
||||||
|
|
||||||
function createCommandEntries(props: {
|
|
||||||
filesOnly: () => boolean
|
|
||||||
command: ReturnType<typeof useCommand>
|
|
||||||
language: ReturnType<typeof useLanguage>
|
|
||||||
}) {
|
|
||||||
const allowed = createMemo(() => {
|
|
||||||
if (props.filesOnly()) return []
|
|
||||||
return props.command.options.filter(
|
|
||||||
(option) =>
|
|
||||||
!option.disabled && !option.hidden && !option.id.startsWith("suggested.") && option.id !== "file.open",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const list = createMemo(() => {
|
|
||||||
const category = props.language.t("palette.group.commands")
|
|
||||||
return allowed().map((option) => createCommandEntry(option, category))
|
|
||||||
})
|
|
||||||
|
|
||||||
const picks = createMemo(() => {
|
|
||||||
const all = allowed()
|
|
||||||
const order = new Map<string, number>(COMMON_COMMAND_IDS.map((id, index) => [id, index]))
|
|
||||||
const picked = all.filter((option) => order.has(option.id))
|
|
||||||
const base = picked.length ? picked : all.slice(0, ENTRY_LIMIT)
|
|
||||||
const sorted = picked.length ? [...base].sort((a, b) => (order.get(a.id) ?? 0) - (order.get(b.id) ?? 0)) : base
|
|
||||||
const category = props.language.t("palette.group.commands")
|
|
||||||
return sorted.map((option) => createCommandEntry(option, category))
|
|
||||||
})
|
|
||||||
|
|
||||||
return { allowed, list, picks }
|
|
||||||
}
|
|
||||||
|
|
||||||
function createFileEntries(props: {
|
|
||||||
file: ReturnType<typeof useFile>
|
|
||||||
tabs: () => ReturnType<ReturnType<typeof useLayout>["tabs"]>
|
|
||||||
language: ReturnType<typeof useLanguage>
|
|
||||||
}) {
|
|
||||||
const tabState = createSessionTabs({
|
|
||||||
tabs: props.tabs,
|
|
||||||
pathFromTab: props.file.pathFromTab,
|
|
||||||
normalizeTab: (tab) => (tab.startsWith("file://") ? props.file.tab(tab) : tab),
|
|
||||||
})
|
|
||||||
const recent = createMemo(() => {
|
|
||||||
const all = tabState.openedTabs()
|
|
||||||
const active = tabState.activeFileTab()
|
|
||||||
const order = active ? [active, ...all.filter((item) => item !== active)] : all
|
|
||||||
const seen = new Set<string>()
|
|
||||||
const category = props.language.t("palette.group.files")
|
|
||||||
const items: Entry[] = []
|
|
||||||
|
|
||||||
for (const item of order) {
|
|
||||||
const path = props.file.pathFromTab(item)
|
|
||||||
if (!path) continue
|
|
||||||
if (seen.has(path)) continue
|
|
||||||
seen.add(path)
|
|
||||||
items.push(createFileEntry(path, category))
|
|
||||||
}
|
|
||||||
|
|
||||||
return items.slice(0, ENTRY_LIMIT)
|
|
||||||
})
|
|
||||||
|
|
||||||
const root = createMemo(() => {
|
|
||||||
const category = props.language.t("palette.group.files")
|
|
||||||
const nodes = props.file.tree.children("")
|
|
||||||
const paths = nodes
|
|
||||||
.filter((node) => node.type === "file")
|
|
||||||
.map((node) => node.path)
|
|
||||||
.sort((a, b) => a.localeCompare(b))
|
|
||||||
return paths.slice(0, ENTRY_LIMIT).map((path) => createFileEntry(path, category))
|
|
||||||
})
|
|
||||||
|
|
||||||
return { recent, root }
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSessionEntries(props: {
|
|
||||||
workspaces: () => string[]
|
|
||||||
label: (directory: string) => string
|
|
||||||
serverSDK: ServerSDK
|
|
||||||
language: ReturnType<typeof useLanguage>
|
|
||||||
}) {
|
|
||||||
const state: {
|
|
||||||
token: number
|
|
||||||
inflight: Promise<Entry[]> | undefined
|
|
||||||
cached: Entry[] | undefined
|
|
||||||
} = {
|
|
||||||
token: 0,
|
|
||||||
inflight: undefined,
|
|
||||||
cached: undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessions = (text: string) => {
|
|
||||||
const query = text.trim()
|
|
||||||
if (!query) {
|
|
||||||
state.token += 1
|
|
||||||
state.inflight = undefined
|
|
||||||
state.cached = undefined
|
|
||||||
return [] as Entry[]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.cached) return state.cached
|
|
||||||
if (state.inflight) return state.inflight
|
|
||||||
|
|
||||||
const current = state.token
|
|
||||||
const dirs = props.workspaces()
|
|
||||||
if (dirs.length === 0) return [] as Entry[]
|
|
||||||
|
|
||||||
state.inflight = Promise.all(
|
|
||||||
dirs.map((directory) => {
|
|
||||||
const description = props.label(directory)
|
|
||||||
return props.serverSDK.client.session
|
|
||||||
.list({ directory, roots: true })
|
|
||||||
.then((x) =>
|
|
||||||
(x.data ?? [])
|
|
||||||
.filter((s) => !!s?.id)
|
|
||||||
.map((s) => ({
|
|
||||||
id: s.id,
|
|
||||||
title: s.title ?? props.language.t("command.session.new"),
|
|
||||||
description,
|
|
||||||
directory,
|
|
||||||
archived: s.time?.archived,
|
|
||||||
updated: s.time?.updated,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.catch(
|
|
||||||
() =>
|
|
||||||
[] as {
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
directory: string
|
|
||||||
archived?: number
|
|
||||||
updated?: number
|
|
||||||
}[],
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.then((results) => {
|
|
||||||
if (state.token !== current) return [] as Entry[]
|
|
||||||
const seen = new Set<string>()
|
|
||||||
const category = props.language.t("command.category.session")
|
|
||||||
const next = results
|
|
||||||
.flat()
|
|
||||||
.filter((item) => {
|
|
||||||
const key = `${item.directory}:${item.id}`
|
|
||||||
if (seen.has(key)) return false
|
|
||||||
seen.add(key)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.map((item) => createSessionEntry(item, category))
|
|
||||||
state.cached = next
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
.catch(() => [] as Entry[])
|
|
||||||
.finally(() => {
|
|
||||||
state.inflight = undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
return state.inflight
|
|
||||||
}
|
|
||||||
|
|
||||||
return { sessions }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) {
|
export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFile?: (path: string) => void }) {
|
||||||
const command = useCommand()
|
|
||||||
const language = useLanguage()
|
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const layout = useLayout()
|
|
||||||
const file = useFile()
|
|
||||||
const dialog = useDialog()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const serverSDK = useServerSDK()
|
|
||||||
const serverSync = useServerSync()
|
|
||||||
const { params, tabs, view } = useSessionLayout()
|
|
||||||
const filesOnly = () => props.mode === "files"
|
const filesOnly = () => props.mode === "files"
|
||||||
const state = { cleanup: undefined as (() => void) | void, committed: false }
|
|
||||||
const [grouped, setGrouped] = createSignal(false)
|
|
||||||
const commandEntries = createCommandEntries({ filesOnly, command, language })
|
|
||||||
const fileEntries = createFileEntries({ file, tabs, language })
|
|
||||||
|
|
||||||
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
|
if (!filesOnly() && settings.general.newLayoutDesigns()) {
|
||||||
const project = createMemo(() => {
|
return <DialogCommandPaletteV2 onOpenFile={props.onOpenFile} />
|
||||||
const directory = projectDirectory()
|
|
||||||
if (!directory) return
|
|
||||||
return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory))
|
|
||||||
})
|
|
||||||
const workspaces = createMemo(() => {
|
|
||||||
const directory = projectDirectory()
|
|
||||||
const current = project()
|
|
||||||
if (!current) return directory ? [directory] : []
|
|
||||||
|
|
||||||
const dirs = [current.worktree, ...(current.sandboxes ?? [])]
|
|
||||||
if (directory && !dirs.includes(directory)) return [...dirs, directory]
|
|
||||||
return dirs
|
|
||||||
})
|
|
||||||
const homedir = createMemo(() => serverSync().data.path.home)
|
|
||||||
const label = (directory: string) => {
|
|
||||||
const current = project()
|
|
||||||
const kind =
|
|
||||||
current && directory === current.worktree
|
|
||||||
? language.t("workspace.type.local")
|
|
||||||
: language.t("workspace.type.sandbox")
|
|
||||||
const [store] = serverSync().child(directory, { bootstrap: false })
|
|
||||||
const home = homedir()
|
|
||||||
const path = home ? directory.replace(home, "~") : directory
|
|
||||||
const name = store.vcs?.branch ?? getFilename(directory)
|
|
||||||
return `${kind} : ${name || path}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sessions } = createSessionEntries({ workspaces, label, serverSDK: serverSDK(), language })
|
|
||||||
|
|
||||||
const items = async (text: string) => {
|
|
||||||
const query = text.trim()
|
|
||||||
setGrouped(query.length > 0)
|
|
||||||
|
|
||||||
if (!query && filesOnly()) {
|
|
||||||
const loaded = file.tree.state("")?.loaded
|
|
||||||
const pending = loaded ? Promise.resolve() : file.tree.list("")
|
|
||||||
const next = uniqueEntries([...fileEntries.recent(), ...fileEntries.root()])
|
|
||||||
|
|
||||||
if (loaded || next.length > 0) {
|
|
||||||
void pending
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
await pending
|
|
||||||
return uniqueEntries([...fileEntries.recent(), ...fileEntries.root()])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!query) return [...commandEntries.picks(), ...fileEntries.recent()]
|
|
||||||
|
|
||||||
if (filesOnly()) {
|
|
||||||
const files = await file.searchFiles(query)
|
|
||||||
const category = language.t("palette.group.files")
|
|
||||||
return files.map((path) => createFileEntry(path, category))
|
|
||||||
}
|
|
||||||
|
|
||||||
const [files, nextSessions] = await Promise.all([file.searchFiles(query), Promise.resolve(sessions(query))])
|
|
||||||
const category = language.t("palette.group.files")
|
|
||||||
const entries = files.map((path) => createFileEntry(path, category))
|
|
||||||
return [...commandEntries.list(), ...nextSessions, ...entries]
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMove = (item: Entry | undefined) => {
|
|
||||||
state.cleanup?.()
|
|
||||||
if (!item) return
|
|
||||||
if (item.type !== "command") return
|
|
||||||
state.cleanup = item.option?.onHighlight?.()
|
|
||||||
}
|
|
||||||
|
|
||||||
const open = (path: string) => {
|
|
||||||
const value = file.tab(path)
|
|
||||||
void tabs().open(value)
|
|
||||||
void file.load(path)
|
|
||||||
if (!view().reviewPanel.opened()) view().reviewPanel.open()
|
|
||||||
layout.fileTree.setTab("all")
|
|
||||||
props.onOpenFile?.(path)
|
|
||||||
tabs().setActive(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelect = (item: Entry | undefined) => {
|
|
||||||
if (!item) return
|
|
||||||
state.committed = true
|
|
||||||
state.cleanup = undefined
|
|
||||||
dialog.close()
|
|
||||||
|
|
||||||
if (item.type === "command") {
|
|
||||||
item.option?.onSelect?.("palette")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.type === "session") {
|
|
||||||
if (!item.directory || !item.sessionID) return
|
|
||||||
navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!item.path) return
|
|
||||||
open(item.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
if (state.committed) return
|
|
||||||
state.cleanup?.()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) {
|
if (filesOnly() && platform.platform === "desktop" && settings.general.newLayoutDesigns()) {
|
||||||
|
return <DialogSelectFileDesktopV2 onOpenFile={props.onOpenFile} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <DialogSelectFileLegacy filesOnly={filesOnly} onOpenFile={props.onOpenFile} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogSelectFileDesktopV2(props: { onOpenFile?: (path: string) => void }) {
|
||||||
|
const language = useLanguage()
|
||||||
|
const serverSDK = useServerSDK()
|
||||||
|
const { params } = useSessionLayout()
|
||||||
|
const projectDirectory = createMemo(() => decode64(params.dir) ?? "")
|
||||||
|
const openFile = createCommandPaletteFileOpener(props.onOpenFile)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogSelectFileV2
|
<DialogSelectFileV2
|
||||||
server={serverSDK().server}
|
server={serverSDK().server}
|
||||||
@@ -400,32 +61,71 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
title={language.t("session.header.searchFiles")}
|
title={language.t("session.header.searchFiles")}
|
||||||
onSelect={(result) => {
|
onSelect={(result) => {
|
||||||
if (typeof result !== "string") return
|
if (typeof result !== "string") return
|
||||||
open(result)
|
openFile(result)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function DialogSelectFileLegacy(props: { filesOnly: () => boolean; onOpenFile?: (path: string) => void }) {
|
||||||
|
const palette = createCommandPaletteModel(props)
|
||||||
|
const [grouped, setGrouped] = createSignal(false)
|
||||||
|
|
||||||
|
const items = async (text: string) => {
|
||||||
|
const query = text.trim()
|
||||||
|
setGrouped(query.length > 0)
|
||||||
|
|
||||||
|
if (!query && props.filesOnly()) {
|
||||||
|
const loaded = palette.file.tree.state("")?.loaded
|
||||||
|
const pending = loaded ? Promise.resolve() : palette.file.tree.list("")
|
||||||
|
const next = uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
|
||||||
|
|
||||||
|
if (loaded || next.length > 0) {
|
||||||
|
void pending
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
await pending
|
||||||
|
return uniqueCommandPaletteEntries([...palette.recentFileEntries(), ...palette.rootFileEntries()])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query) return [...palette.preferredCommandEntries(), ...palette.recentFileEntries()]
|
||||||
|
|
||||||
|
if (props.filesOnly()) {
|
||||||
|
const files = await palette.file.searchFiles(query)
|
||||||
|
const category = palette.language.t("palette.group.files")
|
||||||
|
return files.map((path) => createCommandPaletteFileEntry(path, category))
|
||||||
|
}
|
||||||
|
|
||||||
|
const [files, nextSessions] = await Promise.all([
|
||||||
|
palette.file.searchFiles(query),
|
||||||
|
Promise.resolve(palette.sessions(query)),
|
||||||
|
])
|
||||||
|
const category = palette.language.t("palette.group.files")
|
||||||
|
const entries = files.map((path) => createCommandPaletteFileEntry(path, category))
|
||||||
|
return [...palette.commandEntries(), ...nextSessions, ...entries]
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog class="pt-3 pb-0 !max-h-[480px]" transition>
|
<Dialog class="pt-3 pb-0 !max-h-[480px]" transition>
|
||||||
<List
|
<List
|
||||||
class="px-3"
|
class="px-3"
|
||||||
search={{
|
search={{
|
||||||
placeholder: filesOnly()
|
placeholder: props.filesOnly()
|
||||||
? language.t("session.header.searchFiles")
|
? palette.language.t("session.header.searchFiles")
|
||||||
: language.t("palette.search.placeholder"),
|
: palette.language.t("palette.search.placeholder"),
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
hideIcon: true,
|
hideIcon: true,
|
||||||
}}
|
}}
|
||||||
emptyMessage={language.t("palette.empty")}
|
emptyMessage={palette.language.t("palette.empty")}
|
||||||
loadingMessage={language.t("common.loading")}
|
loadingMessage={palette.language.t("common.loading")}
|
||||||
items={items}
|
items={items}
|
||||||
key={(item) => item.id}
|
key={(item) => item.id}
|
||||||
filterKeys={["title", "description", "category"]}
|
filterKeys={["title", "description", "category"]}
|
||||||
skipFilter={(item) => item.type === "file"}
|
skipFilter={(item) => item.type === "file"}
|
||||||
groupBy={grouped() ? (item) => item.category : () => ""}
|
groupBy={grouped() ? (item) => item.category : () => ""}
|
||||||
onMove={handleMove}
|
onMove={(item: CommandPaletteEntry | undefined) => palette.highlight(item)}
|
||||||
onSelect={handleSelect}
|
onSelect={(item: CommandPaletteEntry | undefined) => palette.select(item)}
|
||||||
>
|
>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<Switch
|
<Switch
|
||||||
@@ -452,7 +152,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={item.keybind}>
|
<Show when={item.keybind}>
|
||||||
<Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "", language.t)}</Keybind>
|
<Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "", palette.language.t)}</Keybind>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
@@ -479,7 +179,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
</div>
|
</div>
|
||||||
<Show when={item.updated}>
|
<Show when={item.updated}>
|
||||||
<span class="text-12-regular text-text-weak whitespace-nowrap ml-2">
|
<span class="text-12-regular text-text-weak whitespace-nowrap ml-2">
|
||||||
{getRelativeTime(new Date(item.updated!).toISOString(), language.t)}
|
{getRelativeTime(new Date(item.updated!).toISOString(), palette.language.t)}
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|||||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
import { Icon } from "@opencode-ai/ui/v2/icon"
|
||||||
import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2"
|
import { Tag as TagV2 } from "@opencode-ai/ui/v2/badge-v2"
|
||||||
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
|
||||||
|
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
||||||
import { ModelTooltip } from "./model-tooltip"
|
import { ModelTooltip } from "./model-tooltip"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { decode64 } from "@/utils/base64"
|
import { decode64 } from "@/utils/base64"
|
||||||
@@ -92,6 +93,7 @@ const ModelList: Component<{
|
|||||||
class="w-full"
|
class="w-full"
|
||||||
placement="right-start"
|
placement="right-start"
|
||||||
gutter={12}
|
gutter={12}
|
||||||
|
openDelay={0}
|
||||||
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
|
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
|
||||||
>
|
>
|
||||||
{node}
|
{node}
|
||||||
@@ -446,11 +448,25 @@ export function ModelSelectorPopoverV2(props: {
|
|||||||
<MenuV2.RadioGroup value={current()}>
|
<MenuV2.RadioGroup value={current()}>
|
||||||
<For each={group.items}>
|
<For each={group.items}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
|
<TooltipV2
|
||||||
|
class="w-full"
|
||||||
|
placement="right-start"
|
||||||
|
gutter={6}
|
||||||
|
openDelay={0}
|
||||||
|
value={
|
||||||
|
<ModelTooltip
|
||||||
|
model={item}
|
||||||
|
latest={item.latest}
|
||||||
|
free={isFree(item.provider.id, item.cost)}
|
||||||
|
v2
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
<MenuV2.RadioItem
|
<MenuV2.RadioItem
|
||||||
value={modelKey(item)}
|
value={modelKey(item)}
|
||||||
data-option-key={modelKey(item)}
|
data-option-key={modelKey(item)}
|
||||||
data-selected-model={current() === modelKey(item) ? true : undefined}
|
data-selected-model={current() === modelKey(item) ? true : undefined}
|
||||||
class="scroll-my-6"
|
class="scroll-my-6 w-full"
|
||||||
classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
|
classList={{ "!bg-v2-overlay-simple-overlay-hover": store.active === modelKey(item) }}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
setStore("active", modelKey(item))
|
setStore("active", modelKey(item))
|
||||||
@@ -466,6 +482,7 @@ export function ModelSelectorPopoverV2(props: {
|
|||||||
<TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2>
|
<TagV2 class="shrink-0">{language.t("model.tag.latest")}</TagV2>
|
||||||
</Show>
|
</Show>
|
||||||
</MenuV2.RadioItem>
|
</MenuV2.RadioItem>
|
||||||
|
</TooltipV2>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</MenuV2.RadioGroup>
|
</MenuV2.RadioGroup>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Show, type Component } from "solid-js"
|
import { Show, type Component, type JSX } from "solid-js"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
|
||||||
type InputKey = "text" | "image" | "audio" | "video" | "pdf"
|
type InputKey = "text" | "image" | "audio" | "video" | "pdf"
|
||||||
@@ -23,7 +23,18 @@ type ModelInfo = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean }> = (props) => {
|
function ModelTooltipRow(props: { name: JSX.Element; value: JSX.Element }) {
|
||||||
|
return (
|
||||||
|
<div class="flex min-w-0 items-center gap-4">
|
||||||
|
<span class="shrink-0 text-v2-text-text-muted">{props.name}</span>
|
||||||
|
<span class="ml-auto min-w-0 truncate text-right text-v2-text-text-base">{props.value}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?: boolean; v2?: boolean }> = (
|
||||||
|
props,
|
||||||
|
) => {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const sourceName = (model: ModelInfo) => {
|
const sourceName = (model: ModelInfo) => {
|
||||||
const value = `${model.id} ${model.name}`.toLowerCase()
|
const value = `${model.id} ${model.name}`.toLowerCase()
|
||||||
@@ -51,6 +62,13 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
|
|||||||
const suffix = tags.length ? ` (${tags.join(", ")})` : ""
|
const suffix = tags.length ? ` (${tags.join(", ")})` : ""
|
||||||
return `${sourceName(props.model)} ${props.model.name}${suffix}`
|
return `${sourceName(props.model)} ${props.model.name}${suffix}`
|
||||||
}
|
}
|
||||||
|
const name = () => {
|
||||||
|
const tags: Array<string> = []
|
||||||
|
if (props.latest) tags.push(language.t("model.tag.latest"))
|
||||||
|
if (props.free) tags.push(language.t("model.tag.free"))
|
||||||
|
const suffix = tags.length ? ` (${tags.join(", ")})` : ""
|
||||||
|
return `${props.model.name}${suffix}`
|
||||||
|
}
|
||||||
const inputs = () => {
|
const inputs = () => {
|
||||||
if (props.model.capabilities) {
|
if (props.model.capabilities) {
|
||||||
const input = props.model.capabilities.input
|
const input = props.model.capabilities.input
|
||||||
@@ -73,6 +91,21 @@ export const ModelTooltip: Component<{ model: ModelInfo; latest?: boolean; free?
|
|||||||
: language.t("model.tooltip.reasoning.none")
|
: language.t("model.tooltip.reasoning.none")
|
||||||
}
|
}
|
||||||
const context = () => language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() })
|
const context = () => language.t("model.tooltip.context", { limit: props.model.limit.context.toLocaleString() })
|
||||||
|
const contextLimit = () => props.model.limit.context.toLocaleString(language.intl())
|
||||||
|
|
||||||
|
if (props.v2) {
|
||||||
|
return (
|
||||||
|
<div class="flex w-[180px] flex-col gap-2">
|
||||||
|
<ModelTooltipRow name={language.t("model.tooltip.model")} value={name()} />
|
||||||
|
<ModelTooltipRow name={language.t("model.tooltip.provider")} value={props.model.provider.name} />
|
||||||
|
<Show when={inputs()}>
|
||||||
|
{(value) => <ModelTooltipRow name={language.t("model.tooltip.inputs")} value={value()} />}
|
||||||
|
</Show>
|
||||||
|
<ModelTooltipRow name={language.t("model.tooltip.reasoning")} value={reasoning()} />
|
||||||
|
<ModelTooltipRow name={language.t("model.tooltip.context.label")} value={contextLimit()} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-1 py-1">
|
<div class="flex flex-col gap-1 py-1">
|
||||||
|
|||||||
@@ -440,11 +440,6 @@ export const SettingsGeneral: Component = () => {
|
|||||||
value={(o) => o.value}
|
value={(o) => o.value}
|
||||||
label={(o) => o.label}
|
label={(o) => o.label}
|
||||||
onSelect={(option) => option && theme.setColorScheme(option.value)}
|
onSelect={(option) => option && theme.setColorScheme(option.value)}
|
||||||
onHighlight={(option) => {
|
|
||||||
if (!option) return
|
|
||||||
theme.previewColorScheme(option.value)
|
|
||||||
return () => theme.cancelPreview()
|
|
||||||
}}
|
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="small"
|
size="small"
|
||||||
triggerVariant="settings"
|
triggerVariant="settings"
|
||||||
@@ -471,11 +466,6 @@ export const SettingsGeneral: Component = () => {
|
|||||||
if (!option) return
|
if (!option) return
|
||||||
theme.setTheme(option.id)
|
theme.setTheme(option.id)
|
||||||
}}
|
}}
|
||||||
onHighlight={(option) => {
|
|
||||||
if (!option) return
|
|
||||||
theme.previewTheme(option.id)
|
|
||||||
return () => theme.cancelPreview()
|
|
||||||
}}
|
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="small"
|
size="small"
|
||||||
triggerVariant="settings"
|
triggerVariant="settings"
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ const SettingsProvidersContent: Component<{ onBack?: () => void }> = (props) =>
|
|||||||
>
|
>
|
||||||
<div class="flex flex-col min-w-0">
|
<div class="flex flex-col min-w-0">
|
||||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
|
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
|
||||||
<ProviderIcon id="session.synthetic" class="size-5 shrink-0 icon-strong-base" />
|
<ProviderIcon id="synthetic" class="size-5 shrink-0 icon-strong-base" />
|
||||||
<span class="text-14-medium text-text-strong">{language.t("provider.custom.title")}</span>
|
<span class="text-14-medium text-text-strong">{language.t("provider.custom.title")}</span>
|
||||||
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
<Tag>{language.t("settings.providers.tag.custom")}</Tag>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -422,11 +422,6 @@ export const SettingsGeneralV2: Component<{
|
|||||||
value={(o) => o.value}
|
value={(o) => o.value}
|
||||||
label={(o) => o.label}
|
label={(o) => o.label}
|
||||||
onSelect={(option) => option && theme.setColorScheme(option.value)}
|
onSelect={(option) => option && theme.setColorScheme(option.value)}
|
||||||
onHighlight={(option) => {
|
|
||||||
if (!option) return
|
|
||||||
theme.previewColorScheme(option.value)
|
|
||||||
return () => theme.cancelPreview()
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</SettingsRowV2>
|
</SettingsRowV2>
|
||||||
|
|
||||||
@@ -454,11 +449,6 @@ export const SettingsGeneralV2: Component<{
|
|||||||
if (!option) return
|
if (!option) return
|
||||||
theme.setTheme(option.id)
|
theme.setTheme(option.id)
|
||||||
}}
|
}}
|
||||||
onHighlight={(option) => {
|
|
||||||
if (!option) return
|
|
||||||
theme.previewTheme(option.id)
|
|
||||||
return () => theme.cancelPreview()
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</SettingsRowV2>
|
</SettingsRowV2>
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ export const SettingsProvidersV2: Component<{ onBack?: () => void }> = (props) =
|
|||||||
<div class="settings-v2-provider-row" data-component="custom-provider-section">
|
<div class="settings-v2-provider-row" data-component="custom-provider-section">
|
||||||
<div class="settings-v2-provider-lead">
|
<div class="settings-v2-provider-lead">
|
||||||
<ProviderIcon
|
<ProviderIcon
|
||||||
id="session.synthetic"
|
id="synthetic"
|
||||||
width={PROVIDER_ICON_SIZE}
|
width={PROVIDER_ICON_SIZE}
|
||||||
height={PROVIDER_ICON_SIZE}
|
height={PROVIDER_ICON_SIZE}
|
||||||
class="settings-v2-provider-icon shrink-0"
|
class="settings-v2-provider-icon shrink-0"
|
||||||
|
|||||||
@@ -246,18 +246,23 @@ export function TabNavItem(props: {
|
|||||||
}}
|
}}
|
||||||
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
|
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
|
||||||
>
|
>
|
||||||
<Show when={props.session()}>
|
<span data-slot="project-avatar-slot" class="flex size-4 shrink-0 items-center justify-center">
|
||||||
|
<Show
|
||||||
|
when={props.session()}
|
||||||
|
fallback={
|
||||||
|
<span class="block size-4 rounded-[3px] border border-v2-border-border-muted" aria-hidden="true" />
|
||||||
|
}
|
||||||
|
>
|
||||||
{(session) => (
|
{(session) => (
|
||||||
<span data-slot="project-avatar-slot">
|
|
||||||
<SessionTabAvatar
|
<SessionTabAvatar
|
||||||
project={project()}
|
project={project()}
|
||||||
directory={session().directory}
|
directory={session().directory}
|
||||||
sessionId={session().id}
|
sessionId={session().id}
|
||||||
server={props.server}
|
server={props.server}
|
||||||
/>
|
/>
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
titleEl = el
|
titleEl = el
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ function SessionTabSlot(props: {
|
|||||||
let ref!: HTMLDivElement
|
let ref!: HTMLDivElement
|
||||||
const sdk = createMemo(() => props.serverCtx()?.sdk ?? null)
|
const sdk = createMemo(() => props.serverCtx()?.sdk ?? null)
|
||||||
const cachedSession = createMemo(() => props.serverCtx()?.sync.session.peek(props.tab.sessionId))
|
const cachedSession = createMemo(() => props.serverCtx()?.sync.session.peek(props.tab.sessionId))
|
||||||
|
const persisted = createMemo(() => tabs.info[props.id])
|
||||||
const [loadedSession] = createResource(
|
const [loadedSession] = createResource(
|
||||||
() => {
|
() => {
|
||||||
const ctx = props.serverCtx()
|
const ctx = props.serverCtx()
|
||||||
@@ -71,8 +72,10 @@ function SessionTabSlot(props: {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const value = session()
|
const value = session()
|
||||||
|
if (!value) return
|
||||||
|
tabs.rememberSessionInfo(props.tab, value)
|
||||||
const current = sdk()
|
const current = sdk()
|
||||||
if (!value || !current) return
|
if (!current) return
|
||||||
createTabPromptState(tabs, props.tab, current.scope, {
|
createTabPromptState(tabs, props.tab, current.scope, {
|
||||||
dir: base64Encode(value.directory),
|
dir: base64Encode(value.directory),
|
||||||
id: value.id,
|
id: value.id,
|
||||||
@@ -86,7 +89,7 @@ function SessionTabSlot(props: {
|
|||||||
data-tab-key={props.id}
|
data-tab-key={props.id}
|
||||||
data-active={props.active()}
|
data-active={props.active()}
|
||||||
class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
|
class="relative flex w-56 min-w-7 max-w-56 flex-shrink"
|
||||||
classList={{ hidden: !session() && !missingSession() }}
|
classList={{ hidden: !session() && !missingSession() && !persisted()?.title }}
|
||||||
>
|
>
|
||||||
<TabNavItem
|
<TabNavItem
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
@@ -95,7 +98,7 @@ function SessionTabSlot(props: {
|
|||||||
href={tabHref(props.tab)}
|
href={tabHref(props.tab)}
|
||||||
server={props.tab.server}
|
server={props.tab.server}
|
||||||
session={session}
|
session={session}
|
||||||
fallbackTitle={missingSession() ? language.t("session.tab.unknown") : undefined}
|
fallbackTitle={persisted()?.title ?? (missingSession() ? language.t("session.tab.unknown") : undefined)}
|
||||||
onTitleChange={(title) => {
|
onTitleChange={(title) => {
|
||||||
const value = session()
|
const value = session()
|
||||||
const ctx = props.serverCtx()
|
const ctx = props.serverCtx()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { upsertCommandRegistration } from "./command"
|
import { resolveKeybindOption, upsertCommandRegistration } from "./command"
|
||||||
|
|
||||||
describe("upsertCommandRegistration", () => {
|
describe("upsertCommandRegistration", () => {
|
||||||
test("replaces keyed registrations", () => {
|
test("replaces keyed registrations", () => {
|
||||||
@@ -23,3 +23,19 @@ describe("upsertCommandRegistration", () => {
|
|||||||
expect(next[1]?.options).toBe(one)
|
expect(next[1]?.options).toBe(one)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("resolveKeybindOption", () => {
|
||||||
|
test("prefers a matching contextual command over the global fallback", () => {
|
||||||
|
const fallback = { id: "tab.close", title: "Close tab" }
|
||||||
|
const contextual = { id: "terminal.close", title: "Close terminal", when: () => true }
|
||||||
|
|
||||||
|
expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(contextual)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("uses the global fallback outside the command context", () => {
|
||||||
|
const fallback = { id: "tab.close", title: "Close tab" }
|
||||||
|
const contextual = { id: "terminal.close", title: "Close terminal", when: () => false }
|
||||||
|
|
||||||
|
expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(fallback)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -82,10 +82,15 @@ export interface CommandOption {
|
|||||||
suggested?: boolean
|
suggested?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
hidden?: boolean
|
hidden?: boolean
|
||||||
|
when?: (event: KeyboardEvent) => boolean
|
||||||
onSelect?: (source?: "palette" | "keybind" | "slash") => void
|
onSelect?: (source?: "palette" | "keybind" | "slash") => void
|
||||||
onHighlight?: () => (() => void) | void
|
onHighlight?: () => (() => void) | void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveKeybindOption(candidates: CommandOption[] | undefined, event: KeyboardEvent) {
|
||||||
|
return candidates?.find((option) => option.when?.(event)) ?? candidates?.find((option) => !option.when)
|
||||||
|
}
|
||||||
|
|
||||||
type CommandSource = "palette" | "keybind" | "slash"
|
type CommandSource = "palette" | "keybind" | "slash"
|
||||||
|
|
||||||
export type CommandCatalogItem = {
|
export type CommandCatalogItem = {
|
||||||
@@ -334,7 +339,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||||||
})
|
})
|
||||||
|
|
||||||
const keymap = createMemo(() => {
|
const keymap = createMemo(() => {
|
||||||
const map = new Map<string, CommandOption>()
|
const map = new Map<string, CommandOption[]>()
|
||||||
for (const option of options()) {
|
for (const option of options()) {
|
||||||
if (option.id.startsWith(SUGGESTED_PREFIX)) continue
|
if (option.id.startsWith(SUGGESTED_PREFIX)) continue
|
||||||
if (option.disabled) continue
|
if (option.disabled) continue
|
||||||
@@ -344,8 +349,12 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||||||
for (const kb of keybinds) {
|
for (const kb of keybinds) {
|
||||||
if (!kb.key) continue
|
if (!kb.key) continue
|
||||||
const sig = signature(kb.key, kb.ctrl, kb.meta, kb.shift, kb.alt)
|
const sig = signature(kb.key, kb.ctrl, kb.meta, kb.shift, kb.alt)
|
||||||
if (map.has(sig)) continue
|
const existing = map.get(sig)
|
||||||
map.set(sig, option)
|
if (existing) {
|
||||||
|
existing.push(option)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
map.set(sig, [option])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map
|
return map
|
||||||
@@ -374,7 +383,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||||||
|
|
||||||
const sig = signatureFromEvent(event)
|
const sig = signatureFromEvent(event)
|
||||||
const isPalette = palette().has(sig)
|
const isPalette = palette().has(sig)
|
||||||
const option = keymap().get(sig)
|
const option = resolveKeybindOption(keymap().get(sig), event)
|
||||||
const modified = event.ctrlKey || event.metaKey || event.altKey
|
const modified = event.ctrlKey || event.metaKey || event.altKey
|
||||||
const isTab = event.key === "Tab"
|
const isTab = event.key === "Tab"
|
||||||
|
|
||||||
@@ -383,17 +392,19 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||||||
|
|
||||||
if (isPalette) {
|
if (isPalette) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
showPalette()
|
showPalette()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!option) return
|
if (!option) return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
option.onSelect?.("keybind")
|
option.onSelect?.("keybind")
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
makeEventListener(document, "keydown", handleKeyDown)
|
makeEventListener(document, "keydown", handleKeyDown, { capture: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
function register(cb: () => CommandOption[]): void
|
function register(cb: () => CommandOption[]): void
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ describe("file watcher invalidation", () => {
|
|||||||
const refresh: string[] = []
|
const refresh: string[] = []
|
||||||
invalidateFromWatcher(
|
invalidateFromWatcher(
|
||||||
{
|
{
|
||||||
type: "filesystem.changed",
|
type: "file.watcher.updated",
|
||||||
properties: {
|
properties: {
|
||||||
file: "src/new.ts",
|
file: "src/new.ts",
|
||||||
event: "add",
|
event: "add",
|
||||||
@@ -32,7 +32,7 @@ describe("file watcher invalidation", () => {
|
|||||||
|
|
||||||
invalidateFromWatcher(
|
invalidateFromWatcher(
|
||||||
{
|
{
|
||||||
type: "filesystem.changed",
|
type: "file.watcher.updated",
|
||||||
properties: {
|
properties: {
|
||||||
file: "src/open.ts",
|
file: "src/open.ts",
|
||||||
event: "change",
|
event: "change",
|
||||||
@@ -63,7 +63,7 @@ describe("file watcher invalidation", () => {
|
|||||||
|
|
||||||
invalidateFromWatcher(
|
invalidateFromWatcher(
|
||||||
{
|
{
|
||||||
type: "filesystem.changed",
|
type: "file.watcher.updated",
|
||||||
properties: {
|
properties: {
|
||||||
file: "src",
|
file: "src",
|
||||||
event: "change",
|
event: "change",
|
||||||
@@ -81,7 +81,7 @@ describe("file watcher invalidation", () => {
|
|||||||
|
|
||||||
invalidateFromWatcher(
|
invalidateFromWatcher(
|
||||||
{
|
{
|
||||||
type: "filesystem.changed",
|
type: "file.watcher.updated",
|
||||||
properties: {
|
properties: {
|
||||||
file: "src/file.ts",
|
file: "src/file.ts",
|
||||||
event: "change",
|
event: "change",
|
||||||
@@ -111,7 +111,7 @@ describe("file watcher invalidation", () => {
|
|||||||
|
|
||||||
invalidateFromWatcher(
|
invalidateFromWatcher(
|
||||||
{
|
{
|
||||||
type: "filesystem.changed",
|
type: "file.watcher.updated",
|
||||||
properties: {
|
properties: {
|
||||||
file: ".git/index.lock",
|
file: ".git/index.lock",
|
||||||
event: "change",
|
event: "change",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type WatcherOps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function invalidateFromWatcher(event: WatcherEvent, ops: WatcherOps) {
|
export function invalidateFromWatcher(event: WatcherEvent, ops: WatcherOps) {
|
||||||
if (event.type !== "filesystem.changed") return
|
if (event.type !== "file.watcher.updated") return
|
||||||
const props =
|
const props =
|
||||||
typeof event.properties === "object" && event.properties ? (event.properties as Record<string, unknown>) : undefined
|
typeof event.properties === "object" && event.properties ? (event.properties as Record<string, unknown>) : undefined
|
||||||
const rawPath = typeof props?.file === "string" ? props.file : undefined
|
const rawPath = typeof props?.file === "string" ? props.file : undefined
|
||||||
|
|||||||
@@ -126,9 +126,8 @@ describe("enqueueServerEvent", () => {
|
|||||||
|
|
||||||
enqueue(partUpdated("old"))
|
enqueue(partUpdated("old"))
|
||||||
enqueue({
|
enqueue({
|
||||||
id: "event-delete",
|
|
||||||
type: "session.deleted",
|
type: "session.deleted",
|
||||||
properties: { sessionID: "session" },
|
properties: { sessionID: "session", info: { id: "session" } },
|
||||||
} as Event)
|
} as Event)
|
||||||
enqueue(partUpdated("new"))
|
enqueue(partUpdated("new"))
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ export type DraftTab = {
|
|||||||
|
|
||||||
export type Tab = SessionTab | DraftTab
|
export type Tab = SessionTab | DraftTab
|
||||||
|
|
||||||
|
export type TabInfo = {
|
||||||
|
title?: string
|
||||||
|
directory?: string
|
||||||
|
}
|
||||||
|
|
||||||
type RecentTab = {
|
type RecentTab = {
|
||||||
key?: string
|
key?: string
|
||||||
}
|
}
|
||||||
@@ -64,6 +69,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
createStore<Tab[]>([]),
|
createStore<Tab[]>([]),
|
||||||
)
|
)
|
||||||
const [recent, setRecent, , recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({}))
|
const [recent, setRecent, , recentReady] = persisted(Persist.window("tabs.recent"), createStore<RecentTab>({}))
|
||||||
|
const [info, setInfo] = persisted(Persist.window("tabs.info"), createStore<Record<string, TabInfo>>({}))
|
||||||
const [closed, setClosed, , closedReady] = persisted(Persist.window("tabs.closed"), createStore<ClosedTab[]>([]))
|
const [closed, setClosed, , closedReady] = persisted(Persist.window("tabs.closed"), createStore<ClosedTab[]>([]))
|
||||||
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
@@ -102,6 +108,15 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform)
|
for (const key of draftPersistedKeys()) removePersisted(Persist.draft(draftID, key), platform)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removeInfo = (key: string) => {
|
||||||
|
if (!info[key]) return
|
||||||
|
setInfo(
|
||||||
|
produce((draft) => {
|
||||||
|
delete draft[key]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
onCleanup(memory.dispose)
|
onCleanup(memory.dispose)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@@ -110,11 +125,19 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
const next = store.filter((tab) => servers.has(tab.server))
|
const next = store.filter((tab) => servers.has(tab.server))
|
||||||
if (next.length !== store.length) {
|
if (next.length !== store.length) {
|
||||||
for (const tab of store) {
|
for (const tab of store) {
|
||||||
if (!servers.has(tab.server)) memory.remove(tabKey(tab))
|
if (!servers.has(tab.server)) {
|
||||||
|
const key = tabKey(tab)
|
||||||
|
memory.remove(key)
|
||||||
|
removeInfo(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setStore(() => next)
|
setStore(() => next)
|
||||||
}
|
}
|
||||||
if (recent.key && !next.some((tab) => tabKey(tab) === recent.key)) setRecentKey(undefined)
|
if (recent.key && !next.some((tab) => tabKey(tab) === recent.key)) setRecentKey(undefined)
|
||||||
|
const keys = new Set(next.map(tabKey))
|
||||||
|
for (const key of Object.keys(info)) {
|
||||||
|
if (!keys.has(key)) removeInfo(key)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@@ -150,6 +173,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
if (nextTab) navigateTab(nextTab)
|
if (nextTab) navigateTab(nextTab)
|
||||||
}).finally(() => closing.delete(key))
|
}).finally(() => closing.delete(key))
|
||||||
memory.remove(key)
|
memory.remove(key)
|
||||||
|
removeInfo(key)
|
||||||
if (draftID) removeDraftPersisted(draftID)
|
if (draftID) removeDraftPersisted(draftID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,6 +288,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
const removed = store.filter((tab) => tab.server === key).map(tabKey)
|
const removed = store.filter((tab) => tab.server === key).map(tabKey)
|
||||||
setStore((tabs) => tabs.filter((tab) => tab.server !== key))
|
setStore((tabs) => tabs.filter((tab) => tab.server !== key))
|
||||||
for (const key of removed) memory.remove(key)
|
for (const key of removed) memory.remove(key)
|
||||||
|
for (const key of removed) removeInfo(key)
|
||||||
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
|
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
|
||||||
for (const draftID of drafts) removeDraftPersisted(draftID)
|
for (const draftID of drafts) removeDraftPersisted(draftID)
|
||||||
if (server.key === key) navigate("/")
|
if (server.key === key) navigate("/")
|
||||||
@@ -318,6 +343,14 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
|
if (recent.key && removed.includes(recent.key)) setRecentKey(undefined)
|
||||||
})
|
})
|
||||||
for (const key of removed) memory.remove(key)
|
for (const key of removed) memory.remove(key)
|
||||||
|
for (const key of removed) removeInfo(key)
|
||||||
|
},
|
||||||
|
rememberSessionInfo(tab: SessionTab, session: Session) {
|
||||||
|
const key = tabKey(tab)
|
||||||
|
const next = { title: session.title, directory: session.directory }
|
||||||
|
const current = info[key]
|
||||||
|
if (current?.title === next.title && current.directory === next.directory) return
|
||||||
|
setInfo(key, next)
|
||||||
},
|
},
|
||||||
select: navigateTab,
|
select: navigateTab,
|
||||||
remember(tab: Tab) {
|
remember(tab: Tab) {
|
||||||
@@ -342,6 +375,6 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...actions, store, ready, recentReady }
|
return { ...actions, store, info, ready, recentReady }
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
+148
-2
@@ -21,6 +21,9 @@ export const dict = {
|
|||||||
"theme.scheme.dark": "داكن",
|
"theme.scheme.dark": "داكن",
|
||||||
"command.sidebar.toggle": "تبديل الشريط الجانبي",
|
"command.sidebar.toggle": "تبديل الشريط الجانبي",
|
||||||
"command.project.open": "فتح مشروع",
|
"command.project.open": "فتح مشروع",
|
||||||
|
"command.project.previous": "المشروع السابق",
|
||||||
|
"command.project.next": "المشروع التالي",
|
||||||
|
"command.project.index": "التبديل إلى المشروع {{index}}",
|
||||||
"command.provider.connect": "اتصال بموفر",
|
"command.provider.connect": "اتصال بموفر",
|
||||||
"command.server.switch": "تبديل الخادم",
|
"command.server.switch": "تبديل الخادم",
|
||||||
"command.settings.open": "فتح الإعدادات",
|
"command.settings.open": "فتح الإعدادات",
|
||||||
@@ -39,6 +42,7 @@ export const dict = {
|
|||||||
"command.session.new": "جلسة جديدة",
|
"command.session.new": "جلسة جديدة",
|
||||||
"command.file.open": "فتح ملف",
|
"command.file.open": "فتح ملف",
|
||||||
"command.tab.close": "إغلاق علامة التبويب",
|
"command.tab.close": "إغلاق علامة التبويب",
|
||||||
|
"command.tab.reopenClosed": "إعادة فتح علامة التبويب المغلقة",
|
||||||
"command.context.addSelection": "إضافة التحديد إلى السياق",
|
"command.context.addSelection": "إضافة التحديد إلى السياق",
|
||||||
"command.context.addSelection.description": "إضافة الأسطر المحددة من الملف الحالي",
|
"command.context.addSelection.description": "إضافة الأسطر المحددة من الملف الحالي",
|
||||||
"command.input.focus": "التركيز على حقل الإدخال",
|
"command.input.focus": "التركيز على حقل الإدخال",
|
||||||
@@ -193,6 +197,11 @@ export const dict = {
|
|||||||
"model.input.video": "فيديو",
|
"model.input.video": "فيديو",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "يسمح: {{inputs}}",
|
"model.tooltip.allows": "يسمح: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "السياق",
|
||||||
|
"model.tooltip.inputs": "المدخلات",
|
||||||
|
"model.tooltip.model": "النموذج",
|
||||||
|
"model.tooltip.provider": "الموفر",
|
||||||
|
"model.tooltip.reasoning": "الاستنتاج",
|
||||||
"model.tooltip.reasoning.allowed": "يسمح بالاستنتاج",
|
"model.tooltip.reasoning.allowed": "يسمح بالاستنتاج",
|
||||||
"model.tooltip.reasoning.none": "بدون استنتاج",
|
"model.tooltip.reasoning.none": "بدون استنتاج",
|
||||||
"model.tooltip.context": "حد السياق {{limit}}",
|
"model.tooltip.context": "حد السياق {{limit}}",
|
||||||
@@ -202,6 +211,7 @@ export const dict = {
|
|||||||
"common.loading": "جارٍ التحميل",
|
"common.loading": "جارٍ التحميل",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "إلغاء",
|
"common.cancel": "إلغاء",
|
||||||
|
"common.clear": "مسح",
|
||||||
"common.connect": "اتصال",
|
"common.connect": "اتصال",
|
||||||
"common.disconnect": "قطع الاتصال",
|
"common.disconnect": "قطع الاتصال",
|
||||||
"common.continue": "إرسال",
|
"common.continue": "إرسال",
|
||||||
@@ -218,6 +228,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.normal": "Prompt",
|
"prompt.mode.normal": "Prompt",
|
||||||
"prompt.mode.shell.exit": "esc للخروج",
|
"prompt.mode.shell.exit": "esc للخروج",
|
||||||
|
"session.child.promptDisabled": "لا يمكن إرسال موجّهات إلى جلسات الوكلاء الفرعيين.",
|
||||||
|
"session.child.backToParent": "العودة إلى الجلسة الرئيسية.",
|
||||||
"prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية",
|
"prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية",
|
||||||
"prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟",
|
"prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟",
|
||||||
"prompt.example.3": "إصلاح الاختبارات المعطلة",
|
"prompt.example.3": "إصلاح الاختبارات المعطلة",
|
||||||
@@ -314,6 +326,74 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "حذف",
|
"dialog.server.menu.delete": "حذف",
|
||||||
"dialog.server.current": "الخادم الحالي",
|
"dialog.server.current": "الخادم الحالي",
|
||||||
"dialog.server.status.default": "افتراضي",
|
"dialog.server.status.default": "افتراضي",
|
||||||
|
"wsl.server.add": "إضافة خادم WSL",
|
||||||
|
"wsl.server.addShort": "إضافة WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "خادم WSL",
|
||||||
|
"wsl.server.retryStart": "إعادة محاولة التشغيل",
|
||||||
|
"wsl.server.updating": "جارٍ التحديث...",
|
||||||
|
"wsl.onboarding.step.distro": "اختر توزيعة",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "جارٍ التحقق من WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "يحتاج Windows إلى إعادة التشغيل لإكمال تثبيت WSL.",
|
||||||
|
"wsl.onboarding.ready": "WSL جاهز.",
|
||||||
|
"wsl.onboarding.required": "يلزم WSL للمتابعة.",
|
||||||
|
"wsl.onboarding.checkingDistros": "جارٍ التحقق من التوزيعات...",
|
||||||
|
"wsl.onboarding.installingDistro": "جارٍ تثبيت {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "جارٍ التحقق من {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "جارٍ سرد التوزيعات...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} جاهزة.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "لم يتم تثبيت {{distro}} بعد.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "افتح {{distro}} مرة واحدة لإكمال الإعداد.",
|
||||||
|
"wsl.onboarding.finishingDistro": "جارٍ إكمال إعداد {{distro}}.",
|
||||||
|
"wsl.onboarding.pickDistro": "اختر توزيعة أو ثبّت واحدة أدناه.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "جارٍ التحقق من OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "جارٍ التحقق من OpenCode في {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "جارٍ تحديث OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "جارٍ تحديث OpenCode في {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "حدّث OpenCode في {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "تحديث OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode جاهز في {{distro}}.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode جاهز.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "ثبّت OpenCode في {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "تثبيت OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "اختر توزيعة أولاً.",
|
||||||
|
"wsl.onboarding.loadFailed": "فشل تحميل حالة WSL.",
|
||||||
|
"wsl.onboarding.loading": "جارٍ التحميل...",
|
||||||
|
"wsl.onboarding.installedDistros": "التوزيعات المثبتة",
|
||||||
|
"wsl.onboarding.checkAgain": "تحقق مجددًا",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "جاهزة",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "جارٍ التحقق...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode غير مثبت",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "bash و curl مفقودان",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "غير مدعومة · استخدم WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "هل تحتاج إلى توزيعة أخرى؟",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "ثبّت توزيعة Linux من كتالوج WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL غير مثبت",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"يلزم WSL (نظام Windows الفرعي لـ Linux) قبل أن يتمكن OpenCode من إضافة خادم WSL",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL غير متاح",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "تعذر على OpenCode التحقق من WSL على هذا الجهاز.",
|
||||||
|
"wsl.onboarding.installWsl": "تثبيت WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired": "أعد تشغيل Windows لإكمال تثبيت WSL، ثم أعد فتح OpenCode.",
|
||||||
|
"wsl.onboarding.next": "التالي",
|
||||||
|
"wsl.onboarding.refresh": "تحديث",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "تمت إضافة جميع التوزيعات المثبتة بالفعل.",
|
||||||
|
"wsl.onboarding.noDistros": "لم يتم اكتشاف أي توزيعات بعد.",
|
||||||
|
"wsl.onboarding.install": "تثبيت",
|
||||||
|
"wsl.onboarding.installing": "جارٍ التثبيت...",
|
||||||
|
"wsl.onboarding.installDistro": "تثبيت توزيعة",
|
||||||
|
"wsl.onboarding.searchDistros": "البحث عن توزيعات",
|
||||||
|
"wsl.onboarding.wsl2Required": "يلزم WSL 2.",
|
||||||
|
"wsl.onboarding.toolsRequired": "تحتاج هذه التوزيعة إلى bash و curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "فتح المحطة الطرفية",
|
||||||
|
"wsl.onboarding.path": "المسار: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "غير موجود",
|
||||||
|
"wsl.onboarding.version": "الإصدار: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "غير معروف",
|
||||||
|
"wsl.onboarding.desktopVersion": "سطح المكتب {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "لا يتطابق الإصدار المثبت مع إصدار تطبيق سطح المكتب.",
|
||||||
|
"wsl.onboarding.adding": "جارٍ الإضافة...",
|
||||||
"dialog.project.edit.title": "تحرير المشروع",
|
"dialog.project.edit.title": "تحرير المشروع",
|
||||||
"dialog.project.edit.name": "الاسم",
|
"dialog.project.edit.name": "الاسم",
|
||||||
"dialog.project.edit.icon": "أيقونة",
|
"dialog.project.edit.icon": "أيقونة",
|
||||||
@@ -370,6 +450,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
"toast.language.title": "لغة",
|
"toast.language.title": "لغة",
|
||||||
@@ -406,10 +487,12 @@ export const dict = {
|
|||||||
"toast.update.action.notYet": "ليس الآن",
|
"toast.update.action.notYet": "ليس الآن",
|
||||||
"error.page.title": "حدث خطأ ما",
|
"error.page.title": "حدث خطأ ما",
|
||||||
"error.page.description": "حدث خطأ أثناء تحميل التطبيق.",
|
"error.page.description": "حدث خطأ أثناء تحميل التطبيق.",
|
||||||
|
"error.page.description.localServerStartup": "حدث خطأ أثناء بدء تشغيل الخادم المحلي.",
|
||||||
"error.page.details.label": "تفاصيل الخطأ",
|
"error.page.details.label": "تفاصيل الخطأ",
|
||||||
"error.page.action.restart": "إعادة تشغيل",
|
"error.page.action.restart": "إعادة تشغيل",
|
||||||
"error.page.action.report": "الإبلاغ عن الخطأ",
|
"error.page.action.report": "الإبلاغ عن الخطأ",
|
||||||
"error.page.action.reported": "تم الإبلاغ عن الخطأ",
|
"error.page.action.reported": "تم الإبلاغ عن الخطأ",
|
||||||
|
"error.page.action.exportLogs": "تصدير السجلات",
|
||||||
"error.page.action.checking": "جارٍ التحقق...",
|
"error.page.action.checking": "جارٍ التحقق...",
|
||||||
"error.page.action.checkUpdates": "التحقق من وجود تحديثات",
|
"error.page.action.checkUpdates": "التحقق من وجود تحديثات",
|
||||||
"error.page.action.updateTo": "تحديث إلى {{version}}",
|
"error.page.action.updateTo": "تحديث إلى {{version}}",
|
||||||
@@ -450,16 +533,38 @@ export const dict = {
|
|||||||
"home.recentProjects": "المشاريع الحديثة",
|
"home.recentProjects": "المشاريع الحديثة",
|
||||||
"home.empty.title": "لا توجد مشاريع حديثة",
|
"home.empty.title": "لا توجد مشاريع حديثة",
|
||||||
"home.empty.description": "ابدأ بفتح مشروع محلي",
|
"home.empty.description": "ابدأ بفتح مشروع محلي",
|
||||||
|
"home.title": "الرئيسية",
|
||||||
|
"home.projects": "المشاريع",
|
||||||
|
"home.project.add": "إضافة مشروع",
|
||||||
|
"home.recentlyClosed": "المغلقة مؤخرًا",
|
||||||
|
"home.server.collapse": "طي مشاريع الخادم",
|
||||||
|
"home.server.expand": "توسيع مشاريع الخادم",
|
||||||
|
"home.sessions.search.placeholder": "البحث في الجلسات",
|
||||||
|
"home.sessions.search.placeholder.scoped": "البحث في جلسات {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "الجلسات",
|
||||||
|
"home.sessions.search.noResults": "لم يتم العثور على جلسات لـ {{query}}",
|
||||||
|
"home.sessions.empty": "لا يوجد شيء هنا بعد",
|
||||||
|
"home.sessions.empty.description": "أنشئ جلسة للبدء",
|
||||||
|
"home.sessions.group.today": "اليوم",
|
||||||
|
"home.sessions.group.yesterday": "أمس",
|
||||||
|
"home.sessions.group.older": "الأقدم",
|
||||||
"session.tab.session": "جلسة",
|
"session.tab.session": "جلسة",
|
||||||
"session.tab.review": "مراجعة",
|
"session.tab.review": "مراجعة",
|
||||||
"session.tab.context": "سياق",
|
"session.tab.context": "سياق",
|
||||||
|
"session.tab.unknown": "جلسة غير معروفة",
|
||||||
"session.panel.reviewAndFiles": "المراجعة والملفات",
|
"session.panel.reviewAndFiles": "المراجعة والملفات",
|
||||||
|
"session.error.notFound": "لا يمكن العثور على هذه الجلسة",
|
||||||
|
"session.error.notFound.description": "يشير هذا التبويب إلى جلسة لم تعد موجودة على هذا الخادم.",
|
||||||
|
"session.error.notFound.closeTab": "إغلاق علامة التبويب",
|
||||||
|
"session.error.serverConnection": "تعذر الاتصال بهذا الخادم",
|
||||||
"session.review.filesChanged": "تم تغيير {{count}} ملفات",
|
"session.review.filesChanged": "تم تغيير {{count}} ملفات",
|
||||||
"session.review.change.one": "تغيير",
|
"session.review.change.one": "تغيير",
|
||||||
"session.review.change.other": "تغييرات",
|
"session.review.change.other": "تغييرات",
|
||||||
"session.review.loadingChanges": "جارٍ تحميل التغييرات...",
|
"session.review.loadingChanges": "جارٍ تحميل التغييرات...",
|
||||||
"session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد",
|
"session.review.empty": "لا توجد تغييرات في هذه الجلسة بعد",
|
||||||
"session.review.noChanges": "لا توجد تغييرات",
|
"session.review.noChanges": "لا توجد تغييرات",
|
||||||
|
"session.review.noUncommittedChanges": "لا توجد تغييرات غير مُودَعة بعد",
|
||||||
|
"session.review.noBranchChanges": "لا توجد تغييرات في الفرع بعد",
|
||||||
"session.review.noVcs": "لم يتم اكتشاف نظام التحكم في الإصدار Git، لن يتم عرض التغييرات",
|
"session.review.noVcs": "لم يتم اكتشاف نظام التحكم في الإصدار Git، لن يتم عرض التغييرات",
|
||||||
"session.review.noSnapshot": "تم تعطيل تتبع اللقطات في التكوين، لذا فإن تغييرات الجلسة غير متوفرة",
|
"session.review.noSnapshot": "تم تعطيل تتبع اللقطات في التكوين، لذا فإن تغييرات الجلسة غير متوفرة",
|
||||||
"session.files.selectToOpen": "اختر ملفًا لفتحه",
|
"session.files.selectToOpen": "اختر ملفًا لفتحه",
|
||||||
@@ -475,6 +580,10 @@ export const dict = {
|
|||||||
"session.todo.title": "المهام",
|
"session.todo.title": "المهام",
|
||||||
"session.todo.collapse": "طي",
|
"session.todo.collapse": "طي",
|
||||||
"session.todo.expand": "توسيع",
|
"session.todo.expand": "توسيع",
|
||||||
|
"session.question.minimize": "تصغير السؤال",
|
||||||
|
"session.question.restore": "استعادة السؤال",
|
||||||
|
"session.question.pending.one": "{{count}} سؤال معلق",
|
||||||
|
"session.question.pending.other": "الأسئلة المعلقة: {{count}}",
|
||||||
"session.followupDock.summary.one": "{{count}} رسالة في الانتظار",
|
"session.followupDock.summary.one": "{{count}} رسالة في الانتظار",
|
||||||
"session.followupDock.summary.other": "{{count}} رسائل في الانتظار",
|
"session.followupDock.summary.other": "{{count}} رسائل في الانتظار",
|
||||||
"session.followupDock.sendNow": "إرسال الآن",
|
"session.followupDock.sendNow": "إرسال الآن",
|
||||||
@@ -487,9 +596,16 @@ export const dict = {
|
|||||||
"session.revertDock.expand": "توسيع الرسائل التي تم التراجع عنها",
|
"session.revertDock.expand": "توسيع الرسائل التي تم التراجع عنها",
|
||||||
"session.revertDock.restore": "استعادة الرسالة",
|
"session.revertDock.restore": "استعادة الرسالة",
|
||||||
"session.new.title": "ابنِ أي شيء",
|
"session.new.title": "ابنِ أي شيء",
|
||||||
|
"session.new.project.new": "مشروع جديد",
|
||||||
|
"session.new.project.search": "البحث عن المشاريع",
|
||||||
|
"session.new.project.add": "إضافة مشروع",
|
||||||
"session.new.worktree.main": "الفرع الرئيسي",
|
"session.new.worktree.main": "الفرع الرئيسي",
|
||||||
"session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
|
"session.new.worktree.mainWithBranch": "الفرع الرئيسي ({{branch}})",
|
||||||
"session.new.worktree.create": "إنشاء شجرة عمل جديدة",
|
"session.new.worktree.create": "إنشاء شجرة عمل جديدة",
|
||||||
|
"session.new.workspace.runIn": "تشغيل الجلسة في",
|
||||||
|
"session.new.workspace.triggerLocal": "محلي",
|
||||||
|
"session.new.workspace.local": "المستودع المحلي",
|
||||||
|
"session.new.workspace.existing": "مساحة عمل…",
|
||||||
"session.new.lastModified": "آخر تعديل",
|
"session.new.lastModified": "آخر تعديل",
|
||||||
"session.header.search.placeholder": "بحث {{project}}",
|
"session.header.search.placeholder": "بحث {{project}}",
|
||||||
"session.header.searchFiles": "بحث عن الملفات",
|
"session.header.searchFiles": "بحث عن الملفات",
|
||||||
@@ -550,6 +666,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "الجلسات الحديثة",
|
"sidebar.project.recentSessions": "الجلسات الحديثة",
|
||||||
"sidebar.project.viewAllSessions": "عرض جميع الجلسات",
|
"sidebar.project.viewAllSessions": "عرض جميع الجلسات",
|
||||||
"sidebar.project.clearNotifications": "مسح الإشعارات",
|
"sidebar.project.clearNotifications": "مسح الإشعارات",
|
||||||
|
"sidebar.empty.title": "لا توجد مشاريع مفتوحة",
|
||||||
|
"sidebar.empty.description": "افتح مشروعًا للبدء",
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
"settings.section.desktop": "سطح المكتب",
|
"settings.section.desktop": "سطح المكتب",
|
||||||
"settings.section.server": "الخادم",
|
"settings.section.server": "الخادم",
|
||||||
@@ -559,6 +677,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.title": "تكامل WSL",
|
"settings.desktop.wsl.title": "تكامل WSL",
|
||||||
"settings.desktop.wsl.description": "تشغيل خادم OpenCode داخل WSL على Windows.",
|
"settings.desktop.wsl.description": "تشغيل خادم OpenCode داخل WSL على Windows.",
|
||||||
"settings.general.section.appearance": "المظهر",
|
"settings.general.section.appearance": "المظهر",
|
||||||
|
"settings.general.section.advanced": "متقدم",
|
||||||
"settings.general.section.notifications": "إشعارات النظام",
|
"settings.general.section.notifications": "إشعارات النظام",
|
||||||
"settings.general.section.updates": "التحديثات",
|
"settings.general.section.updates": "التحديثات",
|
||||||
"settings.general.section.sounds": "المؤثرات الصوتية",
|
"settings.general.section.sounds": "المؤثرات الصوتية",
|
||||||
@@ -566,6 +685,11 @@ export const dict = {
|
|||||||
"settings.general.section.display": "شاشة العرض",
|
"settings.general.section.display": "شاشة العرض",
|
||||||
"settings.general.row.language.title": "اللغة",
|
"settings.general.row.language.title": "اللغة",
|
||||||
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
|
"settings.general.row.language.description": "تغيير لغة العرض لـ OpenCode",
|
||||||
|
"settings.general.row.shell.title": "Shell المحطة الطرفية",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"اختر shell المستخدم في المحطة الطرفية. تُستخدم واجهات shell المتوافقة أيضًا في استدعاءات أدوات الوكيل.",
|
||||||
|
"settings.general.row.shell.autoDefault": "تلقائي (افتراضي)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "المحطة الطرفية فقط",
|
||||||
"settings.general.row.appearance.title": "المظهر",
|
"settings.general.row.appearance.title": "المظهر",
|
||||||
"settings.general.row.appearance.description": "تخصيص كيفية ظهور OpenCode على جهازك",
|
"settings.general.row.appearance.description": "تخصيص كيفية ظهور OpenCode على جهازك",
|
||||||
"settings.general.row.colorScheme.title": "مخطط الألوان",
|
"settings.general.row.colorScheme.title": "مخطط الألوان",
|
||||||
@@ -574,14 +698,29 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
|
"settings.general.row.theme.description": "تخصيص سمة OpenCode.",
|
||||||
"settings.general.row.font.title": "خط الكود",
|
"settings.general.row.font.title": "خط الكود",
|
||||||
"settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية",
|
"settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "خط الطرفية",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "خصّص الخط المستخدم في الطرفية",
|
||||||
"settings.general.row.uiFont.title": "خط الواجهة",
|
"settings.general.row.uiFont.title": "خط الواجهة",
|
||||||
"settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها",
|
"settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها",
|
||||||
"settings.general.row.followup.title": "سلوك المتابعة",
|
"settings.general.row.followup.title": "سلوك المتابعة",
|
||||||
"settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار",
|
"settings.general.row.followup.description": "اختر ما إذا كانت طلبات المتابعة توجه فورًا أو تنتظر في قائمة انتظار",
|
||||||
"settings.general.row.followup.option.queue": "قائمة انتظار",
|
"settings.general.row.followup.option.queue": "قائمة انتظار",
|
||||||
"settings.general.row.followup.option.steer": "توجيه",
|
"settings.general.row.followup.option.steer": "توجيه",
|
||||||
|
"settings.general.row.showFileTree.title": "شجرة الملفات",
|
||||||
|
"settings.general.row.showFileTree.description": "إظهار لوحة شجرة الملفات في الجلسات",
|
||||||
|
"settings.general.row.showNavigation.title": "عناصر التحكم في التنقل",
|
||||||
|
"settings.general.row.showNavigation.description": "إظهار زري الرجوع والتقدم في شريط عنوان سطح المكتب",
|
||||||
|
"settings.general.row.showSearch.title": "لوحة الأوامر",
|
||||||
|
"settings.general.row.showSearch.description": "إظهار زر البحث ولوحة الأوامر في شريط العنوان",
|
||||||
|
"settings.general.row.showTerminal.title": "المحطة الطرفية",
|
||||||
|
"settings.general.row.showTerminal.description": "إظهار زر المحطة الطرفية في شريط عنوان سطح المكتب",
|
||||||
|
"settings.general.row.showStatus.title": "حالة الخادم",
|
||||||
|
"settings.general.row.showStatus.description": "إظهار زر حالة الخادم في شريط العنوان",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "التنقل السفلي",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"وضع شريط العنوان وعلامات تبويب الجلسات أسفل الشاشة على الأجهزة المحمولة",
|
||||||
|
"settings.general.row.showCustomAgents.title": "الوكلاء المخصصون",
|
||||||
|
"settings.general.row.showCustomAgents.description": "إظهار محدد الوكلاء في محرر الرسائل",
|
||||||
"settings.general.row.reasoningSummaries.title": "إظهار ملخصات الاستنتاج",
|
"settings.general.row.reasoningSummaries.title": "إظهار ملخصات الاستنتاج",
|
||||||
"settings.general.row.reasoningSummaries.description": "عرض ملخصات استنتاج النموذج في الشريط الزمني",
|
"settings.general.row.reasoningSummaries.description": "عرض ملخصات استنتاج النموذج في الشريط الزمني",
|
||||||
"settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell",
|
"settings.general.row.shellToolPartsExpanded.title": "توسيع أجزاء أداة shell",
|
||||||
@@ -590,6 +729,11 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit",
|
"settings.general.row.editToolPartsExpanded.title": "توسيع أجزاء أداة edit",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني",
|
"إظهار أجزاء أدوات edit و write و patch موسعة بشكل افتراضي في الشريط الزمني",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "التخطيط والتصاميم الجديدة",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"تمكين التخطيط والصفحة الرئيسية ومحرر الرسائل وواجهة الجلسة المعاد تصميمها",
|
||||||
|
"settings.general.row.pinchZoom.title": "التكبير بإيماءة القرص",
|
||||||
|
"settings.general.row.pinchZoom.description": "السماح بإيماءة القرص على لوحة اللمس وإيماءة Ctrl-scroll للتكبير",
|
||||||
"settings.general.row.wayland.title": "استخدام Wayland الأصلي",
|
"settings.general.row.wayland.title": "استخدام Wayland الأصلي",
|
||||||
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
|
"settings.general.row.wayland.description": "تعطيل التراجع إلى X11 على Wayland. يتطلب إعادة التشغيل.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
@@ -602,6 +746,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "التحقق يدويًا من التحديثات وتثبيتها إذا كانت متاحة",
|
"settings.updates.row.check.description": "التحقق يدويًا من التحديثات وتثبيتها إذا كانت متاحة",
|
||||||
"settings.updates.action.checkNow": "تحقق الآن",
|
"settings.updates.action.checkNow": "تحقق الآن",
|
||||||
"settings.updates.action.checking": "جارٍ التحقق...",
|
"settings.updates.action.checking": "جارٍ التحقق...",
|
||||||
|
"settings.updates.action.downloading": "جارٍ التنزيل...",
|
||||||
|
"settings.updates.action.installing": "جارٍ التثبيت...",
|
||||||
"settings.updates.toast.latest.title": "أنت على آخر إصدار",
|
"settings.updates.toast.latest.title": "أنت على آخر إصدار",
|
||||||
"settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.",
|
"settings.updates.toast.latest.description": "أنت تستخدم أحدث إصدار من OpenCode.",
|
||||||
"sound.option.none": "بلا",
|
"sound.option.none": "بلا",
|
||||||
|
|||||||
+152
-3
@@ -21,6 +21,9 @@ export const dict = {
|
|||||||
"theme.scheme.dark": "Escuro",
|
"theme.scheme.dark": "Escuro",
|
||||||
"command.sidebar.toggle": "Alternar barra lateral",
|
"command.sidebar.toggle": "Alternar barra lateral",
|
||||||
"command.project.open": "Abrir projeto",
|
"command.project.open": "Abrir projeto",
|
||||||
|
"command.project.previous": "Projeto anterior",
|
||||||
|
"command.project.next": "Próximo projeto",
|
||||||
|
"command.project.index": "Alternar para o projeto {{index}}",
|
||||||
"command.provider.connect": "Conectar provedor",
|
"command.provider.connect": "Conectar provedor",
|
||||||
"command.server.switch": "Trocar servidor",
|
"command.server.switch": "Trocar servidor",
|
||||||
"command.settings.open": "Abrir configurações",
|
"command.settings.open": "Abrir configurações",
|
||||||
@@ -39,6 +42,7 @@ export const dict = {
|
|||||||
"command.session.new": "Nova sessão",
|
"command.session.new": "Nova sessão",
|
||||||
"command.file.open": "Abrir arquivo",
|
"command.file.open": "Abrir arquivo",
|
||||||
"command.tab.close": "Fechar aba",
|
"command.tab.close": "Fechar aba",
|
||||||
|
"command.tab.reopenClosed": "Reabrir aba fechada",
|
||||||
"command.context.addSelection": "Adicionar seleção ao contexto",
|
"command.context.addSelection": "Adicionar seleção ao contexto",
|
||||||
"command.context.addSelection.description": "Adicionar as linhas selecionadas do arquivo atual",
|
"command.context.addSelection.description": "Adicionar as linhas selecionadas do arquivo atual",
|
||||||
"command.input.focus": "Focar entrada",
|
"command.input.focus": "Focar entrada",
|
||||||
@@ -193,6 +197,11 @@ export const dict = {
|
|||||||
"model.input.video": "vídeo",
|
"model.input.video": "vídeo",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "Permite: {{inputs}}",
|
"model.tooltip.allows": "Permite: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Contexto",
|
||||||
|
"model.tooltip.inputs": "Entradas",
|
||||||
|
"model.tooltip.model": "Modelo",
|
||||||
|
"model.tooltip.provider": "Provedor",
|
||||||
|
"model.tooltip.reasoning": "Raciocínio",
|
||||||
"model.tooltip.reasoning.allowed": "Permite raciocínio",
|
"model.tooltip.reasoning.allowed": "Permite raciocínio",
|
||||||
"model.tooltip.reasoning.none": "Sem raciocínio",
|
"model.tooltip.reasoning.none": "Sem raciocínio",
|
||||||
"model.tooltip.context": "Limite de contexto {{limit}}",
|
"model.tooltip.context": "Limite de contexto {{limit}}",
|
||||||
@@ -202,6 +211,7 @@ export const dict = {
|
|||||||
"common.loading": "Carregando",
|
"common.loading": "Carregando",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "Cancelar",
|
"common.cancel": "Cancelar",
|
||||||
|
"common.clear": "Limpar",
|
||||||
"common.connect": "Conectar",
|
"common.connect": "Conectar",
|
||||||
"common.disconnect": "Desconectar",
|
"common.disconnect": "Desconectar",
|
||||||
"common.continue": "Enviar",
|
"common.continue": "Enviar",
|
||||||
@@ -218,6 +228,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.normal": "Prompt",
|
"prompt.mode.normal": "Prompt",
|
||||||
"prompt.mode.shell.exit": "esc para sair",
|
"prompt.mode.shell.exit": "esc para sair",
|
||||||
|
"session.child.promptDisabled": "Não é possível enviar prompts em sessões de subagentes.",
|
||||||
|
"session.child.backToParent": "Voltar à sessão principal.",
|
||||||
"prompt.example.1": "Corrigir um TODO no código",
|
"prompt.example.1": "Corrigir um TODO no código",
|
||||||
"prompt.example.2": "Qual é a stack tecnológica deste projeto?",
|
"prompt.example.2": "Qual é a stack tecnológica deste projeto?",
|
||||||
"prompt.example.3": "Corrigir testes quebrados",
|
"prompt.example.3": "Corrigir testes quebrados",
|
||||||
@@ -314,6 +326,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "Excluir",
|
"dialog.server.menu.delete": "Excluir",
|
||||||
"dialog.server.current": "Servidor atual",
|
"dialog.server.current": "Servidor atual",
|
||||||
"dialog.server.status.default": "Padrão",
|
"dialog.server.status.default": "Padrão",
|
||||||
|
"wsl.server.add": "Adicionar servidor WSL",
|
||||||
|
"wsl.server.addShort": "Adicionar WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "Servidor WSL",
|
||||||
|
"wsl.server.retryStart": "Tentar iniciar novamente",
|
||||||
|
"wsl.server.updating": "Atualizando...",
|
||||||
|
"wsl.onboarding.step.distro": "Escolher distribuição",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "Verificando o WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "O Windows precisa ser reiniciado para concluir a instalação do WSL.",
|
||||||
|
"wsl.onboarding.ready": "O WSL está pronto.",
|
||||||
|
"wsl.onboarding.required": "O WSL é necessário para continuar.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Verificando distribuições...",
|
||||||
|
"wsl.onboarding.installingDistro": "Instalando {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "Verificando {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "Listando distribuições...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} está pronta.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "{{distro}} ainda não está instalada.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Abra {{distro}} uma vez para concluir a configuração.",
|
||||||
|
"wsl.onboarding.finishingDistro": "Concluindo a configuração de {{distro}}.",
|
||||||
|
"wsl.onboarding.pickDistro": "Escolha uma distribuição ou instale uma abaixo.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "Verificando o OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "Verificando o OpenCode em {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "Atualizando o OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "Atualizando o OpenCode em {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "Atualize o OpenCode em {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "Atualizar o OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "O OpenCode está pronto em {{distro}}.",
|
||||||
|
"wsl.onboarding.opencodeReady": "O OpenCode está pronto.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "Instale o OpenCode em {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "Instalar o OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Escolha primeiro uma distribuição.",
|
||||||
|
"wsl.onboarding.loadFailed": "Falha ao carregar o estado do WSL.",
|
||||||
|
"wsl.onboarding.loading": "Carregando...",
|
||||||
|
"wsl.onboarding.installedDistros": "Distribuições instaladas",
|
||||||
|
"wsl.onboarding.checkAgain": "Verificar novamente",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Pronta",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Verificando...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode não instalado",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "bash e curl ausentes",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "Sem suporte · Use o WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "Precisa de outra distribuição?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "Instale uma distribuição Linux pelo catálogo do WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL não instalado",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"O WSL (Windows Subsystem for Linux) é necessário para que o OpenCode possa adicionar um servidor WSL",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL indisponível",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "O OpenCode não conseguiu verificar o WSL neste computador.",
|
||||||
|
"wsl.onboarding.installWsl": "Instalar o WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"Reinicie o Windows para concluir a instalação do WSL e abra o OpenCode novamente.",
|
||||||
|
"wsl.onboarding.next": "Próximo",
|
||||||
|
"wsl.onboarding.refresh": "Atualizar",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Todas as distribuições instaladas já foram adicionadas.",
|
||||||
|
"wsl.onboarding.noDistros": "Nenhuma distribuição detectada ainda.",
|
||||||
|
"wsl.onboarding.install": "Instalar",
|
||||||
|
"wsl.onboarding.installing": "Instalando...",
|
||||||
|
"wsl.onboarding.installDistro": "Instalar distribuição",
|
||||||
|
"wsl.onboarding.searchDistros": "Buscar distribuições",
|
||||||
|
"wsl.onboarding.wsl2Required": "O WSL 2 é necessário.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Esta distribuição precisa do bash e do curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "Abrir terminal",
|
||||||
|
"wsl.onboarding.path": "Caminho: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "não encontrado",
|
||||||
|
"wsl.onboarding.version": "Versão: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "desconhecido",
|
||||||
|
"wsl.onboarding.desktopVersion": "desktop {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "A versão instalada não corresponde à versão do aplicativo para desktop.",
|
||||||
|
"wsl.onboarding.adding": "Adicionando...",
|
||||||
"dialog.project.edit.title": "Editar projeto",
|
"dialog.project.edit.title": "Editar projeto",
|
||||||
"dialog.project.edit.name": "Nome",
|
"dialog.project.edit.name": "Nome",
|
||||||
"dialog.project.edit.icon": "Ícone",
|
"dialog.project.edit.icon": "Ícone",
|
||||||
@@ -371,6 +452,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
"toast.language.title": "Idioma",
|
"toast.language.title": "Idioma",
|
||||||
@@ -407,10 +489,12 @@ export const dict = {
|
|||||||
"toast.update.action.notYet": "Agora não",
|
"toast.update.action.notYet": "Agora não",
|
||||||
"error.page.title": "Algo deu errado",
|
"error.page.title": "Algo deu errado",
|
||||||
"error.page.description": "Ocorreu um erro ao carregar a aplicação.",
|
"error.page.description": "Ocorreu um erro ao carregar a aplicação.",
|
||||||
|
"error.page.description.localServerStartup": "Ocorreu um erro ao iniciar o servidor local.",
|
||||||
"error.page.details.label": "Detalhes do Erro",
|
"error.page.details.label": "Detalhes do Erro",
|
||||||
"error.page.action.restart": "Reiniciar",
|
"error.page.action.restart": "Reiniciar",
|
||||||
"error.page.action.report": "Reportar erro",
|
"error.page.action.report": "Reportar erro",
|
||||||
"error.page.action.reported": "Erro reportado",
|
"error.page.action.reported": "Erro reportado",
|
||||||
|
"error.page.action.exportLogs": "Exportar logs",
|
||||||
"error.page.action.checking": "Verificando...",
|
"error.page.action.checking": "Verificando...",
|
||||||
"error.page.action.checkUpdates": "Verificar atualizações",
|
"error.page.action.checkUpdates": "Verificar atualizações",
|
||||||
"error.page.action.updateTo": "Atualizar para {{version}}",
|
"error.page.action.updateTo": "Atualizar para {{version}}",
|
||||||
@@ -453,10 +537,30 @@ export const dict = {
|
|||||||
"home.recentProjects": "Projetos recentes",
|
"home.recentProjects": "Projetos recentes",
|
||||||
"home.empty.title": "Nenhum projeto recente",
|
"home.empty.title": "Nenhum projeto recente",
|
||||||
"home.empty.description": "Comece abrindo um projeto local",
|
"home.empty.description": "Comece abrindo um projeto local",
|
||||||
|
"home.title": "Início",
|
||||||
|
"home.projects": "Projetos",
|
||||||
|
"home.project.add": "Adicionar projeto",
|
||||||
|
"home.recentlyClosed": "Projetos fechados recentemente",
|
||||||
|
"home.server.collapse": "Recolher projetos do servidor",
|
||||||
|
"home.server.expand": "Expandir projetos do servidor",
|
||||||
|
"home.sessions.search.placeholder": "Buscar sessões",
|
||||||
|
"home.sessions.search.placeholder.scoped": "Buscar sessões em {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "Sessões",
|
||||||
|
"home.sessions.search.noResults": "Nenhuma sessão encontrada para {{query}}",
|
||||||
|
"home.sessions.empty": "Nada por aqui ainda",
|
||||||
|
"home.sessions.empty.description": "Crie uma sessão para começar",
|
||||||
|
"home.sessions.group.today": "Hoje",
|
||||||
|
"home.sessions.group.yesterday": "Ontem",
|
||||||
|
"home.sessions.group.older": "Mais antigas",
|
||||||
"session.tab.session": "Sessão",
|
"session.tab.session": "Sessão",
|
||||||
"session.tab.review": "Revisão",
|
"session.tab.review": "Revisão",
|
||||||
"session.tab.context": "Contexto",
|
"session.tab.context": "Contexto",
|
||||||
|
"session.tab.unknown": "Sessão desconhecida",
|
||||||
"session.panel.reviewAndFiles": "Revisão e arquivos",
|
"session.panel.reviewAndFiles": "Revisão e arquivos",
|
||||||
|
"session.error.notFound": "Esta sessão não foi encontrada",
|
||||||
|
"session.error.notFound.description": "Esta aba aponta para uma sessão que não existe mais neste servidor.",
|
||||||
|
"session.error.notFound.closeTab": "Fechar aba",
|
||||||
|
"session.error.serverConnection": "Não é possível conectar a este servidor",
|
||||||
"session.review.filesChanged": "{{count}} Arquivos Alterados",
|
"session.review.filesChanged": "{{count}} Arquivos Alterados",
|
||||||
"session.review.change.one": "Alteração",
|
"session.review.change.one": "Alteração",
|
||||||
"session.review.change.other": "Alterações",
|
"session.review.change.other": "Alterações",
|
||||||
@@ -466,6 +570,8 @@ export const dict = {
|
|||||||
"session.review.noSnapshot":
|
"session.review.noSnapshot":
|
||||||
"O rastreamento de snapshot está desabilitado na configuração, então as alterações da sessão estão indisponíveis",
|
"O rastreamento de snapshot está desabilitado na configuração, então as alterações da sessão estão indisponíveis",
|
||||||
"session.review.noChanges": "Sem alterações",
|
"session.review.noChanges": "Sem alterações",
|
||||||
|
"session.review.noUncommittedChanges": "Ainda não há alterações sem commit",
|
||||||
|
"session.review.noBranchChanges": "Ainda não há alterações na branch",
|
||||||
"session.files.selectToOpen": "Selecione um arquivo para abrir",
|
"session.files.selectToOpen": "Selecione um arquivo para abrir",
|
||||||
"session.files.all": "Todos os arquivos",
|
"session.files.all": "Todos os arquivos",
|
||||||
"session.files.empty": "Nenhum arquivo",
|
"session.files.empty": "Nenhum arquivo",
|
||||||
@@ -479,6 +585,10 @@ export const dict = {
|
|||||||
"session.todo.title": "Tarefas",
|
"session.todo.title": "Tarefas",
|
||||||
"session.todo.collapse": "Recolher",
|
"session.todo.collapse": "Recolher",
|
||||||
"session.todo.expand": "Expandir",
|
"session.todo.expand": "Expandir",
|
||||||
|
"session.question.minimize": "Minimizar pergunta",
|
||||||
|
"session.question.restore": "Restaurar pergunta",
|
||||||
|
"session.question.pending.one": "{{count}} pergunta pendente",
|
||||||
|
"session.question.pending.other": "{{count}} perguntas pendentes",
|
||||||
"session.followupDock.summary.one": "{{count}} mensagem na fila",
|
"session.followupDock.summary.one": "{{count}} mensagem na fila",
|
||||||
"session.followupDock.summary.other": "{{count}} mensagens na fila",
|
"session.followupDock.summary.other": "{{count}} mensagens na fila",
|
||||||
"session.followupDock.sendNow": "Enviar agora",
|
"session.followupDock.sendNow": "Enviar agora",
|
||||||
@@ -491,9 +601,16 @@ export const dict = {
|
|||||||
"session.revertDock.expand": "Expandir mensagens revertidas",
|
"session.revertDock.expand": "Expandir mensagens revertidas",
|
||||||
"session.revertDock.restore": "Restaurar mensagem",
|
"session.revertDock.restore": "Restaurar mensagem",
|
||||||
"session.new.title": "Crie qualquer coisa",
|
"session.new.title": "Crie qualquer coisa",
|
||||||
|
"session.new.project.new": "Novo projeto",
|
||||||
|
"session.new.project.search": "Buscar projetos",
|
||||||
|
"session.new.project.add": "Adicionar projeto",
|
||||||
"session.new.worktree.main": "Branch principal",
|
"session.new.worktree.main": "Branch principal",
|
||||||
"session.new.worktree.mainWithBranch": "Branch principal ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Branch principal ({{branch}})",
|
||||||
"session.new.worktree.create": "Criar novo worktree",
|
"session.new.worktree.create": "Criar novo worktree",
|
||||||
|
"session.new.workspace.runIn": "Executar sessão em",
|
||||||
|
"session.new.workspace.triggerLocal": "Local",
|
||||||
|
"session.new.workspace.local": "Repositório local",
|
||||||
|
"session.new.workspace.existing": "Espaço de trabalho…",
|
||||||
"session.new.lastModified": "Última modificação",
|
"session.new.lastModified": "Última modificação",
|
||||||
"session.header.search.placeholder": "Buscar {{project}}",
|
"session.header.search.placeholder": "Buscar {{project}}",
|
||||||
"session.header.searchFiles": "Buscar arquivos",
|
"session.header.searchFiles": "Buscar arquivos",
|
||||||
@@ -557,15 +674,18 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "Sessões recentes",
|
"sidebar.project.recentSessions": "Sessões recentes",
|
||||||
"sidebar.project.viewAllSessions": "Ver todas as sessões",
|
"sidebar.project.viewAllSessions": "Ver todas as sessões",
|
||||||
"sidebar.project.clearNotifications": "Limpar notificações",
|
"sidebar.project.clearNotifications": "Limpar notificações",
|
||||||
|
"sidebar.empty.title": "Nenhum projeto aberto",
|
||||||
|
"sidebar.empty.description": "Abra um projeto para começar",
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
"settings.section.desktop": "Desktop",
|
"settings.section.desktop": "Desktop",
|
||||||
"settings.section.server": "Servidor",
|
"settings.section.server": "Servidor",
|
||||||
"settings.tab.general": "Geral",
|
"settings.tab.general": "Geral",
|
||||||
"settings.tab.shortcuts": "Atalhos",
|
"settings.tab.shortcuts": "Atalhos",
|
||||||
"settings.desktop.section.wsl": "WSL",
|
"settings.desktop.section.wsl": "WSL",
|
||||||
"settings.desktop.wsl.title": "WSL integration",
|
"settings.desktop.wsl.title": "Integração com WSL",
|
||||||
"settings.desktop.wsl.description": "Executar o servidor OpenCode dentro do WSL no Windows.",
|
"settings.desktop.wsl.description": "Executar o servidor OpenCode dentro do WSL no Windows.",
|
||||||
"settings.general.section.appearance": "Aparência",
|
"settings.general.section.appearance": "Aparência",
|
||||||
|
"settings.general.section.advanced": "Avançado",
|
||||||
"settings.general.section.notifications": "Notificações do sistema",
|
"settings.general.section.notifications": "Notificações do sistema",
|
||||||
"settings.general.section.updates": "Atualizações",
|
"settings.general.section.updates": "Atualizações",
|
||||||
"settings.general.section.sounds": "Efeitos sonoros",
|
"settings.general.section.sounds": "Efeitos sonoros",
|
||||||
@@ -573,6 +693,11 @@ export const dict = {
|
|||||||
"settings.general.section.display": "Tela",
|
"settings.general.section.display": "Tela",
|
||||||
"settings.general.row.language.title": "Idioma",
|
"settings.general.row.language.title": "Idioma",
|
||||||
"settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
|
"settings.general.row.language.description": "Alterar o idioma de exibição do OpenCode",
|
||||||
|
"settings.general.row.shell.title": "Shell do terminal",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"Escolha o shell usado no terminal. Os shells compatíveis também são usados nas chamadas de ferramentas do agente.",
|
||||||
|
"settings.general.row.shell.autoDefault": "Automático (Padrão)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "somente no terminal",
|
||||||
"settings.general.row.appearance.title": "Aparência",
|
"settings.general.row.appearance.title": "Aparência",
|
||||||
"settings.general.row.appearance.description": "Personalize como o OpenCode aparece no seu dispositivo",
|
"settings.general.row.appearance.description": "Personalize como o OpenCode aparece no seu dispositivo",
|
||||||
"settings.general.row.colorScheme.title": "Esquema de cores",
|
"settings.general.row.colorScheme.title": "Esquema de cores",
|
||||||
@@ -581,8 +706,8 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
|
"settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
|
||||||
"settings.general.row.font.title": "Fonte de código",
|
"settings.general.row.font.title": "Fonte de código",
|
||||||
"settings.general.row.font.description": "Personalize a fonte usada em blocos de código",
|
"settings.general.row.font.description": "Personalize a fonte usada em blocos de código",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "Fonte do terminal",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "Personalize a fonte usada no terminal",
|
||||||
"settings.general.row.uiFont.title": "Fonte da interface",
|
"settings.general.row.uiFont.title": "Fonte da interface",
|
||||||
"settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface",
|
"settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface",
|
||||||
"settings.general.row.followup.title": "Comportamento de acompanhamento",
|
"settings.general.row.followup.title": "Comportamento de acompanhamento",
|
||||||
@@ -590,6 +715,22 @@ export const dict = {
|
|||||||
"Escolha se os prompts de acompanhamento orientam imediatamente ou esperam na fila",
|
"Escolha se os prompts de acompanhamento orientam imediatamente ou esperam na fila",
|
||||||
"settings.general.row.followup.option.queue": "Fila",
|
"settings.general.row.followup.option.queue": "Fila",
|
||||||
"settings.general.row.followup.option.steer": "Orientar",
|
"settings.general.row.followup.option.steer": "Orientar",
|
||||||
|
"settings.general.row.showFileTree.title": "Árvore de arquivos",
|
||||||
|
"settings.general.row.showFileTree.description": "Mostrar o painel da árvore de arquivos nas sessões",
|
||||||
|
"settings.general.row.showNavigation.title": "Controles de navegação",
|
||||||
|
"settings.general.row.showNavigation.description":
|
||||||
|
"Mostrar os botões de voltar e avançar na barra de título do desktop",
|
||||||
|
"settings.general.row.showSearch.title": "Paleta de comandos",
|
||||||
|
"settings.general.row.showSearch.description": "Mostrar o botão de busca e paleta de comandos na barra de título",
|
||||||
|
"settings.general.row.showTerminal.title": "Terminal",
|
||||||
|
"settings.general.row.showTerminal.description": "Mostrar o botão do terminal na barra de título do desktop",
|
||||||
|
"settings.general.row.showStatus.title": "Status do servidor",
|
||||||
|
"settings.general.row.showStatus.description": "Mostrar o botão de status do servidor na barra de título",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Navegação inferior",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"Posicionar a barra de título e as abas da sessão na parte inferior da tela em dispositivos móveis",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Agentes personalizados",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Mostrar o seletor de agentes na área de composição",
|
||||||
"settings.general.row.reasoningSummaries.title": "Mostrar resumos de raciocínio",
|
"settings.general.row.reasoningSummaries.title": "Mostrar resumos de raciocínio",
|
||||||
"settings.general.row.reasoningSummaries.description": "Exibir resumos de raciocínio do modelo na linha do tempo",
|
"settings.general.row.reasoningSummaries.description": "Exibir resumos de raciocínio do modelo na linha do tempo",
|
||||||
"settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell",
|
"settings.general.row.shellToolPartsExpanded.title": "Expandir partes da ferramenta shell",
|
||||||
@@ -598,6 +739,12 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição",
|
"settings.general.row.editToolPartsExpanded.title": "Expandir partes da ferramenta de edição",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo",
|
"Mostrar partes das ferramentas de edição, escrita e patch expandidas por padrão na linha do tempo",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Novo layout e design",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Ativar o layout, a página inicial, a área de composição e a interface de sessão reformulados",
|
||||||
|
"settings.general.row.pinchZoom.title": "Zoom com gesto de pinça",
|
||||||
|
"settings.general.row.pinchZoom.description":
|
||||||
|
"Permitir gestos de pinça no trackpad e de Ctrl+rolagem para aplicar zoom",
|
||||||
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
||||||
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
|
"settings.general.row.wayland.description": "Desabilitar fallback X11 no Wayland. Requer reinicialização.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
@@ -610,6 +757,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "Verificar atualizações manualmente e instalar se houver",
|
"settings.updates.row.check.description": "Verificar atualizações manualmente e instalar se houver",
|
||||||
"settings.updates.action.checkNow": "Verificar agora",
|
"settings.updates.action.checkNow": "Verificar agora",
|
||||||
"settings.updates.action.checking": "Verificando...",
|
"settings.updates.action.checking": "Verificando...",
|
||||||
|
"settings.updates.action.downloading": "Baixando...",
|
||||||
|
"settings.updates.action.installing": "Instalando...",
|
||||||
"settings.updates.toast.latest.title": "Você está atualizado",
|
"settings.updates.toast.latest.title": "Você está atualizado",
|
||||||
"settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.",
|
"settings.updates.toast.latest.description": "Você está usando a versão mais recente do OpenCode.",
|
||||||
"sound.option.none": "Nenhum",
|
"sound.option.none": "Nenhum",
|
||||||
|
|||||||
+151
-2
@@ -23,6 +23,9 @@ export const dict = {
|
|||||||
|
|
||||||
"command.sidebar.toggle": "Prikaži/sakrij bočnu traku",
|
"command.sidebar.toggle": "Prikaži/sakrij bočnu traku",
|
||||||
"command.project.open": "Otvori projekat",
|
"command.project.open": "Otvori projekat",
|
||||||
|
"command.project.previous": "Prethodni projekat",
|
||||||
|
"command.project.next": "Sljedeći projekat",
|
||||||
|
"command.project.index": "Prebaci na projekat {{index}}",
|
||||||
"command.provider.connect": "Poveži provajdera",
|
"command.provider.connect": "Poveži provajdera",
|
||||||
"command.server.switch": "Promijeni server",
|
"command.server.switch": "Promijeni server",
|
||||||
"command.settings.open": "Otvori postavke",
|
"command.settings.open": "Otvori postavke",
|
||||||
@@ -45,6 +48,7 @@ export const dict = {
|
|||||||
"command.session.new": "Nova sesija",
|
"command.session.new": "Nova sesija",
|
||||||
"command.file.open": "Otvori datoteku",
|
"command.file.open": "Otvori datoteku",
|
||||||
"command.tab.close": "Zatvori karticu",
|
"command.tab.close": "Zatvori karticu",
|
||||||
|
"command.tab.reopenClosed": "Ponovo otvori zatvorenu karticu",
|
||||||
"command.context.addSelection": "Dodaj odabir u kontekst",
|
"command.context.addSelection": "Dodaj odabir u kontekst",
|
||||||
"command.context.addSelection.description": "Dodaj odabrane linije iz trenutne datoteke",
|
"command.context.addSelection.description": "Dodaj odabrane linije iz trenutne datoteke",
|
||||||
"command.input.focus": "Fokusiraj polje za unos",
|
"command.input.focus": "Fokusiraj polje za unos",
|
||||||
@@ -209,6 +213,11 @@ export const dict = {
|
|||||||
"model.input.video": "video",
|
"model.input.video": "video",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "Podržava: {{inputs}}",
|
"model.tooltip.allows": "Podržava: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Kontekst",
|
||||||
|
"model.tooltip.inputs": "Ulazi",
|
||||||
|
"model.tooltip.model": "Model",
|
||||||
|
"model.tooltip.provider": "Provajder",
|
||||||
|
"model.tooltip.reasoning": "Rasuđivanje",
|
||||||
"model.tooltip.reasoning.allowed": "Podržava rasuđivanje",
|
"model.tooltip.reasoning.allowed": "Podržava rasuđivanje",
|
||||||
"model.tooltip.reasoning.none": "Bez rasuđivanja",
|
"model.tooltip.reasoning.none": "Bez rasuđivanja",
|
||||||
"model.tooltip.context": "Limit konteksta {{limit}}",
|
"model.tooltip.context": "Limit konteksta {{limit}}",
|
||||||
@@ -219,6 +228,7 @@ export const dict = {
|
|||||||
"common.loading": "Učitavanje",
|
"common.loading": "Učitavanje",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "Otkaži",
|
"common.cancel": "Otkaži",
|
||||||
|
"common.clear": "Očisti",
|
||||||
"common.connect": "Poveži",
|
"common.connect": "Poveži",
|
||||||
"common.disconnect": "Prekini vezu",
|
"common.disconnect": "Prekini vezu",
|
||||||
"common.continue": "Pošalji",
|
"common.continue": "Pošalji",
|
||||||
@@ -236,6 +246,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.normal": "Prompt",
|
"prompt.mode.normal": "Prompt",
|
||||||
"prompt.mode.shell.exit": "esc za izlaz",
|
"prompt.mode.shell.exit": "esc za izlaz",
|
||||||
|
"session.child.promptDisabled": "Sesijama podagenta nije moguće slati upite.",
|
||||||
|
"session.child.backToParent": "Nazad na glavnu sesiju.",
|
||||||
|
|
||||||
"prompt.example.1": "Popravi TODO u bazi koda",
|
"prompt.example.1": "Popravi TODO u bazi koda",
|
||||||
"prompt.example.2": "Koji je tehnološki stack ovog projekta?",
|
"prompt.example.2": "Koji je tehnološki stack ovog projekta?",
|
||||||
@@ -342,6 +354,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "Izbriši",
|
"dialog.server.menu.delete": "Izbriši",
|
||||||
"dialog.server.current": "Trenutni server",
|
"dialog.server.current": "Trenutni server",
|
||||||
"dialog.server.status.default": "Podrazumijevano",
|
"dialog.server.status.default": "Podrazumijevano",
|
||||||
|
"wsl.server.add": "Dodaj WSL server",
|
||||||
|
"wsl.server.addShort": "Dodaj WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "WSL server",
|
||||||
|
"wsl.server.retryStart": "Ponovi pokretanje",
|
||||||
|
"wsl.server.updating": "Ažuriranje...",
|
||||||
|
"wsl.onboarding.step.distro": "Odaberi distribuciju",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "Provjera WSL-a...",
|
||||||
|
"wsl.onboarding.restartRequired": "Potrebno je ponovo pokrenuti Windows da bi se završila instalacija WSL-a.",
|
||||||
|
"wsl.onboarding.ready": "WSL je spreman.",
|
||||||
|
"wsl.onboarding.required": "WSL je potreban za nastavak.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Provjera distribucija...",
|
||||||
|
"wsl.onboarding.installingDistro": "Instaliranje {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "Provjera {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "Učitavanje liste distribucija...",
|
||||||
|
"wsl.onboarding.distroReady": "Distribucija {{distro}} je spremna.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "Distribucija {{distro}} još nije instalirana.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Otvori {{distro}} jednom da završiš postavljanje.",
|
||||||
|
"wsl.onboarding.finishingDistro": "Završavanje postavljanja za {{distro}}.",
|
||||||
|
"wsl.onboarding.pickDistro": "Odaberi distribuciju ili instaliraj jednu ispod.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "Provjera OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "Provjera OpenCode u distribuciji {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "Ažuriranje OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "Ažuriranje OpenCode u distribuciji {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "Ažuriraj OpenCode u distribuciji {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "Ažuriraj OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode je spreman u distribuciji {{distro}}.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode je spreman.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "Instaliraj OpenCode u distribuciji {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "Instaliraj OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Prvo odaberi distribuciju.",
|
||||||
|
"wsl.onboarding.loadFailed": "Nije uspjelo učitavanje WSL stanja.",
|
||||||
|
"wsl.onboarding.loading": "Učitavanje...",
|
||||||
|
"wsl.onboarding.installedDistros": "Instalirane distribucije",
|
||||||
|
"wsl.onboarding.checkAgain": "Provjeri ponovo",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Spremno",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Provjera...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nije instaliran",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "Nedostaju bash i curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "Nije podržano · Koristi WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "Treba ti druga distribucija?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "Instaliraj Linux distribuciju iz WSL kataloga",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL nije instaliran",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"WSL (Windows Subsystem for Linux) je potreban da bi OpenCode mogao dodati WSL server",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL nije dostupan",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode nije mogao provjeriti WSL na ovom uređaju.",
|
||||||
|
"wsl.onboarding.installWsl": "Instaliraj WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"Ponovo pokreni Windows da završiš instalaciju WSL-a, zatim ponovo otvori OpenCode.",
|
||||||
|
"wsl.onboarding.next": "Dalje",
|
||||||
|
"wsl.onboarding.refresh": "Osvježi",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Sve instalirane distribucije su već dodane.",
|
||||||
|
"wsl.onboarding.noDistros": "Još nema otkrivenih distribucija.",
|
||||||
|
"wsl.onboarding.install": "Instaliraj",
|
||||||
|
"wsl.onboarding.installing": "Instaliranje...",
|
||||||
|
"wsl.onboarding.installDistro": "Instaliraj distribuciju",
|
||||||
|
"wsl.onboarding.searchDistros": "Pretraži distribucije",
|
||||||
|
"wsl.onboarding.wsl2Required": "Potreban je WSL 2.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Ovoj distribuciji su potrebni bash i curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "Otvori terminal",
|
||||||
|
"wsl.onboarding.path": "Putanja: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "nije pronađeno",
|
||||||
|
"wsl.onboarding.version": "Verzija: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "nepoznato",
|
||||||
|
"wsl.onboarding.desktopVersion": "desktop {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "Instalirana verzija ne odgovara verziji desktop aplikacije.",
|
||||||
|
"wsl.onboarding.adding": "Dodavanje...",
|
||||||
|
|
||||||
"dialog.project.edit.title": "Uredi projekat",
|
"dialog.project.edit.title": "Uredi projekat",
|
||||||
"dialog.project.edit.name": "Naziv",
|
"dialog.project.edit.name": "Naziv",
|
||||||
@@ -405,6 +486,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
|
|
||||||
@@ -453,10 +535,12 @@ export const dict = {
|
|||||||
|
|
||||||
"error.page.title": "Nešto je pošlo po zlu",
|
"error.page.title": "Nešto je pošlo po zlu",
|
||||||
"error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.",
|
"error.page.description": "Došlo je do greške prilikom učitavanja aplikacije.",
|
||||||
|
"error.page.description.localServerStartup": "Došlo je do greške pri pokretanju lokalnog servera.",
|
||||||
"error.page.details.label": "Detalji greške",
|
"error.page.details.label": "Detalji greške",
|
||||||
"error.page.action.restart": "Restartuj",
|
"error.page.action.restart": "Restartuj",
|
||||||
"error.page.action.report": "Prijavi grešku",
|
"error.page.action.report": "Prijavi grešku",
|
||||||
"error.page.action.reported": "Greška prijavljena",
|
"error.page.action.reported": "Greška prijavljena",
|
||||||
|
"error.page.action.exportLogs": "Izvezi logove",
|
||||||
"error.page.action.checking": "Provjera...",
|
"error.page.action.checking": "Provjera...",
|
||||||
"error.page.action.checkUpdates": "Provjeri ažuriranja",
|
"error.page.action.checkUpdates": "Provjeri ažuriranja",
|
||||||
"error.page.action.updateTo": "Ažuriraj na {{version}}",
|
"error.page.action.updateTo": "Ažuriraj na {{version}}",
|
||||||
@@ -504,11 +588,31 @@ export const dict = {
|
|||||||
"home.recentProjects": "Nedavni projekti",
|
"home.recentProjects": "Nedavni projekti",
|
||||||
"home.empty.title": "Nema nedavnih projekata",
|
"home.empty.title": "Nema nedavnih projekata",
|
||||||
"home.empty.description": "Kreni tako što ćeš otvoriti lokalni projekat",
|
"home.empty.description": "Kreni tako što ćeš otvoriti lokalni projekat",
|
||||||
|
"home.title": "Početna",
|
||||||
|
"home.projects": "Projekti",
|
||||||
|
"home.project.add": "Dodaj projekat",
|
||||||
|
"home.recentlyClosed": "Nedavno zatvoreni projekti",
|
||||||
|
"home.server.collapse": "Sažmi projekte servera",
|
||||||
|
"home.server.expand": "Proširi projekte servera",
|
||||||
|
"home.sessions.search.placeholder": "Pretraži sesije",
|
||||||
|
"home.sessions.search.placeholder.scoped": "Pretraži sesije u {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "Sesije",
|
||||||
|
"home.sessions.search.noResults": "Nema pronađenih sesija za {{query}}",
|
||||||
|
"home.sessions.empty": "Ovdje još nema ničega",
|
||||||
|
"home.sessions.empty.description": "Kreiraj sesiju za početak",
|
||||||
|
"home.sessions.group.today": "Danas",
|
||||||
|
"home.sessions.group.yesterday": "Jučer",
|
||||||
|
"home.sessions.group.older": "Starije",
|
||||||
|
|
||||||
"session.tab.session": "Sesija",
|
"session.tab.session": "Sesija",
|
||||||
"session.tab.review": "Pregled",
|
"session.tab.review": "Pregled",
|
||||||
"session.tab.context": "Kontekst",
|
"session.tab.context": "Kontekst",
|
||||||
|
"session.tab.unknown": "Nepoznata sesija",
|
||||||
"session.panel.reviewAndFiles": "Pregled i datoteke",
|
"session.panel.reviewAndFiles": "Pregled i datoteke",
|
||||||
|
"session.error.notFound": "Ova sesija nije pronađena",
|
||||||
|
"session.error.notFound.description": "Ova kartica upućuje na sesiju koja više ne postoji na ovom serveru.",
|
||||||
|
"session.error.notFound.closeTab": "Zatvori karticu",
|
||||||
|
"session.error.serverConnection": "Nije moguće povezati se s ovim serverom",
|
||||||
"session.review.filesChanged": "Izmijenjeno {{count}} datoteka",
|
"session.review.filesChanged": "Izmijenjeno {{count}} datoteka",
|
||||||
"session.review.change.one": "Izmjena",
|
"session.review.change.one": "Izmjena",
|
||||||
"session.review.change.other": "Izmjene",
|
"session.review.change.other": "Izmjene",
|
||||||
@@ -518,6 +622,8 @@ export const dict = {
|
|||||||
"session.review.noSnapshot":
|
"session.review.noSnapshot":
|
||||||
"Praćenje snimaka (snapshot) je onemogućeno u konfiguraciji, pa promjene sesije nisu dostupne",
|
"Praćenje snimaka (snapshot) je onemogućeno u konfiguraciji, pa promjene sesije nisu dostupne",
|
||||||
"session.review.noChanges": "Nema izmjena",
|
"session.review.noChanges": "Nema izmjena",
|
||||||
|
"session.review.noUncommittedChanges": "Još nema nepotvrđenih promjena",
|
||||||
|
"session.review.noBranchChanges": "Još nema promjena na grani",
|
||||||
|
|
||||||
"session.files.selectToOpen": "Odaberi datoteku za otvaranje",
|
"session.files.selectToOpen": "Odaberi datoteku za otvaranje",
|
||||||
"session.files.all": "Sve datoteke",
|
"session.files.all": "Sve datoteke",
|
||||||
@@ -534,6 +640,10 @@ export const dict = {
|
|||||||
"session.todo.title": "Zadaci",
|
"session.todo.title": "Zadaci",
|
||||||
"session.todo.collapse": "Sažmi",
|
"session.todo.collapse": "Sažmi",
|
||||||
"session.todo.expand": "Proširi",
|
"session.todo.expand": "Proširi",
|
||||||
|
"session.question.minimize": "Minimiziraj pitanje",
|
||||||
|
"session.question.restore": "Vrati pitanje",
|
||||||
|
"session.question.pending.one": "{{count}} pitanje na čekanju",
|
||||||
|
"session.question.pending.other": "{{count}} pitanja na čekanju",
|
||||||
"session.followupDock.summary.one": "{{count}} poruka na čekanju",
|
"session.followupDock.summary.one": "{{count}} poruka na čekanju",
|
||||||
"session.followupDock.summary.other": "{{count}} poruka na čekanju",
|
"session.followupDock.summary.other": "{{count}} poruka na čekanju",
|
||||||
"session.followupDock.sendNow": "Pošalji sada",
|
"session.followupDock.sendNow": "Pošalji sada",
|
||||||
@@ -547,9 +657,16 @@ export const dict = {
|
|||||||
"session.revertDock.restore": "Vrati poruku",
|
"session.revertDock.restore": "Vrati poruku",
|
||||||
|
|
||||||
"session.new.title": "Napravi bilo šta",
|
"session.new.title": "Napravi bilo šta",
|
||||||
|
"session.new.project.new": "Novi projekat",
|
||||||
|
"session.new.project.search": "Pretraži projekte",
|
||||||
|
"session.new.project.add": "Dodaj projekat",
|
||||||
"session.new.worktree.main": "Glavna grana",
|
"session.new.worktree.main": "Glavna grana",
|
||||||
"session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Glavna grana ({{branch}})",
|
||||||
"session.new.worktree.create": "Kreiraj novi worktree",
|
"session.new.worktree.create": "Kreiraj novi worktree",
|
||||||
|
"session.new.workspace.runIn": "Pokreni sesiju u",
|
||||||
|
"session.new.workspace.triggerLocal": "Lokalno",
|
||||||
|
"session.new.workspace.local": "Lokalni repozitorij",
|
||||||
|
"session.new.workspace.existing": "Radni prostor…",
|
||||||
"session.new.lastModified": "Posljednja izmjena",
|
"session.new.lastModified": "Posljednja izmjena",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Pretraži {{project}}",
|
"session.header.search.placeholder": "Pretraži {{project}}",
|
||||||
@@ -618,6 +735,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "Nedavne sesije",
|
"sidebar.project.recentSessions": "Nedavne sesije",
|
||||||
"sidebar.project.viewAllSessions": "Prikaži sve sesije",
|
"sidebar.project.viewAllSessions": "Prikaži sve sesije",
|
||||||
"sidebar.project.clearNotifications": "Očisti obavijesti",
|
"sidebar.project.clearNotifications": "Očisti obavijesti",
|
||||||
|
"sidebar.empty.title": "Nema otvorenih projekata",
|
||||||
|
"sidebar.empty.description": "Otvori projekat za početak",
|
||||||
|
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
|
|
||||||
@@ -630,6 +749,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.description": "Pokreni OpenCode server unutar WSL-a na Windowsu.",
|
"settings.desktop.wsl.description": "Pokreni OpenCode server unutar WSL-a na Windowsu.",
|
||||||
|
|
||||||
"settings.general.section.appearance": "Izgled",
|
"settings.general.section.appearance": "Izgled",
|
||||||
|
"settings.general.section.advanced": "Napredno",
|
||||||
"settings.general.section.notifications": "Sistemske obavijesti",
|
"settings.general.section.notifications": "Sistemske obavijesti",
|
||||||
"settings.general.section.updates": "Ažuriranja",
|
"settings.general.section.updates": "Ažuriranja",
|
||||||
"settings.general.section.sounds": "Zvučni efekti",
|
"settings.general.section.sounds": "Zvučni efekti",
|
||||||
@@ -638,6 +758,11 @@ export const dict = {
|
|||||||
|
|
||||||
"settings.general.row.language.title": "Jezik",
|
"settings.general.row.language.title": "Jezik",
|
||||||
"settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u",
|
"settings.general.row.language.description": "Promijeni jezik prikaza u OpenCode-u",
|
||||||
|
"settings.general.row.shell.title": "Shell terminala",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"Odaberi shell koji se koristi za terminal. Kompatibilni shellovi koriste se i za pozive alata agenta.",
|
||||||
|
"settings.general.row.shell.autoDefault": "Automatski (podrazumijevano)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "samo terminal",
|
||||||
"settings.general.row.appearance.title": "Izgled",
|
"settings.general.row.appearance.title": "Izgled",
|
||||||
"settings.general.row.appearance.description": "Prilagodi kako OpenCode izgleda na tvom uređaju",
|
"settings.general.row.appearance.description": "Prilagodi kako OpenCode izgleda na tvom uređaju",
|
||||||
"settings.general.row.colorScheme.title": "Šema boja",
|
"settings.general.row.colorScheme.title": "Šema boja",
|
||||||
@@ -646,14 +771,30 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "Prilagodi temu OpenCode-a.",
|
"settings.general.row.theme.description": "Prilagodi temu OpenCode-a.",
|
||||||
"settings.general.row.font.title": "Font za kod",
|
"settings.general.row.font.title": "Font za kod",
|
||||||
"settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda",
|
"settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "Font terminala",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "Prilagodite font koji se koristi u terminalu",
|
||||||
"settings.general.row.uiFont.title": "UI font",
|
"settings.general.row.uiFont.title": "UI font",
|
||||||
"settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu",
|
"settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu",
|
||||||
"settings.general.row.followup.title": "Ponašanje nadovezivanja",
|
"settings.general.row.followup.title": "Ponašanje nadovezivanja",
|
||||||
"settings.general.row.followup.description": "Odaberi da li upiti nadovezivanja usmjeravaju odmah ili čekaju u redu",
|
"settings.general.row.followup.description": "Odaberi da li upiti nadovezivanja usmjeravaju odmah ili čekaju u redu",
|
||||||
"settings.general.row.followup.option.queue": "Red čekanja",
|
"settings.general.row.followup.option.queue": "Red čekanja",
|
||||||
"settings.general.row.followup.option.steer": "Usmjeri",
|
"settings.general.row.followup.option.steer": "Usmjeri",
|
||||||
|
"settings.general.row.showFileTree.title": "Stablo datoteka",
|
||||||
|
"settings.general.row.showFileTree.description": "Prikaži stablo datoteka u sesijama",
|
||||||
|
"settings.general.row.showNavigation.title": "Kontrole navigacije",
|
||||||
|
"settings.general.row.showNavigation.description":
|
||||||
|
"Prikaži dugmad za nazad i naprijed u naslovnoj traci desktop aplikacije",
|
||||||
|
"settings.general.row.showSearch.title": "Paleta komandi",
|
||||||
|
"settings.general.row.showSearch.description": "Prikaži dugme za pretragu i paletu komandi u naslovnoj traci",
|
||||||
|
"settings.general.row.showTerminal.title": "Terminal",
|
||||||
|
"settings.general.row.showTerminal.description": "Prikaži dugme terminala u naslovnoj traci desktop aplikacije",
|
||||||
|
"settings.general.row.showStatus.title": "Status servera",
|
||||||
|
"settings.general.row.showStatus.description": "Prikaži dugme statusa servera u naslovnoj traci",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Donja navigacija",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"Postavi naslovnu traku i kartice sesije na dno ekrana na mobilnim uređajima",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Prilagođeni agenti",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Prikaži izbor agenta u uređivaču poruke",
|
||||||
"settings.general.row.reasoningSummaries.title": "Prikaži sažetke rasuđivanja",
|
"settings.general.row.reasoningSummaries.title": "Prikaži sažetke rasuđivanja",
|
||||||
"settings.general.row.reasoningSummaries.description": "Prikaži sažetke rasuđivanja modela na vremenskoj traci",
|
"settings.general.row.reasoningSummaries.description": "Prikaži sažetke rasuđivanja modela na vremenskoj traci",
|
||||||
|
|
||||||
@@ -663,6 +804,12 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje",
|
"settings.general.row.editToolPartsExpanded.title": "Proširi dijelove alata za uređivanje",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci",
|
"Prikaži dijelove alata za uređivanje, pisanje i patch podrazumijevano proširene na vremenskoj traci",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Novi raspored i dizajn",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Omogući redizajnirani raspored, početnu stranicu, uređivač poruke i interfejs sesije",
|
||||||
|
"settings.general.row.pinchZoom.title": "Zumiranje štipanjem",
|
||||||
|
"settings.general.row.pinchZoom.description":
|
||||||
|
"Dozvoli zumiranje gestom štipanja na dodirnoj ploči i pomoću Ctrl-pomjeranja",
|
||||||
"settings.general.row.wayland.title": "Koristi nativni Wayland",
|
"settings.general.row.wayland.title": "Koristi nativni Wayland",
|
||||||
"settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
|
"settings.general.row.wayland.description": "Onemogući X11 fallback na Waylandu. Zahtijeva restart.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
@@ -677,6 +824,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna",
|
"settings.updates.row.check.description": "Ručno provjeri ažuriranja i instaliraj ako su dostupna",
|
||||||
"settings.updates.action.checkNow": "Provjeri sada",
|
"settings.updates.action.checkNow": "Provjeri sada",
|
||||||
"settings.updates.action.checking": "Provjera...",
|
"settings.updates.action.checking": "Provjera...",
|
||||||
|
"settings.updates.action.downloading": "Preuzimanje...",
|
||||||
|
"settings.updates.action.installing": "Instaliranje...",
|
||||||
"settings.updates.toast.latest.title": "Sve je ažurno",
|
"settings.updates.toast.latest.title": "Sve je ažurno",
|
||||||
"settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.",
|
"settings.updates.toast.latest.description": "Koristiš najnoviju verziju OpenCode-a.",
|
||||||
"sound.option.none": "Nijedan",
|
"sound.option.none": "Nijedan",
|
||||||
|
|||||||
+149
-2
@@ -23,6 +23,9 @@ export const dict = {
|
|||||||
|
|
||||||
"command.sidebar.toggle": "Skift sidebjælke",
|
"command.sidebar.toggle": "Skift sidebjælke",
|
||||||
"command.project.open": "Åbn projekt",
|
"command.project.open": "Åbn projekt",
|
||||||
|
"command.project.previous": "Forrige projekt",
|
||||||
|
"command.project.next": "Næste projekt",
|
||||||
|
"command.project.index": "Skift til projekt {{index}}",
|
||||||
"command.provider.connect": "Tilslut udbyder",
|
"command.provider.connect": "Tilslut udbyder",
|
||||||
"command.server.switch": "Skift server",
|
"command.server.switch": "Skift server",
|
||||||
"command.settings.open": "Åbn indstillinger",
|
"command.settings.open": "Åbn indstillinger",
|
||||||
@@ -45,6 +48,7 @@ export const dict = {
|
|||||||
"command.session.new": "Ny session",
|
"command.session.new": "Ny session",
|
||||||
"command.file.open": "Åbn fil",
|
"command.file.open": "Åbn fil",
|
||||||
"command.tab.close": "Luk fane",
|
"command.tab.close": "Luk fane",
|
||||||
|
"command.tab.reopenClosed": "Åbn lukket fane igen",
|
||||||
"command.context.addSelection": "Tilføj markering til kontekst",
|
"command.context.addSelection": "Tilføj markering til kontekst",
|
||||||
"command.context.addSelection.description": "Tilføj markerede linjer fra den aktuelle fil",
|
"command.context.addSelection.description": "Tilføj markerede linjer fra den aktuelle fil",
|
||||||
"command.input.focus": "Fokuser inputfelt",
|
"command.input.focus": "Fokuser inputfelt",
|
||||||
@@ -208,6 +212,11 @@ export const dict = {
|
|||||||
"model.input.video": "video",
|
"model.input.video": "video",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "Tillader: {{inputs}}",
|
"model.tooltip.allows": "Tillader: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Kontekst",
|
||||||
|
"model.tooltip.inputs": "Input",
|
||||||
|
"model.tooltip.model": "Model",
|
||||||
|
"model.tooltip.provider": "Udbyder",
|
||||||
|
"model.tooltip.reasoning": "Tænkning",
|
||||||
"model.tooltip.reasoning.allowed": "Tillader tænkning",
|
"model.tooltip.reasoning.allowed": "Tillader tænkning",
|
||||||
"model.tooltip.reasoning.none": "Ingen tænkning",
|
"model.tooltip.reasoning.none": "Ingen tænkning",
|
||||||
"model.tooltip.context": "Kontekstgrænse {{limit}}",
|
"model.tooltip.context": "Kontekstgrænse {{limit}}",
|
||||||
@@ -217,6 +226,7 @@ export const dict = {
|
|||||||
"common.loading": "Indlæser",
|
"common.loading": "Indlæser",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "Annuller",
|
"common.cancel": "Annuller",
|
||||||
|
"common.clear": "Ryd",
|
||||||
"common.connect": "Forbind",
|
"common.connect": "Forbind",
|
||||||
"common.disconnect": "Frakobl",
|
"common.disconnect": "Frakobl",
|
||||||
"common.continue": "Indsend",
|
"common.continue": "Indsend",
|
||||||
@@ -234,6 +244,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.normal": "Prompt",
|
"prompt.mode.normal": "Prompt",
|
||||||
"prompt.mode.shell.exit": "esc for at afslutte",
|
"prompt.mode.shell.exit": "esc for at afslutte",
|
||||||
|
"session.child.promptDisabled": "Der kan ikke sendes prompts til underagentsessioner.",
|
||||||
|
"session.child.backToParent": "Tilbage til hovedsessionen.",
|
||||||
|
|
||||||
"prompt.example.1": "Ret en TODO i koden",
|
"prompt.example.1": "Ret en TODO i koden",
|
||||||
"prompt.example.2": "Hvad er teknologistakken for dette projekt?",
|
"prompt.example.2": "Hvad er teknologistakken for dette projekt?",
|
||||||
@@ -340,6 +352,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "Slet",
|
"dialog.server.menu.delete": "Slet",
|
||||||
"dialog.server.current": "Nuværende server",
|
"dialog.server.current": "Nuværende server",
|
||||||
"dialog.server.status.default": "Standard",
|
"dialog.server.status.default": "Standard",
|
||||||
|
"wsl.server.add": "Tilføj WSL-server",
|
||||||
|
"wsl.server.addShort": "Tilføj WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "WSL-server",
|
||||||
|
"wsl.server.retryStart": "Prøv at starte igen",
|
||||||
|
"wsl.server.updating": "Opdaterer...",
|
||||||
|
"wsl.onboarding.step.distro": "Vælg distribution",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "Tjekker WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "Windows skal genstartes for at fuldføre installationen af WSL.",
|
||||||
|
"wsl.onboarding.ready": "WSL er klar.",
|
||||||
|
"wsl.onboarding.required": "WSL er påkrævet for at fortsætte.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Tjekker distributioner...",
|
||||||
|
"wsl.onboarding.installingDistro": "Installerer {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "Tjekker {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "Henter liste over distributioner...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} er klar.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "{{distro}} er ikke installeret endnu.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Åbn {{distro}} én gang for at fuldføre opsætningen.",
|
||||||
|
"wsl.onboarding.finishingDistro": "Fuldfører opsætningen af {{distro}}.",
|
||||||
|
"wsl.onboarding.pickDistro": "Vælg en distribution, eller installer en nedenfor.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "Tjekker OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "Tjekker OpenCode i {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "Opdaterer OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "Opdaterer OpenCode i {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "Opdater OpenCode i {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "Opdater OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode er klar i {{distro}}.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode er klar.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "Installer OpenCode i {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "Installer OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Vælg først en distribution.",
|
||||||
|
"wsl.onboarding.loadFailed": "Kunne ikke indlæse WSL-status.",
|
||||||
|
"wsl.onboarding.loading": "Indlæser...",
|
||||||
|
"wsl.onboarding.installedDistros": "Installerede distributioner",
|
||||||
|
"wsl.onboarding.checkAgain": "Tjek igen",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Klar",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Tjekker...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode er ikke installeret",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "Mangler bash, curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "Ikke understøttet · Brug WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "Har du brug for en anden distribution?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "Installer en Linux-distribution fra WSL-kataloget",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL er ikke installeret",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"WSL (Windows Subsystem for Linux) er påkrævet, før OpenCode kan tilføje en WSL-server",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL er ikke tilgængelig",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode kunne ikke bekræfte WSL på denne maskine.",
|
||||||
|
"wsl.onboarding.installWsl": "Installer WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"Genstart Windows for at fuldføre installationen af WSL, og åbn derefter OpenCode igen.",
|
||||||
|
"wsl.onboarding.next": "Næste",
|
||||||
|
"wsl.onboarding.refresh": "Opdater",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Alle installerede distributioner er allerede tilføjet.",
|
||||||
|
"wsl.onboarding.noDistros": "Ingen distributioner fundet endnu.",
|
||||||
|
"wsl.onboarding.install": "Installer",
|
||||||
|
"wsl.onboarding.installing": "Installerer...",
|
||||||
|
"wsl.onboarding.installDistro": "Installer distribution",
|
||||||
|
"wsl.onboarding.searchDistros": "Søg efter distributioner",
|
||||||
|
"wsl.onboarding.wsl2Required": "WSL 2 er påkrævet.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Denne distribution kræver bash og curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "Åbn terminal",
|
||||||
|
"wsl.onboarding.path": "Sti: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "ikke fundet",
|
||||||
|
"wsl.onboarding.version": "Version: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "ukendt",
|
||||||
|
"wsl.onboarding.desktopVersion": "desktop {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "Den installerede version matcher ikke desktopappens version.",
|
||||||
|
"wsl.onboarding.adding": "Tilføjer...",
|
||||||
|
|
||||||
"dialog.project.edit.title": "Rediger projekt",
|
"dialog.project.edit.title": "Rediger projekt",
|
||||||
"dialog.project.edit.name": "Navn",
|
"dialog.project.edit.name": "Navn",
|
||||||
@@ -403,6 +484,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
|
|
||||||
@@ -450,10 +532,12 @@ export const dict = {
|
|||||||
|
|
||||||
"error.page.title": "Noget gik galt",
|
"error.page.title": "Noget gik galt",
|
||||||
"error.page.description": "Der opstod en fejl under indlæsning af applikationen.",
|
"error.page.description": "Der opstod en fejl under indlæsning af applikationen.",
|
||||||
|
"error.page.description.localServerStartup": "Der opstod en fejl under start af den lokale server.",
|
||||||
"error.page.details.label": "Fejldetaljer",
|
"error.page.details.label": "Fejldetaljer",
|
||||||
"error.page.action.restart": "Genstart",
|
"error.page.action.restart": "Genstart",
|
||||||
"error.page.action.report": "Rapportér fejl",
|
"error.page.action.report": "Rapportér fejl",
|
||||||
"error.page.action.reported": "Fejl rapporteret",
|
"error.page.action.reported": "Fejl rapporteret",
|
||||||
|
"error.page.action.exportLogs": "Eksportér logfiler",
|
||||||
"error.page.action.checking": "Tjekker...",
|
"error.page.action.checking": "Tjekker...",
|
||||||
"error.page.action.checkUpdates": "Tjek for opdateringer",
|
"error.page.action.checkUpdates": "Tjek for opdateringer",
|
||||||
"error.page.action.updateTo": "Opdater til {{version}}",
|
"error.page.action.updateTo": "Opdater til {{version}}",
|
||||||
@@ -501,11 +585,31 @@ export const dict = {
|
|||||||
"home.recentProjects": "Seneste projekter",
|
"home.recentProjects": "Seneste projekter",
|
||||||
"home.empty.title": "Ingen seneste projekter",
|
"home.empty.title": "Ingen seneste projekter",
|
||||||
"home.empty.description": "Kom i gang ved at åbne et lokalt projekt",
|
"home.empty.description": "Kom i gang ved at åbne et lokalt projekt",
|
||||||
|
"home.title": "Hjem",
|
||||||
|
"home.projects": "Projekter",
|
||||||
|
"home.project.add": "Tilføj projekt",
|
||||||
|
"home.recentlyClosed": "Lukket for nylig",
|
||||||
|
"home.server.collapse": "Skjul serverprojekter",
|
||||||
|
"home.server.expand": "Udvid serverprojekter",
|
||||||
|
"home.sessions.search.placeholder": "Søg i sessioner",
|
||||||
|
"home.sessions.search.placeholder.scoped": "Søg i sessioner i {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "Sessioner",
|
||||||
|
"home.sessions.search.noResults": "Ingen sessioner fundet for {{query}}",
|
||||||
|
"home.sessions.empty": "Intet her endnu",
|
||||||
|
"home.sessions.empty.description": "Opret en session for at komme i gang",
|
||||||
|
"home.sessions.group.today": "I dag",
|
||||||
|
"home.sessions.group.yesterday": "I går",
|
||||||
|
"home.sessions.group.older": "Ældre",
|
||||||
|
|
||||||
"session.tab.session": "Session",
|
"session.tab.session": "Session",
|
||||||
"session.tab.review": "Gennemgang",
|
"session.tab.review": "Gennemgang",
|
||||||
"session.tab.context": "Kontekst",
|
"session.tab.context": "Kontekst",
|
||||||
|
"session.tab.unknown": "Ukendt session",
|
||||||
"session.panel.reviewAndFiles": "Gennemgang og filer",
|
"session.panel.reviewAndFiles": "Gennemgang og filer",
|
||||||
|
"session.error.notFound": "Denne session kan ikke findes",
|
||||||
|
"session.error.notFound.description": "Denne fane henviser til en session, der ikke længere findes på denne server.",
|
||||||
|
"session.error.notFound.closeTab": "Luk fane",
|
||||||
|
"session.error.serverConnection": "Kan ikke oprette forbindelse til denne server",
|
||||||
"session.review.filesChanged": "{{count}} Filer ændret",
|
"session.review.filesChanged": "{{count}} Filer ændret",
|
||||||
"session.review.change.one": "Ændring",
|
"session.review.change.one": "Ændring",
|
||||||
"session.review.change.other": "Ændringer",
|
"session.review.change.other": "Ændringer",
|
||||||
@@ -515,6 +619,8 @@ export const dict = {
|
|||||||
"session.review.noSnapshot":
|
"session.review.noSnapshot":
|
||||||
"Snapshot-sporing er deaktiveret i konfigurationen, så sessionsændringer er ikke tilgængelige",
|
"Snapshot-sporing er deaktiveret i konfigurationen, så sessionsændringer er ikke tilgængelige",
|
||||||
"session.review.noChanges": "Ingen ændringer",
|
"session.review.noChanges": "Ingen ændringer",
|
||||||
|
"session.review.noUncommittedChanges": "Ingen ændringer uden commit endnu",
|
||||||
|
"session.review.noBranchChanges": "Ingen grenændringer endnu",
|
||||||
"session.files.selectToOpen": "Vælg en fil at åbne",
|
"session.files.selectToOpen": "Vælg en fil at åbne",
|
||||||
"session.files.all": "Alle filer",
|
"session.files.all": "Alle filer",
|
||||||
"session.files.empty": "Ingen filer",
|
"session.files.empty": "Ingen filer",
|
||||||
@@ -529,6 +635,10 @@ export const dict = {
|
|||||||
"session.todo.title": "Opgaver",
|
"session.todo.title": "Opgaver",
|
||||||
"session.todo.collapse": "Skjul",
|
"session.todo.collapse": "Skjul",
|
||||||
"session.todo.expand": "Udvid",
|
"session.todo.expand": "Udvid",
|
||||||
|
"session.question.minimize": "Minimer spørgsmål",
|
||||||
|
"session.question.restore": "Gendan spørgsmål",
|
||||||
|
"session.question.pending.one": "{{count}} afventende spørgsmål",
|
||||||
|
"session.question.pending.other": "{{count}} afventende spørgsmål",
|
||||||
"session.followupDock.summary.one": "{{count}} besked i kø",
|
"session.followupDock.summary.one": "{{count}} besked i kø",
|
||||||
"session.followupDock.summary.other": "{{count}} beskeder i kø",
|
"session.followupDock.summary.other": "{{count}} beskeder i kø",
|
||||||
"session.followupDock.sendNow": "Send nu",
|
"session.followupDock.sendNow": "Send nu",
|
||||||
@@ -542,9 +652,16 @@ export const dict = {
|
|||||||
"session.revertDock.restore": "Gendan besked",
|
"session.revertDock.restore": "Gendan besked",
|
||||||
|
|
||||||
"session.new.title": "Byg hvad som helst",
|
"session.new.title": "Byg hvad som helst",
|
||||||
|
"session.new.project.new": "Nyt projekt",
|
||||||
|
"session.new.project.search": "Søg efter projekter",
|
||||||
|
"session.new.project.add": "Tilføj projekt",
|
||||||
"session.new.worktree.main": "Hovedgren",
|
"session.new.worktree.main": "Hovedgren",
|
||||||
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Hovedgren ({{branch}})",
|
||||||
"session.new.worktree.create": "Opret nyt worktree",
|
"session.new.worktree.create": "Opret nyt worktree",
|
||||||
|
"session.new.workspace.runIn": "Kør session i",
|
||||||
|
"session.new.workspace.triggerLocal": "Lokal",
|
||||||
|
"session.new.workspace.local": "Lokalt repository",
|
||||||
|
"session.new.workspace.existing": "Arbejdsområde…",
|
||||||
"session.new.lastModified": "Sidst ændret",
|
"session.new.lastModified": "Sidst ændret",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Søg {{project}}",
|
"session.header.search.placeholder": "Søg {{project}}",
|
||||||
@@ -614,6 +731,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "Seneste sessioner",
|
"sidebar.project.recentSessions": "Seneste sessioner",
|
||||||
"sidebar.project.viewAllSessions": "Vis alle sessioner",
|
"sidebar.project.viewAllSessions": "Vis alle sessioner",
|
||||||
"sidebar.project.clearNotifications": "Ryd notifikationer",
|
"sidebar.project.clearNotifications": "Ryd notifikationer",
|
||||||
|
"sidebar.empty.title": "Ingen åbne projekter",
|
||||||
|
"sidebar.empty.description": "Åbn et projekt for at komme i gang",
|
||||||
|
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
"settings.section.desktop": "Desktop",
|
"settings.section.desktop": "Desktop",
|
||||||
@@ -625,6 +744,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.description": "Kør OpenCode-serveren inde i WSL på Windows.",
|
"settings.desktop.wsl.description": "Kør OpenCode-serveren inde i WSL på Windows.",
|
||||||
|
|
||||||
"settings.general.section.appearance": "Udseende",
|
"settings.general.section.appearance": "Udseende",
|
||||||
|
"settings.general.section.advanced": "Avanceret",
|
||||||
"settings.general.section.notifications": "Systemmeddelelser",
|
"settings.general.section.notifications": "Systemmeddelelser",
|
||||||
"settings.general.section.updates": "Opdateringer",
|
"settings.general.section.updates": "Opdateringer",
|
||||||
"settings.general.section.sounds": "Lydeffekter",
|
"settings.general.section.sounds": "Lydeffekter",
|
||||||
@@ -633,6 +753,11 @@ export const dict = {
|
|||||||
|
|
||||||
"settings.general.row.language.title": "Sprog",
|
"settings.general.row.language.title": "Sprog",
|
||||||
"settings.general.row.language.description": "Ændr visningssproget for OpenCode",
|
"settings.general.row.language.description": "Ændr visningssproget for OpenCode",
|
||||||
|
"settings.general.row.shell.title": "Terminalshell",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"Vælg den shell, der bruges i din terminal. Kompatible shells bruges også til agentens værktøjskald.",
|
||||||
|
"settings.general.row.shell.autoDefault": "Automatisk (standard)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "kun terminal",
|
||||||
"settings.general.row.appearance.title": "Udseende",
|
"settings.general.row.appearance.title": "Udseende",
|
||||||
"settings.general.row.appearance.description": "Tilpas hvordan OpenCode ser ud på din enhed",
|
"settings.general.row.appearance.description": "Tilpas hvordan OpenCode ser ud på din enhed",
|
||||||
"settings.general.row.colorScheme.title": "Farveskema",
|
"settings.general.row.colorScheme.title": "Farveskema",
|
||||||
@@ -641,14 +766,29 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.",
|
"settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.",
|
||||||
"settings.general.row.font.title": "Kode-skrifttype",
|
"settings.general.row.font.title": "Kode-skrifttype",
|
||||||
"settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke",
|
"settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "Terminalskrifttype",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "Tilpas den skrifttype, der bruges i terminalen",
|
||||||
"settings.general.row.uiFont.title": "UI-skrifttype",
|
"settings.general.row.uiFont.title": "UI-skrifttype",
|
||||||
"settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen",
|
"settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen",
|
||||||
"settings.general.row.followup.title": "Opfølgningsadfærd",
|
"settings.general.row.followup.title": "Opfølgningsadfærd",
|
||||||
"settings.general.row.followup.description": "Vælg om opfølgende forespørgsler skal styre straks eller vente i kø",
|
"settings.general.row.followup.description": "Vælg om opfølgende forespørgsler skal styre straks eller vente i kø",
|
||||||
"settings.general.row.followup.option.queue": "Kø",
|
"settings.general.row.followup.option.queue": "Kø",
|
||||||
"settings.general.row.followup.option.steer": "Styr",
|
"settings.general.row.followup.option.steer": "Styr",
|
||||||
|
"settings.general.row.showFileTree.title": "Filtræ",
|
||||||
|
"settings.general.row.showFileTree.description": "Vis filtræspanelet i sessioner",
|
||||||
|
"settings.general.row.showNavigation.title": "Navigationsknapper",
|
||||||
|
"settings.general.row.showNavigation.description": "Vis tilbage- og frem-knapperne i desktopappens titellinje",
|
||||||
|
"settings.general.row.showSearch.title": "Kommandopalette",
|
||||||
|
"settings.general.row.showSearch.description": "Vis knappen til søgning og kommandopaletten i titellinjen",
|
||||||
|
"settings.general.row.showTerminal.title": "Terminal",
|
||||||
|
"settings.general.row.showTerminal.description": "Vis terminalknappen i desktopappens titellinje",
|
||||||
|
"settings.general.row.showStatus.title": "Serverstatus",
|
||||||
|
"settings.general.row.showStatus.description": "Vis serverstatusknappen i titellinjen",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Navigation nederst",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"Placer titellinjen og sessionsfanerne nederst på skærmen på mobilenheder",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Brugerdefinerede agenter",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Vis agentvælgeren i promptfeltet",
|
||||||
"settings.general.row.reasoningSummaries.title": "Vis tænkeoversigter",
|
"settings.general.row.reasoningSummaries.title": "Vis tænkeoversigter",
|
||||||
"settings.general.row.reasoningSummaries.description": "Vis model tænkeoversigter i tidslinjen",
|
"settings.general.row.reasoningSummaries.description": "Vis model tænkeoversigter i tidslinjen",
|
||||||
|
|
||||||
@@ -657,6 +797,11 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele",
|
"settings.general.row.editToolPartsExpanded.title": "Udvid edit-værktøjsdele",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen",
|
"Vis edit-, write- og patch-værktøjsdele udvidet som standard i tidslinjen",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Nyt layout og design",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Aktivér det nydesignede layout, startsiden, promptfeltet og sessionsbrugerfladen",
|
||||||
|
"settings.general.row.pinchZoom.title": "Knib for at zoome",
|
||||||
|
"settings.general.row.pinchZoom.description": "Tillad knibebevægelser på pegefeltet og Ctrl-rulning for at zoome",
|
||||||
"settings.general.row.wayland.title": "Brug native Wayland",
|
"settings.general.row.wayland.title": "Brug native Wayland",
|
||||||
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
|
"settings.general.row.wayland.description": "Deaktiver X11-fallback på Wayland. Kræver genstart.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
@@ -671,6 +816,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "Tjek manuelt for opdateringer og installer, hvis tilgængelig",
|
"settings.updates.row.check.description": "Tjek manuelt for opdateringer og installer, hvis tilgængelig",
|
||||||
"settings.updates.action.checkNow": "Tjek nu",
|
"settings.updates.action.checkNow": "Tjek nu",
|
||||||
"settings.updates.action.checking": "Tjekker...",
|
"settings.updates.action.checking": "Tjekker...",
|
||||||
|
"settings.updates.action.downloading": "Downloader...",
|
||||||
|
"settings.updates.action.installing": "Installerer...",
|
||||||
"settings.updates.toast.latest.title": "Du er opdateret",
|
"settings.updates.toast.latest.title": "Du er opdateret",
|
||||||
"settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.",
|
"settings.updates.toast.latest.description": "Du kører den nyeste version af OpenCode.",
|
||||||
|
|
||||||
|
|||||||
+152
-2
@@ -25,6 +25,9 @@ export const dict = {
|
|||||||
"theme.scheme.dark": "Dunkel",
|
"theme.scheme.dark": "Dunkel",
|
||||||
"command.sidebar.toggle": "Seitenleiste umschalten",
|
"command.sidebar.toggle": "Seitenleiste umschalten",
|
||||||
"command.project.open": "Projekt öffnen",
|
"command.project.open": "Projekt öffnen",
|
||||||
|
"command.project.previous": "Vorheriges Projekt",
|
||||||
|
"command.project.next": "Nächstes Projekt",
|
||||||
|
"command.project.index": "Zu Projekt {{index}} wechseln",
|
||||||
"command.provider.connect": "Anbieter verbinden",
|
"command.provider.connect": "Anbieter verbinden",
|
||||||
"command.server.switch": "Server wechseln",
|
"command.server.switch": "Server wechseln",
|
||||||
"command.settings.open": "Einstellungen öffnen",
|
"command.settings.open": "Einstellungen öffnen",
|
||||||
@@ -43,6 +46,7 @@ export const dict = {
|
|||||||
"command.session.new": "Neue Sitzung",
|
"command.session.new": "Neue Sitzung",
|
||||||
"command.file.open": "Datei öffnen",
|
"command.file.open": "Datei öffnen",
|
||||||
"command.tab.close": "Tab schließen",
|
"command.tab.close": "Tab schließen",
|
||||||
|
"command.tab.reopenClosed": "Geschlossenen Tab wieder öffnen",
|
||||||
"command.context.addSelection": "Auswahl zum Kontext hinzufügen",
|
"command.context.addSelection": "Auswahl zum Kontext hinzufügen",
|
||||||
"command.context.addSelection.description": "Ausgewählte Zeilen aus der aktuellen Datei hinzufügen",
|
"command.context.addSelection.description": "Ausgewählte Zeilen aus der aktuellen Datei hinzufügen",
|
||||||
"command.input.focus": "Eingabefeld fokussieren",
|
"command.input.focus": "Eingabefeld fokussieren",
|
||||||
@@ -198,6 +202,11 @@ export const dict = {
|
|||||||
"model.input.video": "Video",
|
"model.input.video": "Video",
|
||||||
"model.input.pdf": "PDF",
|
"model.input.pdf": "PDF",
|
||||||
"model.tooltip.allows": "Erlaubt: {{inputs}}",
|
"model.tooltip.allows": "Erlaubt: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Kontext",
|
||||||
|
"model.tooltip.inputs": "Eingaben",
|
||||||
|
"model.tooltip.model": "Modell",
|
||||||
|
"model.tooltip.provider": "Anbieter",
|
||||||
|
"model.tooltip.reasoning": "Reasoning",
|
||||||
"model.tooltip.reasoning.allowed": "Erlaubt Reasoning",
|
"model.tooltip.reasoning.allowed": "Erlaubt Reasoning",
|
||||||
"model.tooltip.reasoning.none": "Kein Reasoning",
|
"model.tooltip.reasoning.none": "Kein Reasoning",
|
||||||
"model.tooltip.context": "Kontextlimit {{limit}}",
|
"model.tooltip.context": "Kontextlimit {{limit}}",
|
||||||
@@ -207,6 +216,7 @@ export const dict = {
|
|||||||
"common.loading": "Laden",
|
"common.loading": "Laden",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "Abbrechen",
|
"common.cancel": "Abbrechen",
|
||||||
|
"common.clear": "Leeren",
|
||||||
"common.connect": "Verbinden",
|
"common.connect": "Verbinden",
|
||||||
"common.disconnect": "Trennen",
|
"common.disconnect": "Trennen",
|
||||||
"common.continue": "Absenden",
|
"common.continue": "Absenden",
|
||||||
@@ -223,6 +233,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.normal": "Prompt",
|
"prompt.mode.normal": "Prompt",
|
||||||
"prompt.mode.shell.exit": "esc zum Verlassen",
|
"prompt.mode.shell.exit": "esc zum Verlassen",
|
||||||
|
"session.child.promptDisabled": "Unteragenten-Sitzungen können keine Prompts erhalten.",
|
||||||
|
"session.child.backToParent": "Zurück zur Hauptsitzung.",
|
||||||
"prompt.example.1": "Ein TODO in der Codebasis beheben",
|
"prompt.example.1": "Ein TODO in der Codebasis beheben",
|
||||||
"prompt.example.2": "Was ist der Tech-Stack dieses Projekts?",
|
"prompt.example.2": "Was ist der Tech-Stack dieses Projekts?",
|
||||||
"prompt.example.3": "Fehlerhafte Tests beheben",
|
"prompt.example.3": "Fehlerhafte Tests beheben",
|
||||||
@@ -320,6 +332,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "Löschen",
|
"dialog.server.menu.delete": "Löschen",
|
||||||
"dialog.server.current": "Aktueller Server",
|
"dialog.server.current": "Aktueller Server",
|
||||||
"dialog.server.status.default": "Standard",
|
"dialog.server.status.default": "Standard",
|
||||||
|
"wsl.server.add": "WSL-Server hinzufügen",
|
||||||
|
"wsl.server.addShort": "WSL hinzufügen",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "WSL-Server",
|
||||||
|
"wsl.server.retryStart": "Start erneut versuchen",
|
||||||
|
"wsl.server.updating": "Wird aktualisiert...",
|
||||||
|
"wsl.onboarding.step.distro": "Distribution auswählen",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "WSL wird geprüft...",
|
||||||
|
"wsl.onboarding.restartRequired": "Windows muss neu gestartet werden, um die WSL-Installation abzuschließen.",
|
||||||
|
"wsl.onboarding.ready": "WSL ist bereit.",
|
||||||
|
"wsl.onboarding.required": "WSL ist erforderlich, um fortzufahren.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Distributionen werden geprüft...",
|
||||||
|
"wsl.onboarding.installingDistro": "{{distro}} wird installiert...",
|
||||||
|
"wsl.onboarding.checkingDistro": "{{distro}} wird geprüft...",
|
||||||
|
"wsl.onboarding.listingDistros": "Distributionen werden aufgelistet...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} ist bereit.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "{{distro}} ist noch nicht installiert.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Öffnen Sie {{distro}} einmal, um die Einrichtung abzuschließen.",
|
||||||
|
"wsl.onboarding.finishingDistro": "Einrichtung von {{distro}} wird abgeschlossen.",
|
||||||
|
"wsl.onboarding.pickDistro": "Wählen Sie eine Distribution aus oder installieren Sie unten eine.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "OpenCode wird geprüft...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "OpenCode in {{distro}} wird geprüft...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "OpenCode wird aktualisiert...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "OpenCode in {{distro}} wird aktualisiert...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "Aktualisieren Sie OpenCode in {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "OpenCode aktualisieren",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode ist in {{distro}} bereit.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode ist bereit.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "Installieren Sie OpenCode in {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "OpenCode installieren",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Wählen Sie zuerst eine Distribution aus.",
|
||||||
|
"wsl.onboarding.loadFailed": "WSL-Status konnte nicht geladen werden.",
|
||||||
|
"wsl.onboarding.loading": "Wird geladen...",
|
||||||
|
"wsl.onboarding.installedDistros": "Installierte Distributionen",
|
||||||
|
"wsl.onboarding.checkAgain": "Erneut prüfen",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Bereit",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Wird geprüft...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nicht installiert",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "Fehlend: bash, curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "Nicht unterstützt · WSL 2 verwenden",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "Andere Distribution benötigt?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "Installieren Sie eine Linux-Distribution aus dem WSL-Katalog",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL nicht installiert",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"WSL (Windows Subsystem for Linux) ist erforderlich, bevor OpenCode einen WSL-Server hinzufügen kann",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL nicht verfügbar",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode konnte WSL auf diesem Computer nicht überprüfen.",
|
||||||
|
"wsl.onboarding.installWsl": "WSL installieren",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"Starten Sie Windows neu, um die WSL-Installation abzuschließen, und öffnen Sie dann OpenCode erneut.",
|
||||||
|
"wsl.onboarding.next": "Weiter",
|
||||||
|
"wsl.onboarding.refresh": "Aktualisieren",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Alle installierten Distributionen wurden bereits hinzugefügt.",
|
||||||
|
"wsl.onboarding.noDistros": "Noch keine Distributionen erkannt.",
|
||||||
|
"wsl.onboarding.install": "Installieren",
|
||||||
|
"wsl.onboarding.installing": "Wird installiert...",
|
||||||
|
"wsl.onboarding.installDistro": "Distribution installieren",
|
||||||
|
"wsl.onboarding.searchDistros": "Distributionen durchsuchen",
|
||||||
|
"wsl.onboarding.wsl2Required": "WSL 2 ist erforderlich.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Diese Distribution benötigt bash und curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "Terminal öffnen",
|
||||||
|
"wsl.onboarding.path": "Pfad: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "nicht gefunden",
|
||||||
|
"wsl.onboarding.version": "Version: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "unbekannt",
|
||||||
|
"wsl.onboarding.desktopVersion": "Desktop {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "Die installierte Version entspricht nicht der Version der Desktop-App.",
|
||||||
|
"wsl.onboarding.adding": "Wird hinzugefügt...",
|
||||||
"dialog.project.edit.title": "Projekt bearbeiten",
|
"dialog.project.edit.title": "Projekt bearbeiten",
|
||||||
"dialog.project.edit.name": "Name",
|
"dialog.project.edit.name": "Name",
|
||||||
"dialog.project.edit.icon": "Icon",
|
"dialog.project.edit.icon": "Icon",
|
||||||
@@ -378,6 +459,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
"toast.language.title": "Sprache",
|
"toast.language.title": "Sprache",
|
||||||
@@ -414,10 +496,12 @@ export const dict = {
|
|||||||
"toast.update.action.notYet": "Noch nicht",
|
"toast.update.action.notYet": "Noch nicht",
|
||||||
"error.page.title": "Etwas ist schiefgelaufen",
|
"error.page.title": "Etwas ist schiefgelaufen",
|
||||||
"error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.",
|
"error.page.description": "Beim Laden der Anwendung ist ein Fehler aufgetreten.",
|
||||||
|
"error.page.description.localServerStartup": "Beim Starten des lokalen Servers ist ein Fehler aufgetreten.",
|
||||||
"error.page.details.label": "Fehlerdetails",
|
"error.page.details.label": "Fehlerdetails",
|
||||||
"error.page.action.restart": "Neustart",
|
"error.page.action.restart": "Neustart",
|
||||||
"error.page.action.report": "Fehler melden",
|
"error.page.action.report": "Fehler melden",
|
||||||
"error.page.action.reported": "Fehler gemeldet",
|
"error.page.action.reported": "Fehler gemeldet",
|
||||||
|
"error.page.action.exportLogs": "Protokolle exportieren",
|
||||||
"error.page.action.checking": "Prüfen...",
|
"error.page.action.checking": "Prüfen...",
|
||||||
"error.page.action.checkUpdates": "Nach Updates suchen",
|
"error.page.action.checkUpdates": "Nach Updates suchen",
|
||||||
"error.page.action.updateTo": "Auf {{version}} aktualisieren",
|
"error.page.action.updateTo": "Auf {{version}} aktualisieren",
|
||||||
@@ -461,10 +545,31 @@ export const dict = {
|
|||||||
"home.recentProjects": "Letzte Projekte",
|
"home.recentProjects": "Letzte Projekte",
|
||||||
"home.empty.title": "Keine letzten Projekte",
|
"home.empty.title": "Keine letzten Projekte",
|
||||||
"home.empty.description": "Starten Sie, indem Sie ein lokales Projekt öffnen",
|
"home.empty.description": "Starten Sie, indem Sie ein lokales Projekt öffnen",
|
||||||
|
"home.title": "Startseite",
|
||||||
|
"home.projects": "Projekte",
|
||||||
|
"home.project.add": "Projekt hinzufügen",
|
||||||
|
"home.recentlyClosed": "Kürzlich geschlossen",
|
||||||
|
"home.server.collapse": "Serverprojekte einklappen",
|
||||||
|
"home.server.expand": "Serverprojekte ausklappen",
|
||||||
|
"home.sessions.search.placeholder": "Sitzungen durchsuchen",
|
||||||
|
"home.sessions.search.placeholder.scoped": "Sitzungen in {{scope}} durchsuchen",
|
||||||
|
"home.sessions.search.sessions": "Sitzungen",
|
||||||
|
"home.sessions.search.noResults": "Keine Sitzungen für {{query}} gefunden",
|
||||||
|
"home.sessions.empty": "Hier ist noch nichts",
|
||||||
|
"home.sessions.empty.description": "Erstellen Sie eine Sitzung, um loszulegen",
|
||||||
|
"home.sessions.group.today": "Heute",
|
||||||
|
"home.sessions.group.yesterday": "Gestern",
|
||||||
|
"home.sessions.group.older": "Älter",
|
||||||
"session.tab.session": "Sitzung",
|
"session.tab.session": "Sitzung",
|
||||||
"session.tab.review": "Überprüfung",
|
"session.tab.review": "Überprüfung",
|
||||||
"session.tab.context": "Kontext",
|
"session.tab.context": "Kontext",
|
||||||
|
"session.tab.unknown": "Unbekannte Sitzung",
|
||||||
"session.panel.reviewAndFiles": "Überprüfung und Dateien",
|
"session.panel.reviewAndFiles": "Überprüfung und Dateien",
|
||||||
|
"session.error.notFound": "Diese Sitzung wurde nicht gefunden",
|
||||||
|
"session.error.notFound.description":
|
||||||
|
"Dieser Tab verweist auf eine Sitzung, die auf diesem Server nicht mehr existiert.",
|
||||||
|
"session.error.notFound.closeTab": "Tab schließen",
|
||||||
|
"session.error.serverConnection": "Verbindung zu diesem Server nicht möglich",
|
||||||
"session.review.filesChanged": "{{count}} Dateien geändert",
|
"session.review.filesChanged": "{{count}} Dateien geändert",
|
||||||
"session.review.change.one": "Änderung",
|
"session.review.change.one": "Änderung",
|
||||||
"session.review.change.other": "Änderungen",
|
"session.review.change.other": "Änderungen",
|
||||||
@@ -474,6 +579,8 @@ export const dict = {
|
|||||||
"session.review.noSnapshot":
|
"session.review.noSnapshot":
|
||||||
"Snapshot-Tracking ist in der Konfiguration deaktiviert, daher sind Sitzungsänderungen nicht verfügbar",
|
"Snapshot-Tracking ist in der Konfiguration deaktiviert, daher sind Sitzungsänderungen nicht verfügbar",
|
||||||
"session.review.noChanges": "Keine Änderungen",
|
"session.review.noChanges": "Keine Änderungen",
|
||||||
|
"session.review.noUncommittedChanges": "Noch keine nicht committeten Änderungen",
|
||||||
|
"session.review.noBranchChanges": "Noch keine Branch-Änderungen",
|
||||||
"session.files.selectToOpen": "Datei zum Öffnen auswählen",
|
"session.files.selectToOpen": "Datei zum Öffnen auswählen",
|
||||||
"session.files.all": "Alle Dateien",
|
"session.files.all": "Alle Dateien",
|
||||||
"session.files.empty": "Keine Dateien",
|
"session.files.empty": "Keine Dateien",
|
||||||
@@ -487,6 +594,10 @@ export const dict = {
|
|||||||
"session.todo.title": "Aufgaben",
|
"session.todo.title": "Aufgaben",
|
||||||
"session.todo.collapse": "Einklappen",
|
"session.todo.collapse": "Einklappen",
|
||||||
"session.todo.expand": "Ausklappen",
|
"session.todo.expand": "Ausklappen",
|
||||||
|
"session.question.minimize": "Frage minimieren",
|
||||||
|
"session.question.restore": "Frage wiederherstellen",
|
||||||
|
"session.question.pending.one": "{{count}} ausstehende Frage",
|
||||||
|
"session.question.pending.other": "{{count}} ausstehende Fragen",
|
||||||
"session.followupDock.summary.one": "{{count}} Nachricht in der Warteschlange",
|
"session.followupDock.summary.one": "{{count}} Nachricht in der Warteschlange",
|
||||||
"session.followupDock.summary.other": "{{count}} Nachrichten in der Warteschlange",
|
"session.followupDock.summary.other": "{{count}} Nachrichten in der Warteschlange",
|
||||||
"session.followupDock.sendNow": "Jetzt senden",
|
"session.followupDock.sendNow": "Jetzt senden",
|
||||||
@@ -499,9 +610,16 @@ export const dict = {
|
|||||||
"session.revertDock.expand": "Zurückgesetzte Nachrichten ausklappen",
|
"session.revertDock.expand": "Zurückgesetzte Nachrichten ausklappen",
|
||||||
"session.revertDock.restore": "Nachricht wiederherstellen",
|
"session.revertDock.restore": "Nachricht wiederherstellen",
|
||||||
"session.new.title": "Baue, was du willst",
|
"session.new.title": "Baue, was du willst",
|
||||||
|
"session.new.project.new": "Neues Projekt",
|
||||||
|
"session.new.project.search": "Projekte durchsuchen",
|
||||||
|
"session.new.project.add": "Projekt hinzufügen",
|
||||||
"session.new.worktree.main": "Haupt-Branch",
|
"session.new.worktree.main": "Haupt-Branch",
|
||||||
"session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Haupt-Branch ({{branch}})",
|
||||||
"session.new.worktree.create": "Neuen Worktree erstellen",
|
"session.new.worktree.create": "Neuen Worktree erstellen",
|
||||||
|
"session.new.workspace.runIn": "Sitzung ausführen in",
|
||||||
|
"session.new.workspace.triggerLocal": "Lokal",
|
||||||
|
"session.new.workspace.local": "Lokales Repository",
|
||||||
|
"session.new.workspace.existing": "Arbeitsbereich…",
|
||||||
"session.new.lastModified": "Zuletzt geändert",
|
"session.new.lastModified": "Zuletzt geändert",
|
||||||
"session.header.search.placeholder": "{{project}} durchsuchen",
|
"session.header.search.placeholder": "{{project}} durchsuchen",
|
||||||
"session.header.searchFiles": "Dateien suchen",
|
"session.header.searchFiles": "Dateien suchen",
|
||||||
@@ -566,6 +684,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "Letzte Sitzungen",
|
"sidebar.project.recentSessions": "Letzte Sitzungen",
|
||||||
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
|
"sidebar.project.viewAllSessions": "Alle Sitzungen anzeigen",
|
||||||
"sidebar.project.clearNotifications": "Benachrichtigungen löschen",
|
"sidebar.project.clearNotifications": "Benachrichtigungen löschen",
|
||||||
|
"sidebar.empty.title": "Keine Projekte geöffnet",
|
||||||
|
"sidebar.empty.description": "Öffnen Sie ein Projekt, um loszulegen",
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
"settings.section.desktop": "Desktop",
|
"settings.section.desktop": "Desktop",
|
||||||
"settings.section.server": "Server",
|
"settings.section.server": "Server",
|
||||||
@@ -575,6 +695,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.title": "WSL-Integration",
|
"settings.desktop.wsl.title": "WSL-Integration",
|
||||||
"settings.desktop.wsl.description": "OpenCode-Server innerhalb von WSL unter Windows ausführen.",
|
"settings.desktop.wsl.description": "OpenCode-Server innerhalb von WSL unter Windows ausführen.",
|
||||||
"settings.general.section.appearance": "Erscheinungsbild",
|
"settings.general.section.appearance": "Erscheinungsbild",
|
||||||
|
"settings.general.section.advanced": "Erweitert",
|
||||||
"settings.general.section.notifications": "Systembenachrichtigungen",
|
"settings.general.section.notifications": "Systembenachrichtigungen",
|
||||||
"settings.general.section.updates": "Updates",
|
"settings.general.section.updates": "Updates",
|
||||||
"settings.general.section.sounds": "Soundeffekte",
|
"settings.general.section.sounds": "Soundeffekte",
|
||||||
@@ -582,6 +703,11 @@ export const dict = {
|
|||||||
"settings.general.section.display": "Anzeige",
|
"settings.general.section.display": "Anzeige",
|
||||||
"settings.general.row.language.title": "Sprache",
|
"settings.general.row.language.title": "Sprache",
|
||||||
"settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
|
"settings.general.row.language.description": "Die Anzeigesprache für OpenCode ändern",
|
||||||
|
"settings.general.row.shell.title": "Terminal-Shell",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"Wählen Sie die Shell für Ihr Terminal aus. Kompatible Shells werden auch für Tool-Aufrufe von Agenten verwendet.",
|
||||||
|
"settings.general.row.shell.autoDefault": "Automatisch (Standard)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "nur Terminal",
|
||||||
"settings.general.row.appearance.title": "Erscheinungsbild",
|
"settings.general.row.appearance.title": "Erscheinungsbild",
|
||||||
"settings.general.row.appearance.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht",
|
"settings.general.row.appearance.description": "Anpassen, wie OpenCode auf Ihrem Gerät aussieht",
|
||||||
"settings.general.row.colorScheme.title": "Farbschema",
|
"settings.general.row.colorScheme.title": "Farbschema",
|
||||||
@@ -591,8 +717,8 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
|
"settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
|
||||||
"settings.general.row.font.title": "Code-Schriftart",
|
"settings.general.row.font.title": "Code-Schriftart",
|
||||||
"settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen",
|
"settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "Terminalschriftart",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "Passe die im Terminal verwendete Schriftart an",
|
||||||
"settings.general.row.uiFont.title": "UI-Schriftart",
|
"settings.general.row.uiFont.title": "UI-Schriftart",
|
||||||
"settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen",
|
"settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen",
|
||||||
"settings.general.row.followup.title": "Verhalten bei Folgefragen",
|
"settings.general.row.followup.title": "Verhalten bei Folgefragen",
|
||||||
@@ -600,6 +726,23 @@ export const dict = {
|
|||||||
"Wählen Sie, ob Folgefragen sofort steuern oder in einer Warteschlange warten",
|
"Wählen Sie, ob Folgefragen sofort steuern oder in einer Warteschlange warten",
|
||||||
"settings.general.row.followup.option.queue": "Warteschlange",
|
"settings.general.row.followup.option.queue": "Warteschlange",
|
||||||
"settings.general.row.followup.option.steer": "Steuern",
|
"settings.general.row.followup.option.steer": "Steuern",
|
||||||
|
"settings.general.row.showFileTree.title": "Dateibaum",
|
||||||
|
"settings.general.row.showFileTree.description": "Dateibaum in Sitzungen anzeigen",
|
||||||
|
"settings.general.row.showNavigation.title": "Navigationssteuerung",
|
||||||
|
"settings.general.row.showNavigation.description":
|
||||||
|
"Schaltflächen zum Vor- und Zurücknavigieren in der Desktop-Titelleiste anzeigen",
|
||||||
|
"settings.general.row.showSearch.title": "Befehlspalette",
|
||||||
|
"settings.general.row.showSearch.description":
|
||||||
|
"Schaltfläche für Suche und Befehlspalette in der Titelleiste anzeigen",
|
||||||
|
"settings.general.row.showTerminal.title": "Terminal",
|
||||||
|
"settings.general.row.showTerminal.description": "Terminal-Schaltfläche in der Desktop-Titelleiste anzeigen",
|
||||||
|
"settings.general.row.showStatus.title": "Serverstatus",
|
||||||
|
"settings.general.row.showStatus.description": "Serverstatus-Schaltfläche in der Titelleiste anzeigen",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Navigation unten",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"Titelleiste und Sitzungs-Tabs auf Mobilgeräten am unteren Bildschirmrand platzieren",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Benutzerdefinierte Agenten",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Agentenauswahl im Eingabebereich anzeigen",
|
||||||
"settings.general.row.reasoningSummaries.title": "Reasoning-Zusammenfassungen anzeigen",
|
"settings.general.row.reasoningSummaries.title": "Reasoning-Zusammenfassungen anzeigen",
|
||||||
"settings.general.row.reasoningSummaries.description":
|
"settings.general.row.reasoningSummaries.description":
|
||||||
"Zusammenfassungen des Modell-Reasonings in der Timeline anzeigen",
|
"Zusammenfassungen des Modell-Reasonings in der Timeline anzeigen",
|
||||||
@@ -609,6 +752,11 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen",
|
"settings.general.row.editToolPartsExpanded.title": "Edit-Tool-Abschnitte ausklappen",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
|
"Edit-, Write- und Patch-Tool-Abschnitte standardmäßig in der Timeline ausgeklappt anzeigen",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Neues Layout und Design",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Neu gestaltetes Layout, Startseite, Eingabebereich und Sitzungsoberfläche aktivieren",
|
||||||
|
"settings.general.row.pinchZoom.title": "Zoom per Fingergeste",
|
||||||
|
"settings.general.row.pinchZoom.description": "Zoomen per Zwei-Finger- und Ctrl-Scroll-Geste erlauben",
|
||||||
"settings.general.row.wayland.title": "Natives Wayland verwenden",
|
"settings.general.row.wayland.title": "Natives Wayland verwenden",
|
||||||
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
|
"settings.general.row.wayland.description": "X11-Fallback unter Wayland deaktivieren. Erfordert Neustart.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
@@ -621,6 +769,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "Manuell nach Updates suchen und installieren, wenn verfügbar",
|
"settings.updates.row.check.description": "Manuell nach Updates suchen und installieren, wenn verfügbar",
|
||||||
"settings.updates.action.checkNow": "Jetzt prüfen",
|
"settings.updates.action.checkNow": "Jetzt prüfen",
|
||||||
"settings.updates.action.checking": "Wird geprüft...",
|
"settings.updates.action.checking": "Wird geprüft...",
|
||||||
|
"settings.updates.action.downloading": "Wird heruntergeladen...",
|
||||||
|
"settings.updates.action.installing": "Wird installiert...",
|
||||||
"settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand",
|
"settings.updates.toast.latest.title": "Du bist auf dem neuesten Stand",
|
||||||
"settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.",
|
"settings.updates.toast.latest.description": "Du verwendest die aktuelle Version von OpenCode.",
|
||||||
"sound.option.none": "Keine",
|
"sound.option.none": "Keine",
|
||||||
|
|||||||
@@ -212,6 +212,11 @@ export const dict = {
|
|||||||
"model.input.video": "video",
|
"model.input.video": "video",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "Allows: {{inputs}}",
|
"model.tooltip.allows": "Allows: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Context",
|
||||||
|
"model.tooltip.inputs": "Inputs",
|
||||||
|
"model.tooltip.model": "Model",
|
||||||
|
"model.tooltip.provider": "Provider",
|
||||||
|
"model.tooltip.reasoning": "Reasoning",
|
||||||
"model.tooltip.reasoning.allowed": "Allows reasoning",
|
"model.tooltip.reasoning.allowed": "Allows reasoning",
|
||||||
"model.tooltip.reasoning.none": "No reasoning",
|
"model.tooltip.reasoning.none": "No reasoning",
|
||||||
"model.tooltip.context": "Context limit {{limit}}",
|
"model.tooltip.context": "Context limit {{limit}}",
|
||||||
|
|||||||
+152
-2
@@ -23,6 +23,9 @@ export const dict = {
|
|||||||
|
|
||||||
"command.sidebar.toggle": "Alternar barra lateral",
|
"command.sidebar.toggle": "Alternar barra lateral",
|
||||||
"command.project.open": "Abrir proyecto",
|
"command.project.open": "Abrir proyecto",
|
||||||
|
"command.project.previous": "Proyecto anterior",
|
||||||
|
"command.project.next": "Siguiente proyecto",
|
||||||
|
"command.project.index": "Cambiar al proyecto {{index}}",
|
||||||
"command.provider.connect": "Conectar proveedor",
|
"command.provider.connect": "Conectar proveedor",
|
||||||
"command.server.switch": "Cambiar servidor",
|
"command.server.switch": "Cambiar servidor",
|
||||||
"command.settings.open": "Abrir ajustes",
|
"command.settings.open": "Abrir ajustes",
|
||||||
@@ -45,6 +48,7 @@ export const dict = {
|
|||||||
"command.session.new": "Nueva sesión",
|
"command.session.new": "Nueva sesión",
|
||||||
"command.file.open": "Abrir archivo",
|
"command.file.open": "Abrir archivo",
|
||||||
"command.tab.close": "Cerrar pestaña",
|
"command.tab.close": "Cerrar pestaña",
|
||||||
|
"command.tab.reopenClosed": "Reabrir pestaña cerrada",
|
||||||
"command.context.addSelection": "Añadir selección al contexto",
|
"command.context.addSelection": "Añadir selección al contexto",
|
||||||
"command.context.addSelection.description": "Añadir las líneas seleccionadas del archivo actual",
|
"command.context.addSelection.description": "Añadir las líneas seleccionadas del archivo actual",
|
||||||
"command.input.focus": "Enfocar entrada",
|
"command.input.focus": "Enfocar entrada",
|
||||||
@@ -208,6 +212,11 @@ export const dict = {
|
|||||||
"model.input.video": "video",
|
"model.input.video": "video",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "Permite: {{inputs}}",
|
"model.tooltip.allows": "Permite: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Contexto",
|
||||||
|
"model.tooltip.inputs": "Entradas",
|
||||||
|
"model.tooltip.model": "Modelo",
|
||||||
|
"model.tooltip.provider": "Proveedor",
|
||||||
|
"model.tooltip.reasoning": "Razonamiento",
|
||||||
"model.tooltip.reasoning.allowed": "Permite razonamiento",
|
"model.tooltip.reasoning.allowed": "Permite razonamiento",
|
||||||
"model.tooltip.reasoning.none": "Sin razonamiento",
|
"model.tooltip.reasoning.none": "Sin razonamiento",
|
||||||
"model.tooltip.context": "Límite de contexto {{limit}}",
|
"model.tooltip.context": "Límite de contexto {{limit}}",
|
||||||
@@ -218,6 +227,7 @@ export const dict = {
|
|||||||
"common.loading": "Cargando",
|
"common.loading": "Cargando",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "Cancelar",
|
"common.cancel": "Cancelar",
|
||||||
|
"common.clear": "Limpiar",
|
||||||
"common.connect": "Conectar",
|
"common.connect": "Conectar",
|
||||||
"common.disconnect": "Desconectar",
|
"common.disconnect": "Desconectar",
|
||||||
"common.continue": "Enviar",
|
"common.continue": "Enviar",
|
||||||
@@ -235,6 +245,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.normal": "Prompt",
|
"prompt.mode.normal": "Prompt",
|
||||||
"prompt.mode.shell.exit": "esc para salir",
|
"prompt.mode.shell.exit": "esc para salir",
|
||||||
|
"session.child.promptDisabled": "No se pueden enviar prompts a las sesiones de subagentes.",
|
||||||
|
"session.child.backToParent": "Volver a la sesión principal.",
|
||||||
|
|
||||||
"prompt.example.1": "Arreglar un TODO en el código",
|
"prompt.example.1": "Arreglar un TODO en el código",
|
||||||
"prompt.example.2": "¿Cuál es el stack tecnológico de este proyecto?",
|
"prompt.example.2": "¿Cuál es el stack tecnológico de este proyecto?",
|
||||||
@@ -341,6 +353,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "Eliminar",
|
"dialog.server.menu.delete": "Eliminar",
|
||||||
"dialog.server.current": "Servidor actual",
|
"dialog.server.current": "Servidor actual",
|
||||||
"dialog.server.status.default": "Predeterminado",
|
"dialog.server.status.default": "Predeterminado",
|
||||||
|
"wsl.server.add": "Añadir servidor WSL",
|
||||||
|
"wsl.server.addShort": "Añadir WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "Servidor WSL",
|
||||||
|
"wsl.server.retryStart": "Reintentar el inicio",
|
||||||
|
"wsl.server.updating": "Actualizando...",
|
||||||
|
"wsl.onboarding.step.distro": "Elegir distribución",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "Comprobando WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "Es necesario reiniciar Windows para terminar de instalar WSL.",
|
||||||
|
"wsl.onboarding.ready": "WSL está listo.",
|
||||||
|
"wsl.onboarding.required": "Se necesita WSL para continuar.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Comprobando distribuciones...",
|
||||||
|
"wsl.onboarding.installingDistro": "Instalando {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "Comprobando {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "Listando distribuciones...",
|
||||||
|
"wsl.onboarding.distroReady": "La distribución {{distro}} está lista.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "La distribución {{distro}} aún no está instalada.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Abre {{distro}} una vez para completar la configuración.",
|
||||||
|
"wsl.onboarding.finishingDistro": "Completando la configuración de {{distro}}.",
|
||||||
|
"wsl.onboarding.pickDistro": "Elige una distribución o instala una abajo.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "Comprobando OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "Comprobando OpenCode en {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "Actualizando OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "Actualizando OpenCode en {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "Actualiza OpenCode en {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "Actualizar OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode está listo en {{distro}}.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode está listo.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "Instala OpenCode en {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "Instalar OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Elige primero una distribución.",
|
||||||
|
"wsl.onboarding.loadFailed": "No se pudo cargar el estado de WSL.",
|
||||||
|
"wsl.onboarding.loading": "Cargando...",
|
||||||
|
"wsl.onboarding.installedDistros": "Distribuciones instaladas",
|
||||||
|
"wsl.onboarding.checkAgain": "Comprobar de nuevo",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Lista",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Comprobando...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode no está instalado",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "Faltan bash y curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "No compatible · Usa WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "¿Necesitas otra distribución?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "Instala una distribución de Linux desde el catálogo de WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL no está instalado",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"Se necesita WSL (Subsistema de Windows para Linux) para que OpenCode pueda añadir un servidor WSL",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL no disponible",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode no pudo comprobar WSL en este equipo.",
|
||||||
|
"wsl.onboarding.installWsl": "Instalar WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"Reinicia Windows para terminar de instalar WSL y, después, vuelve a abrir OpenCode.",
|
||||||
|
"wsl.onboarding.next": "Siguiente",
|
||||||
|
"wsl.onboarding.refresh": "Actualizar",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Ya se han añadido todas las distribuciones instaladas.",
|
||||||
|
"wsl.onboarding.noDistros": "Aún no se han detectado distribuciones.",
|
||||||
|
"wsl.onboarding.install": "Instalar",
|
||||||
|
"wsl.onboarding.installing": "Instalando...",
|
||||||
|
"wsl.onboarding.installDistro": "Instalar distribución",
|
||||||
|
"wsl.onboarding.searchDistros": "Buscar distribuciones",
|
||||||
|
"wsl.onboarding.wsl2Required": "Se necesita WSL 2.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Esta distribución necesita bash y curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "Abrir terminal",
|
||||||
|
"wsl.onboarding.path": "Ruta: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "no encontrado",
|
||||||
|
"wsl.onboarding.version": "Versión: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "desconocida",
|
||||||
|
"wsl.onboarding.desktopVersion": "escritorio {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "La versión instalada no coincide con la versión de la aplicación de escritorio.",
|
||||||
|
"wsl.onboarding.adding": "Añadiendo...",
|
||||||
|
|
||||||
"dialog.project.edit.title": "Editar proyecto",
|
"dialog.project.edit.title": "Editar proyecto",
|
||||||
"dialog.project.edit.name": "Nombre",
|
"dialog.project.edit.name": "Nombre",
|
||||||
@@ -405,6 +486,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
|
|
||||||
@@ -453,10 +535,12 @@ export const dict = {
|
|||||||
|
|
||||||
"error.page.title": "Algo salió mal",
|
"error.page.title": "Algo salió mal",
|
||||||
"error.page.description": "Ocurrió un error al cargar la aplicación.",
|
"error.page.description": "Ocurrió un error al cargar la aplicación.",
|
||||||
|
"error.page.description.localServerStartup": "Ocurrió un error al iniciar el servidor local.",
|
||||||
"error.page.details.label": "Detalles del error",
|
"error.page.details.label": "Detalles del error",
|
||||||
"error.page.action.restart": "Reiniciar",
|
"error.page.action.restart": "Reiniciar",
|
||||||
"error.page.action.report": "Informar error",
|
"error.page.action.report": "Informar error",
|
||||||
"error.page.action.reported": "Error informado",
|
"error.page.action.reported": "Error informado",
|
||||||
|
"error.page.action.exportLogs": "Exportar registros",
|
||||||
"error.page.action.checking": "Comprobando...",
|
"error.page.action.checking": "Comprobando...",
|
||||||
"error.page.action.checkUpdates": "Buscar actualizaciones",
|
"error.page.action.checkUpdates": "Buscar actualizaciones",
|
||||||
"error.page.action.updateTo": "Actualizar a {{version}}",
|
"error.page.action.updateTo": "Actualizar a {{version}}",
|
||||||
@@ -505,11 +589,31 @@ export const dict = {
|
|||||||
"home.recentProjects": "Proyectos recientes",
|
"home.recentProjects": "Proyectos recientes",
|
||||||
"home.empty.title": "Sin proyectos recientes",
|
"home.empty.title": "Sin proyectos recientes",
|
||||||
"home.empty.description": "Empieza abriendo un proyecto local",
|
"home.empty.description": "Empieza abriendo un proyecto local",
|
||||||
|
"home.title": "Inicio",
|
||||||
|
"home.projects": "Proyectos",
|
||||||
|
"home.project.add": "Añadir proyecto",
|
||||||
|
"home.recentlyClosed": "Proyectos cerrados recientemente",
|
||||||
|
"home.server.collapse": "Contraer proyectos del servidor",
|
||||||
|
"home.server.expand": "Expandir proyectos del servidor",
|
||||||
|
"home.sessions.search.placeholder": "Buscar sesiones",
|
||||||
|
"home.sessions.search.placeholder.scoped": "Buscar sesiones en {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "Sesiones",
|
||||||
|
"home.sessions.search.noResults": "No se encontraron sesiones para {{query}}",
|
||||||
|
"home.sessions.empty": "Aún no hay nada",
|
||||||
|
"home.sessions.empty.description": "Crea una sesión para empezar",
|
||||||
|
"home.sessions.group.today": "Hoy",
|
||||||
|
"home.sessions.group.yesterday": "Ayer",
|
||||||
|
"home.sessions.group.older": "Anteriores",
|
||||||
|
|
||||||
"session.tab.session": "Sesión",
|
"session.tab.session": "Sesión",
|
||||||
"session.tab.review": "Revisión",
|
"session.tab.review": "Revisión",
|
||||||
"session.tab.context": "Contexto",
|
"session.tab.context": "Contexto",
|
||||||
|
"session.tab.unknown": "Sesión desconocida",
|
||||||
"session.panel.reviewAndFiles": "Revisión y archivos",
|
"session.panel.reviewAndFiles": "Revisión y archivos",
|
||||||
|
"session.error.notFound": "No se encuentra esta sesión",
|
||||||
|
"session.error.notFound.description": "Esta pestaña apunta a una sesión que ya no existe en este servidor.",
|
||||||
|
"session.error.notFound.closeTab": "Cerrar pestaña",
|
||||||
|
"session.error.serverConnection": "No se puede conectar a este servidor",
|
||||||
"session.review.filesChanged": "{{count}} Archivos Cambiados",
|
"session.review.filesChanged": "{{count}} Archivos Cambiados",
|
||||||
"session.review.change.one": "Cambio",
|
"session.review.change.one": "Cambio",
|
||||||
"session.review.change.other": "Cambios",
|
"session.review.change.other": "Cambios",
|
||||||
@@ -519,6 +623,8 @@ export const dict = {
|
|||||||
"session.review.noSnapshot":
|
"session.review.noSnapshot":
|
||||||
"El seguimiento de instantáneas está deshabilitado en la configuración, por lo que los cambios de sesión no están disponibles",
|
"El seguimiento de instantáneas está deshabilitado en la configuración, por lo que los cambios de sesión no están disponibles",
|
||||||
"session.review.noChanges": "Sin cambios",
|
"session.review.noChanges": "Sin cambios",
|
||||||
|
"session.review.noUncommittedChanges": "Aún no hay cambios sin confirmar",
|
||||||
|
"session.review.noBranchChanges": "Aún no hay cambios en la rama",
|
||||||
|
|
||||||
"session.files.selectToOpen": "Selecciona un archivo para abrir",
|
"session.files.selectToOpen": "Selecciona un archivo para abrir",
|
||||||
"session.files.all": "Todos los archivos",
|
"session.files.all": "Todos los archivos",
|
||||||
@@ -535,6 +641,10 @@ export const dict = {
|
|||||||
"session.todo.title": "Tareas",
|
"session.todo.title": "Tareas",
|
||||||
"session.todo.collapse": "Contraer",
|
"session.todo.collapse": "Contraer",
|
||||||
"session.todo.expand": "Expandir",
|
"session.todo.expand": "Expandir",
|
||||||
|
"session.question.minimize": "Minimizar pregunta",
|
||||||
|
"session.question.restore": "Restaurar pregunta",
|
||||||
|
"session.question.pending.one": "{{count}} pregunta pendiente",
|
||||||
|
"session.question.pending.other": "{{count}} preguntas pendientes",
|
||||||
"session.followupDock.summary.one": "{{count}} mensaje en cola",
|
"session.followupDock.summary.one": "{{count}} mensaje en cola",
|
||||||
"session.followupDock.summary.other": "{{count}} mensajes en cola",
|
"session.followupDock.summary.other": "{{count}} mensajes en cola",
|
||||||
"session.followupDock.sendNow": "Enviar ahora",
|
"session.followupDock.sendNow": "Enviar ahora",
|
||||||
@@ -548,9 +658,16 @@ export const dict = {
|
|||||||
"session.revertDock.restore": "Restaurar mensaje",
|
"session.revertDock.restore": "Restaurar mensaje",
|
||||||
|
|
||||||
"session.new.title": "Construye lo que quieras",
|
"session.new.title": "Construye lo que quieras",
|
||||||
|
"session.new.project.new": "Nuevo proyecto",
|
||||||
|
"session.new.project.search": "Buscar proyectos",
|
||||||
|
"session.new.project.add": "Añadir proyecto",
|
||||||
"session.new.worktree.main": "Rama principal",
|
"session.new.worktree.main": "Rama principal",
|
||||||
"session.new.worktree.mainWithBranch": "Rama principal ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Rama principal ({{branch}})",
|
||||||
"session.new.worktree.create": "Crear nuevo árbol de trabajo",
|
"session.new.worktree.create": "Crear nuevo árbol de trabajo",
|
||||||
|
"session.new.workspace.runIn": "Ejecutar sesión en",
|
||||||
|
"session.new.workspace.triggerLocal": "Local",
|
||||||
|
"session.new.workspace.local": "Repositorio local",
|
||||||
|
"session.new.workspace.existing": "Espacio de trabajo…",
|
||||||
"session.new.lastModified": "Última modificación",
|
"session.new.lastModified": "Última modificación",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Buscar {{project}}",
|
"session.header.search.placeholder": "Buscar {{project}}",
|
||||||
@@ -621,6 +738,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "Sesiones recientes",
|
"sidebar.project.recentSessions": "Sesiones recientes",
|
||||||
"sidebar.project.viewAllSessions": "Ver todas las sesiones",
|
"sidebar.project.viewAllSessions": "Ver todas las sesiones",
|
||||||
"sidebar.project.clearNotifications": "Borrar notificaciones",
|
"sidebar.project.clearNotifications": "Borrar notificaciones",
|
||||||
|
"sidebar.empty.title": "No hay proyectos abiertos",
|
||||||
|
"sidebar.empty.description": "Abre un proyecto para empezar",
|
||||||
|
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
|
|
||||||
@@ -633,6 +752,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.description": "Ejecutar el servidor OpenCode dentro de WSL en Windows.",
|
"settings.desktop.wsl.description": "Ejecutar el servidor OpenCode dentro de WSL en Windows.",
|
||||||
|
|
||||||
"settings.general.section.appearance": "Apariencia",
|
"settings.general.section.appearance": "Apariencia",
|
||||||
|
"settings.general.section.advanced": "Avanzado",
|
||||||
"settings.general.section.notifications": "Notificaciones del sistema",
|
"settings.general.section.notifications": "Notificaciones del sistema",
|
||||||
"settings.general.section.updates": "Actualizaciones",
|
"settings.general.section.updates": "Actualizaciones",
|
||||||
"settings.general.section.sounds": "Efectos de sonido",
|
"settings.general.section.sounds": "Efectos de sonido",
|
||||||
@@ -641,6 +761,11 @@ export const dict = {
|
|||||||
|
|
||||||
"settings.general.row.language.title": "Idioma",
|
"settings.general.row.language.title": "Idioma",
|
||||||
"settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode",
|
"settings.general.row.language.description": "Cambiar el idioma de visualización para OpenCode",
|
||||||
|
"settings.general.row.shell.title": "Shell del terminal",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"Elige el shell que se usará en el terminal. Los shells compatibles también se usarán para las llamadas a herramientas del agente.",
|
||||||
|
"settings.general.row.shell.autoDefault": "Automático (Predeterminado)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "solo en el terminal",
|
||||||
"settings.general.row.appearance.title": "Apariencia",
|
"settings.general.row.appearance.title": "Apariencia",
|
||||||
"settings.general.row.appearance.description": "Personaliza cómo se ve OpenCode en tu dispositivo",
|
"settings.general.row.appearance.description": "Personaliza cómo se ve OpenCode en tu dispositivo",
|
||||||
"settings.general.row.colorScheme.title": "Esquema de color",
|
"settings.general.row.colorScheme.title": "Esquema de color",
|
||||||
@@ -649,8 +774,8 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
|
"settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
|
||||||
"settings.general.row.font.title": "Fuente de código",
|
"settings.general.row.font.title": "Fuente de código",
|
||||||
"settings.general.row.font.description": "Personaliza la fuente usada en bloques de código",
|
"settings.general.row.font.description": "Personaliza la fuente usada en bloques de código",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "Fuente del terminal",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "Personaliza la fuente utilizada en el terminal",
|
||||||
"settings.general.row.uiFont.title": "Fuente de la interfaz",
|
"settings.general.row.uiFont.title": "Fuente de la interfaz",
|
||||||
"settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz",
|
"settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz",
|
||||||
"settings.general.row.followup.title": "Comportamiento de seguimiento",
|
"settings.general.row.followup.title": "Comportamiento de seguimiento",
|
||||||
@@ -658,6 +783,23 @@ export const dict = {
|
|||||||
"Elige si los prompts de seguimiento se dirigen inmediatamente o esperan en una cola",
|
"Elige si los prompts de seguimiento se dirigen inmediatamente o esperan en una cola",
|
||||||
"settings.general.row.followup.option.queue": "Cola",
|
"settings.general.row.followup.option.queue": "Cola",
|
||||||
"settings.general.row.followup.option.steer": "Dirigir",
|
"settings.general.row.followup.option.steer": "Dirigir",
|
||||||
|
"settings.general.row.showFileTree.title": "Árbol de archivos",
|
||||||
|
"settings.general.row.showFileTree.description": "Mostrar el panel del árbol de archivos en las sesiones",
|
||||||
|
"settings.general.row.showNavigation.title": "Controles de navegación",
|
||||||
|
"settings.general.row.showNavigation.description":
|
||||||
|
"Mostrar los botones para retroceder y avanzar en la barra de título de la aplicación de escritorio",
|
||||||
|
"settings.general.row.showSearch.title": "Paleta de comandos",
|
||||||
|
"settings.general.row.showSearch.description":
|
||||||
|
"Mostrar el botón de búsqueda y paleta de comandos en la barra de título",
|
||||||
|
"settings.general.row.showTerminal.title": "Terminal",
|
||||||
|
"settings.general.row.showTerminal.description": "Mostrar el botón del terminal en la barra de título",
|
||||||
|
"settings.general.row.showStatus.title": "Estado del servidor",
|
||||||
|
"settings.general.row.showStatus.description": "Mostrar el botón de estado del servidor en la barra de título",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Navegación inferior",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"Colocar la barra de título y las pestañas de sesión en la parte inferior de la pantalla en dispositivos móviles",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Agentes personalizados",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Mostrar el selector de agentes en el editor",
|
||||||
"settings.general.row.reasoningSummaries.title": "Mostrar resúmenes de razonamiento",
|
"settings.general.row.reasoningSummaries.title": "Mostrar resúmenes de razonamiento",
|
||||||
"settings.general.row.reasoningSummaries.description":
|
"settings.general.row.reasoningSummaries.description":
|
||||||
"Mostrar resúmenes del razonamiento del modelo en la línea de tiempo",
|
"Mostrar resúmenes del razonamiento del modelo en la línea de tiempo",
|
||||||
@@ -667,6 +809,12 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición",
|
"settings.general.row.editToolPartsExpanded.title": "Expandir partes de la herramienta de edición",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo",
|
"Mostrar las partes de las herramientas de edición, escritura y parcheado expandidas por defecto en la línea de tiempo",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Nuevo diseño y estilos",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Activar el nuevo diseño de la interfaz, la página de inicio, el editor y las sesiones",
|
||||||
|
"settings.general.row.pinchZoom.title": "Pellizcar para ampliar",
|
||||||
|
"settings.general.row.pinchZoom.description":
|
||||||
|
"Permitir ampliar con el gesto de pellizco del panel táctil y con Ctrl-scroll",
|
||||||
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
"settings.general.row.wayland.title": "Usar Wayland nativo",
|
||||||
"settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
|
"settings.general.row.wayland.description": "Deshabilitar fallback a X11 en Wayland. Requiere reinicio.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
@@ -682,6 +830,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "Buscar actualizaciones manualmente e instalarlas si hay alguna",
|
"settings.updates.row.check.description": "Buscar actualizaciones manualmente e instalarlas si hay alguna",
|
||||||
"settings.updates.action.checkNow": "Buscar ahora",
|
"settings.updates.action.checkNow": "Buscar ahora",
|
||||||
"settings.updates.action.checking": "Buscando...",
|
"settings.updates.action.checking": "Buscando...",
|
||||||
|
"settings.updates.action.downloading": "Descargando...",
|
||||||
|
"settings.updates.action.installing": "Instalando...",
|
||||||
"settings.updates.toast.latest.title": "Estás al día",
|
"settings.updates.toast.latest.title": "Estás al día",
|
||||||
"settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.",
|
"settings.updates.toast.latest.description": "Estás usando la última versión de OpenCode.",
|
||||||
"sound.option.none": "Ninguno",
|
"sound.option.none": "Ninguno",
|
||||||
|
|||||||
+153
-2
@@ -21,6 +21,9 @@ export const dict = {
|
|||||||
"theme.scheme.dark": "Sombre",
|
"theme.scheme.dark": "Sombre",
|
||||||
"command.sidebar.toggle": "Basculer la barre latérale",
|
"command.sidebar.toggle": "Basculer la barre latérale",
|
||||||
"command.project.open": "Ouvrir un projet",
|
"command.project.open": "Ouvrir un projet",
|
||||||
|
"command.project.previous": "Projet précédent",
|
||||||
|
"command.project.next": "Projet suivant",
|
||||||
|
"command.project.index": "Passer au projet {{index}}",
|
||||||
"command.provider.connect": "Connecter un fournisseur",
|
"command.provider.connect": "Connecter un fournisseur",
|
||||||
"command.server.switch": "Changer de serveur",
|
"command.server.switch": "Changer de serveur",
|
||||||
"command.settings.open": "Ouvrir les paramètres",
|
"command.settings.open": "Ouvrir les paramètres",
|
||||||
@@ -39,6 +42,7 @@ export const dict = {
|
|||||||
"command.session.new": "Nouvelle session",
|
"command.session.new": "Nouvelle session",
|
||||||
"command.file.open": "Ouvrir un fichier",
|
"command.file.open": "Ouvrir un fichier",
|
||||||
"command.tab.close": "Fermer l'onglet",
|
"command.tab.close": "Fermer l'onglet",
|
||||||
|
"command.tab.reopenClosed": "Rouvrir l'onglet fermé",
|
||||||
"command.context.addSelection": "Ajouter la sélection au contexte",
|
"command.context.addSelection": "Ajouter la sélection au contexte",
|
||||||
"command.context.addSelection.description": "Ajouter les lignes sélectionnées du fichier actuel",
|
"command.context.addSelection.description": "Ajouter les lignes sélectionnées du fichier actuel",
|
||||||
"command.input.focus": "Focus sur l'entrée",
|
"command.input.focus": "Focus sur l'entrée",
|
||||||
@@ -193,6 +197,11 @@ export const dict = {
|
|||||||
"model.input.video": "vidéo",
|
"model.input.video": "vidéo",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "Autorise : {{inputs}}",
|
"model.tooltip.allows": "Autorise : {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Contexte",
|
||||||
|
"model.tooltip.inputs": "Entrées",
|
||||||
|
"model.tooltip.model": "Modèle",
|
||||||
|
"model.tooltip.provider": "Fournisseur",
|
||||||
|
"model.tooltip.reasoning": "Raisonnement",
|
||||||
"model.tooltip.reasoning.allowed": "Autorise le raisonnement",
|
"model.tooltip.reasoning.allowed": "Autorise le raisonnement",
|
||||||
"model.tooltip.reasoning.none": "Sans raisonnement",
|
"model.tooltip.reasoning.none": "Sans raisonnement",
|
||||||
"model.tooltip.context": "Limite de contexte {{limit}}",
|
"model.tooltip.context": "Limite de contexte {{limit}}",
|
||||||
@@ -202,6 +211,7 @@ export const dict = {
|
|||||||
"common.loading": "Chargement",
|
"common.loading": "Chargement",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "Annuler",
|
"common.cancel": "Annuler",
|
||||||
|
"common.clear": "Effacer",
|
||||||
"common.connect": "Connecter",
|
"common.connect": "Connecter",
|
||||||
"common.disconnect": "Déconnecter",
|
"common.disconnect": "Déconnecter",
|
||||||
"common.continue": "Soumettre",
|
"common.continue": "Soumettre",
|
||||||
@@ -218,6 +228,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.normal": "Prompt",
|
"prompt.mode.normal": "Prompt",
|
||||||
"prompt.mode.shell.exit": "esc pour quitter",
|
"prompt.mode.shell.exit": "esc pour quitter",
|
||||||
|
"session.child.promptDisabled": "Les sessions de sous-agents ne peuvent pas recevoir de messages.",
|
||||||
|
"session.child.backToParent": "Revenir à la session principale.",
|
||||||
"prompt.example.1": "Corriger un TODO dans la base de code",
|
"prompt.example.1": "Corriger un TODO dans la base de code",
|
||||||
"prompt.example.2": "Quelle est la pile technique de ce projet ?",
|
"prompt.example.2": "Quelle est la pile technique de ce projet ?",
|
||||||
"prompt.example.3": "Réparer les tests échoués",
|
"prompt.example.3": "Réparer les tests échoués",
|
||||||
@@ -315,6 +327,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "Supprimer",
|
"dialog.server.menu.delete": "Supprimer",
|
||||||
"dialog.server.current": "Serveur actuel",
|
"dialog.server.current": "Serveur actuel",
|
||||||
"dialog.server.status.default": "Défaut",
|
"dialog.server.status.default": "Défaut",
|
||||||
|
"wsl.server.add": "Ajouter un serveur WSL",
|
||||||
|
"wsl.server.addShort": "Ajouter WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "Serveur WSL",
|
||||||
|
"wsl.server.retryStart": "Réessayer de démarrer",
|
||||||
|
"wsl.server.updating": "Mise à jour...",
|
||||||
|
"wsl.onboarding.step.distro": "Choisir une distribution",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "Vérification de WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "Windows doit redémarrer pour terminer l'installation de WSL.",
|
||||||
|
"wsl.onboarding.ready": "WSL est prêt.",
|
||||||
|
"wsl.onboarding.required": "WSL est requis pour continuer.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Vérification des distributions...",
|
||||||
|
"wsl.onboarding.installingDistro": "Installation de {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "Vérification de {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "Récupération de la liste des distributions...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} est prête.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "{{distro}} n'est pas encore installée.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Ouvrez {{distro}} une fois pour terminer la configuration.",
|
||||||
|
"wsl.onboarding.finishingDistro": "Finalisation de la configuration de {{distro}}.",
|
||||||
|
"wsl.onboarding.pickDistro": "Choisissez une distribution ou installez-en une ci-dessous.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "Vérification d'OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "Vérification d'OpenCode dans {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "Mise à jour d'OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "Mise à jour d'OpenCode dans {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "Mettez à jour OpenCode dans {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "Mettre à jour OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode est prêt dans {{distro}}.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode est prêt.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "Installez OpenCode dans {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "Installer OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Choisissez d'abord une distribution.",
|
||||||
|
"wsl.onboarding.loadFailed": "Impossible de charger l'état de WSL.",
|
||||||
|
"wsl.onboarding.loading": "Chargement...",
|
||||||
|
"wsl.onboarding.installedDistros": "Distributions installées",
|
||||||
|
"wsl.onboarding.checkAgain": "Vérifier à nouveau",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Prête",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Vérification...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode non installé",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "bash et curl manquants",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "Non prise en charge · Utilisez WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "Besoin d'une autre distribution ?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "Installez une distribution Linux depuis le catalogue WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL n'est pas installé",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"WSL (Windows Subsystem for Linux) est requis avant qu'OpenCode puisse ajouter un serveur WSL",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL indisponible",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode n'a pas pu vérifier WSL sur cet appareil.",
|
||||||
|
"wsl.onboarding.installWsl": "Installer WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"Redémarrez Windows pour terminer l'installation de WSL, puis rouvrez OpenCode.",
|
||||||
|
"wsl.onboarding.next": "Suivant",
|
||||||
|
"wsl.onboarding.refresh": "Actualiser",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Toutes les distributions installées ont déjà été ajoutées.",
|
||||||
|
"wsl.onboarding.noDistros": "Aucune distribution détectée pour l'instant.",
|
||||||
|
"wsl.onboarding.install": "Installer",
|
||||||
|
"wsl.onboarding.installing": "Installation...",
|
||||||
|
"wsl.onboarding.installDistro": "Installer une distribution",
|
||||||
|
"wsl.onboarding.searchDistros": "Rechercher des distributions",
|
||||||
|
"wsl.onboarding.wsl2Required": "WSL 2 est requis.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Cette distribution nécessite bash et curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "Ouvrir le terminal",
|
||||||
|
"wsl.onboarding.path": "Chemin : {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "introuvable",
|
||||||
|
"wsl.onboarding.version": "Version : {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "inconnue",
|
||||||
|
"wsl.onboarding.desktopVersion": "desktop {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "La version installée ne correspond pas à celle de l'application desktop.",
|
||||||
|
"wsl.onboarding.adding": "Ajout...",
|
||||||
"dialog.project.edit.title": "Modifier le projet",
|
"dialog.project.edit.title": "Modifier le projet",
|
||||||
"dialog.project.edit.name": "Nom",
|
"dialog.project.edit.name": "Nom",
|
||||||
"dialog.project.edit.icon": "Icône",
|
"dialog.project.edit.icon": "Icône",
|
||||||
@@ -373,6 +454,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
"toast.language.title": "Langue",
|
"toast.language.title": "Langue",
|
||||||
@@ -410,10 +492,12 @@ export const dict = {
|
|||||||
"toast.update.action.notYet": "Pas encore",
|
"toast.update.action.notYet": "Pas encore",
|
||||||
"error.page.title": "Quelque chose s'est mal passé",
|
"error.page.title": "Quelque chose s'est mal passé",
|
||||||
"error.page.description": "Une erreur s'est produite lors du chargement de l'application.",
|
"error.page.description": "Une erreur s'est produite lors du chargement de l'application.",
|
||||||
|
"error.page.description.localServerStartup": "Une erreur s'est produite lors du démarrage du serveur local.",
|
||||||
"error.page.details.label": "Détails de l'erreur",
|
"error.page.details.label": "Détails de l'erreur",
|
||||||
"error.page.action.restart": "Redémarrer",
|
"error.page.action.restart": "Redémarrer",
|
||||||
"error.page.action.report": "Signaler l'erreur",
|
"error.page.action.report": "Signaler l'erreur",
|
||||||
"error.page.action.reported": "Erreur signalée",
|
"error.page.action.reported": "Erreur signalée",
|
||||||
|
"error.page.action.exportLogs": "Exporter les journaux",
|
||||||
"error.page.action.checking": "Vérification...",
|
"error.page.action.checking": "Vérification...",
|
||||||
"error.page.action.checkUpdates": "Vérifier les mises à jour",
|
"error.page.action.checkUpdates": "Vérifier les mises à jour",
|
||||||
"error.page.action.updateTo": "Mettre à jour vers {{version}}",
|
"error.page.action.updateTo": "Mettre à jour vers {{version}}",
|
||||||
@@ -458,16 +542,38 @@ export const dict = {
|
|||||||
"home.recentProjects": "Projets récents",
|
"home.recentProjects": "Projets récents",
|
||||||
"home.empty.title": "Aucun projet récent",
|
"home.empty.title": "Aucun projet récent",
|
||||||
"home.empty.description": "Commencez par ouvrir un projet local",
|
"home.empty.description": "Commencez par ouvrir un projet local",
|
||||||
|
"home.title": "Accueil",
|
||||||
|
"home.projects": "Projets",
|
||||||
|
"home.project.add": "Ajouter un projet",
|
||||||
|
"home.recentlyClosed": "Récemment fermés",
|
||||||
|
"home.server.collapse": "Réduire les projets du serveur",
|
||||||
|
"home.server.expand": "Développer les projets du serveur",
|
||||||
|
"home.sessions.search.placeholder": "Rechercher des sessions",
|
||||||
|
"home.sessions.search.placeholder.scoped": "Rechercher les sessions dans {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "Sessions",
|
||||||
|
"home.sessions.search.noResults": "Aucune session trouvée pour {{query}}",
|
||||||
|
"home.sessions.empty": "Rien ici pour l'instant",
|
||||||
|
"home.sessions.empty.description": "Créez une session pour commencer",
|
||||||
|
"home.sessions.group.today": "Aujourd'hui",
|
||||||
|
"home.sessions.group.yesterday": "Hier",
|
||||||
|
"home.sessions.group.older": "Plus anciennes",
|
||||||
"session.tab.session": "Session",
|
"session.tab.session": "Session",
|
||||||
"session.tab.review": "Revue",
|
"session.tab.review": "Revue",
|
||||||
"session.tab.context": "Contexte",
|
"session.tab.context": "Contexte",
|
||||||
|
"session.tab.unknown": "Session inconnue",
|
||||||
"session.panel.reviewAndFiles": "Revue et fichiers",
|
"session.panel.reviewAndFiles": "Revue et fichiers",
|
||||||
|
"session.error.notFound": "Cette session est introuvable",
|
||||||
|
"session.error.notFound.description": "Cet onglet pointe vers une session qui n'existe plus sur ce serveur.",
|
||||||
|
"session.error.notFound.closeTab": "Fermer l'onglet",
|
||||||
|
"session.error.serverConnection": "Impossible de se connecter à ce serveur",
|
||||||
"session.review.filesChanged": "{{count}} fichiers modifiés",
|
"session.review.filesChanged": "{{count}} fichiers modifiés",
|
||||||
"session.review.change.one": "Modification",
|
"session.review.change.one": "Modification",
|
||||||
"session.review.change.other": "Modifications",
|
"session.review.change.other": "Modifications",
|
||||||
"session.review.loadingChanges": "Chargement des modifications...",
|
"session.review.loadingChanges": "Chargement des modifications...",
|
||||||
"session.review.empty": "Aucune modification dans cette session pour l'instant",
|
"session.review.empty": "Aucune modification dans cette session pour l'instant",
|
||||||
"session.review.noChanges": "Aucune modification",
|
"session.review.noChanges": "Aucune modification",
|
||||||
|
"session.review.noUncommittedChanges": "Aucune modification non validée pour l'instant",
|
||||||
|
"session.review.noBranchChanges": "Aucune modification de branche pour l'instant",
|
||||||
"session.review.noVcs": "Aucun système de contrôle de version Git détecté, modifications non affichées",
|
"session.review.noVcs": "Aucun système de contrôle de version Git détecté, modifications non affichées",
|
||||||
"session.review.noSnapshot":
|
"session.review.noSnapshot":
|
||||||
"Le suivi des instantanés est désactivé dans la configuration, les modifications de session sont donc indisponibles",
|
"Le suivi des instantanés est désactivé dans la configuration, les modifications de session sont donc indisponibles",
|
||||||
@@ -484,6 +590,10 @@ export const dict = {
|
|||||||
"session.todo.title": "Tâches",
|
"session.todo.title": "Tâches",
|
||||||
"session.todo.collapse": "Réduire",
|
"session.todo.collapse": "Réduire",
|
||||||
"session.todo.expand": "Développer",
|
"session.todo.expand": "Développer",
|
||||||
|
"session.question.minimize": "Réduire la question",
|
||||||
|
"session.question.restore": "Restaurer la question",
|
||||||
|
"session.question.pending.one": "{{count}} question en attente",
|
||||||
|
"session.question.pending.other": "{{count}} questions en attente",
|
||||||
"session.followupDock.summary.one": "{{count}} message en file d'attente",
|
"session.followupDock.summary.one": "{{count}} message en file d'attente",
|
||||||
"session.followupDock.summary.other": "{{count}} messages en file d'attente",
|
"session.followupDock.summary.other": "{{count}} messages en file d'attente",
|
||||||
"session.followupDock.sendNow": "Envoyer maintenant",
|
"session.followupDock.sendNow": "Envoyer maintenant",
|
||||||
@@ -496,9 +606,16 @@ export const dict = {
|
|||||||
"session.revertDock.expand": "Développer les messages annulés",
|
"session.revertDock.expand": "Développer les messages annulés",
|
||||||
"session.revertDock.restore": "Restaurer le message",
|
"session.revertDock.restore": "Restaurer le message",
|
||||||
"session.new.title": "Créez ce que vous voulez",
|
"session.new.title": "Créez ce que vous voulez",
|
||||||
|
"session.new.project.new": "Nouveau projet",
|
||||||
|
"session.new.project.search": "Rechercher des projets",
|
||||||
|
"session.new.project.add": "Ajouter un projet",
|
||||||
"session.new.worktree.main": "Branche principale",
|
"session.new.worktree.main": "Branche principale",
|
||||||
"session.new.worktree.mainWithBranch": "Branche principale ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Branche principale ({{branch}})",
|
||||||
"session.new.worktree.create": "Créer un nouvel arbre de travail",
|
"session.new.worktree.create": "Créer un nouvel arbre de travail",
|
||||||
|
"session.new.workspace.runIn": "Exécuter la session dans",
|
||||||
|
"session.new.workspace.triggerLocal": "Local",
|
||||||
|
"session.new.workspace.local": "Dépôt local",
|
||||||
|
"session.new.workspace.existing": "Espace de travail…",
|
||||||
"session.new.lastModified": "Dernière modification",
|
"session.new.lastModified": "Dernière modification",
|
||||||
"session.header.search.placeholder": "Rechercher {{project}}",
|
"session.header.search.placeholder": "Rechercher {{project}}",
|
||||||
"session.header.searchFiles": "Rechercher des fichiers",
|
"session.header.searchFiles": "Rechercher des fichiers",
|
||||||
@@ -564,6 +681,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "Sessions récentes",
|
"sidebar.project.recentSessions": "Sessions récentes",
|
||||||
"sidebar.project.viewAllSessions": "Voir toutes les sessions",
|
"sidebar.project.viewAllSessions": "Voir toutes les sessions",
|
||||||
"sidebar.project.clearNotifications": "Effacer les notifications",
|
"sidebar.project.clearNotifications": "Effacer les notifications",
|
||||||
|
"sidebar.empty.title": "Aucun projet ouvert",
|
||||||
|
"sidebar.empty.description": "Ouvrez un projet pour commencer",
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
"settings.section.desktop": "Bureau",
|
"settings.section.desktop": "Bureau",
|
||||||
"settings.section.server": "Serveur",
|
"settings.section.server": "Serveur",
|
||||||
@@ -573,6 +692,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.title": "Intégration WSL",
|
"settings.desktop.wsl.title": "Intégration WSL",
|
||||||
"settings.desktop.wsl.description": "Exécuter le serveur OpenCode dans WSL sur Windows.",
|
"settings.desktop.wsl.description": "Exécuter le serveur OpenCode dans WSL sur Windows.",
|
||||||
"settings.general.section.appearance": "Apparence",
|
"settings.general.section.appearance": "Apparence",
|
||||||
|
"settings.general.section.advanced": "Avancé",
|
||||||
"settings.general.section.notifications": "Notifications système",
|
"settings.general.section.notifications": "Notifications système",
|
||||||
"settings.general.section.updates": "Mises à jour",
|
"settings.general.section.updates": "Mises à jour",
|
||||||
"settings.general.section.sounds": "Effets sonores",
|
"settings.general.section.sounds": "Effets sonores",
|
||||||
@@ -580,6 +700,11 @@ export const dict = {
|
|||||||
"settings.general.section.display": "Affichage",
|
"settings.general.section.display": "Affichage",
|
||||||
"settings.general.row.language.title": "Langue",
|
"settings.general.row.language.title": "Langue",
|
||||||
"settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
|
"settings.general.row.language.description": "Changer la langue d'affichage pour OpenCode",
|
||||||
|
"settings.general.row.shell.title": "Shell du terminal",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"Choisissez le shell utilisé par votre terminal. Les shells compatibles sont également utilisés pour les appels d'outils de l'agent.",
|
||||||
|
"settings.general.row.shell.autoDefault": "Auto (par défaut)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "terminal uniquement",
|
||||||
"settings.general.row.appearance.title": "Apparence",
|
"settings.general.row.appearance.title": "Apparence",
|
||||||
"settings.general.row.appearance.description": "Personnaliser l'apparence d'OpenCode sur votre appareil",
|
"settings.general.row.appearance.description": "Personnaliser l'apparence d'OpenCode sur votre appareil",
|
||||||
"settings.general.row.colorScheme.title": "Schéma de couleurs",
|
"settings.general.row.colorScheme.title": "Schéma de couleurs",
|
||||||
@@ -588,8 +713,8 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
|
"settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
|
||||||
"settings.general.row.font.title": "Police de code",
|
"settings.general.row.font.title": "Police de code",
|
||||||
"settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code",
|
"settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "Police du terminal",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "Personnalisez la police utilisée dans le terminal",
|
||||||
"settings.general.row.uiFont.title": "Police de l'interface",
|
"settings.general.row.uiFont.title": "Police de l'interface",
|
||||||
"settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface",
|
"settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface",
|
||||||
"settings.general.row.followup.title": "Comportement de suivi",
|
"settings.general.row.followup.title": "Comportement de suivi",
|
||||||
@@ -597,6 +722,24 @@ export const dict = {
|
|||||||
"Choisissez si les messages de suivi dirigent immédiatement ou attendent dans une file d'attente",
|
"Choisissez si les messages de suivi dirigent immédiatement ou attendent dans une file d'attente",
|
||||||
"settings.general.row.followup.option.queue": "File d'attente",
|
"settings.general.row.followup.option.queue": "File d'attente",
|
||||||
"settings.general.row.followup.option.steer": "Diriger",
|
"settings.general.row.followup.option.steer": "Diriger",
|
||||||
|
"settings.general.row.showFileTree.title": "Arborescence des fichiers",
|
||||||
|
"settings.general.row.showFileTree.description": "Afficher le panneau d'arborescence des fichiers dans les sessions",
|
||||||
|
"settings.general.row.showNavigation.title": "Commandes de navigation",
|
||||||
|
"settings.general.row.showNavigation.description":
|
||||||
|
"Afficher les boutons précédent et suivant dans la barre de titre de l'application desktop",
|
||||||
|
"settings.general.row.showSearch.title": "Palette de commandes",
|
||||||
|
"settings.general.row.showSearch.description":
|
||||||
|
"Afficher le bouton de recherche et de palette de commandes dans la barre de titre",
|
||||||
|
"settings.general.row.showTerminal.title": "Terminal",
|
||||||
|
"settings.general.row.showTerminal.description":
|
||||||
|
"Afficher le bouton du terminal dans la barre de titre de l'application desktop",
|
||||||
|
"settings.general.row.showStatus.title": "État du serveur",
|
||||||
|
"settings.general.row.showStatus.description": "Afficher le bouton d'état du serveur dans la barre de titre",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Navigation en bas",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"Placer la barre de titre et les onglets de session en bas de l'écran sur mobile",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Agents personnalisés",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Afficher le sélecteur d'agent dans la zone de saisie",
|
||||||
"settings.general.row.reasoningSummaries.title": "Afficher les résumés de raisonnement",
|
"settings.general.row.reasoningSummaries.title": "Afficher les résumés de raisonnement",
|
||||||
"settings.general.row.reasoningSummaries.description":
|
"settings.general.row.reasoningSummaries.description":
|
||||||
"Afficher les résumés de raisonnement du modèle dans la chronologie",
|
"Afficher les résumés de raisonnement du modèle dans la chronologie",
|
||||||
@@ -606,6 +749,12 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit",
|
"settings.general.row.editToolPartsExpanded.title": "Développer les parties de l'outil edit",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie",
|
"Afficher les parties des outils edit, write et patch développées par défaut dans la chronologie",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Nouvelle mise en page et nouveaux designs",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Activer la nouvelle mise en page et les nouvelles interfaces d'accueil, de saisie et de session",
|
||||||
|
"settings.general.row.pinchZoom.title": "Pincer pour zoomer",
|
||||||
|
"settings.general.row.pinchZoom.description":
|
||||||
|
"Autoriser les gestes de pincement du pavé tactile et Ctrl-défilement pour zoomer",
|
||||||
"settings.general.row.wayland.title": "Utiliser Wayland natif",
|
"settings.general.row.wayland.title": "Utiliser Wayland natif",
|
||||||
"settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
|
"settings.general.row.wayland.description": "Désactiver le repli X11 sur Wayland. Nécessite un redémarrage.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
@@ -618,6 +767,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "Vérifier manuellement les mises à jour et installer si disponible",
|
"settings.updates.row.check.description": "Vérifier manuellement les mises à jour et installer si disponible",
|
||||||
"settings.updates.action.checkNow": "Vérifier maintenant",
|
"settings.updates.action.checkNow": "Vérifier maintenant",
|
||||||
"settings.updates.action.checking": "Vérification...",
|
"settings.updates.action.checking": "Vérification...",
|
||||||
|
"settings.updates.action.downloading": "Téléchargement...",
|
||||||
|
"settings.updates.action.installing": "Installation...",
|
||||||
"settings.updates.toast.latest.title": "Vous êtes à jour",
|
"settings.updates.toast.latest.title": "Vous êtes à jour",
|
||||||
"settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.",
|
"settings.updates.toast.latest.description": "Vous utilisez la dernière version d'OpenCode.",
|
||||||
"sound.option.none": "Aucun",
|
"sound.option.none": "Aucun",
|
||||||
|
|||||||
+151
-3
@@ -21,6 +21,9 @@ export const dict = {
|
|||||||
"theme.scheme.dark": "ダーク",
|
"theme.scheme.dark": "ダーク",
|
||||||
"command.sidebar.toggle": "サイドバーの切り替え",
|
"command.sidebar.toggle": "サイドバーの切り替え",
|
||||||
"command.project.open": "プロジェクトを開く",
|
"command.project.open": "プロジェクトを開く",
|
||||||
|
"command.project.previous": "前のプロジェクト",
|
||||||
|
"command.project.next": "次のプロジェクト",
|
||||||
|
"command.project.index": "プロジェクト{{index}}に切り替え",
|
||||||
"command.provider.connect": "プロバイダーに接続",
|
"command.provider.connect": "プロバイダーに接続",
|
||||||
"command.server.switch": "サーバーの切り替え",
|
"command.server.switch": "サーバーの切り替え",
|
||||||
"command.settings.open": "設定を開く",
|
"command.settings.open": "設定を開く",
|
||||||
@@ -39,6 +42,7 @@ export const dict = {
|
|||||||
"command.session.new": "新しいセッション",
|
"command.session.new": "新しいセッション",
|
||||||
"command.file.open": "ファイルを開く",
|
"command.file.open": "ファイルを開く",
|
||||||
"command.tab.close": "タブを閉じる",
|
"command.tab.close": "タブを閉じる",
|
||||||
|
"command.tab.reopenClosed": "閉じたタブを再度開く",
|
||||||
"command.context.addSelection": "選択範囲をコンテキストに追加",
|
"command.context.addSelection": "選択範囲をコンテキストに追加",
|
||||||
"command.context.addSelection.description": "現在のファイルから選択した行を追加",
|
"command.context.addSelection.description": "現在のファイルから選択した行を追加",
|
||||||
"command.input.focus": "入力欄にフォーカス",
|
"command.input.focus": "入力欄にフォーカス",
|
||||||
@@ -149,7 +153,7 @@ export const dict = {
|
|||||||
"provider.custom.field.providerID.placeholder": "myprovider",
|
"provider.custom.field.providerID.placeholder": "myprovider",
|
||||||
"provider.custom.field.providerID.description": "小文字、数字、ハイフン、アンダースコア",
|
"provider.custom.field.providerID.description": "小文字、数字、ハイフン、アンダースコア",
|
||||||
"provider.custom.field.name.label": "表示名",
|
"provider.custom.field.name.label": "表示名",
|
||||||
"provider.custom.field.name.placeholder": "My AI Provider",
|
"provider.custom.field.name.placeholder": "マイAIプロバイダー",
|
||||||
"provider.custom.field.baseURL.label": "ベースURL",
|
"provider.custom.field.baseURL.label": "ベースURL",
|
||||||
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
|
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
|
||||||
"provider.custom.field.apiKey.label": "APIキー",
|
"provider.custom.field.apiKey.label": "APIキー",
|
||||||
@@ -192,6 +196,11 @@ export const dict = {
|
|||||||
"model.input.video": "動画",
|
"model.input.video": "動画",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "対応: {{inputs}}",
|
"model.tooltip.allows": "対応: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "コンテキスト",
|
||||||
|
"model.tooltip.inputs": "入力",
|
||||||
|
"model.tooltip.model": "モデル",
|
||||||
|
"model.tooltip.provider": "プロバイダー",
|
||||||
|
"model.tooltip.reasoning": "推論",
|
||||||
"model.tooltip.reasoning.allowed": "推論を許可",
|
"model.tooltip.reasoning.allowed": "推論を許可",
|
||||||
"model.tooltip.reasoning.none": "推論なし",
|
"model.tooltip.reasoning.none": "推論なし",
|
||||||
"model.tooltip.context": "コンテキスト上限 {{limit}}",
|
"model.tooltip.context": "コンテキスト上限 {{limit}}",
|
||||||
@@ -201,6 +210,7 @@ export const dict = {
|
|||||||
"common.loading": "読み込み中",
|
"common.loading": "読み込み中",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "キャンセル",
|
"common.cancel": "キャンセル",
|
||||||
|
"common.clear": "クリア",
|
||||||
"common.connect": "接続",
|
"common.connect": "接続",
|
||||||
"common.disconnect": "切断",
|
"common.disconnect": "切断",
|
||||||
"common.continue": "送信",
|
"common.continue": "送信",
|
||||||
@@ -217,6 +227,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "シェル",
|
"prompt.mode.shell": "シェル",
|
||||||
"prompt.mode.normal": "プロンプト",
|
"prompt.mode.normal": "プロンプト",
|
||||||
"prompt.mode.shell.exit": "escで終了",
|
"prompt.mode.shell.exit": "escで終了",
|
||||||
|
"session.child.promptDisabled": "サブエージェントのセッションにはプロンプトを送信できません。",
|
||||||
|
"session.child.backToParent": "メインセッションに戻る。",
|
||||||
"prompt.example.1": "コードベースのTODOを修正",
|
"prompt.example.1": "コードベースのTODOを修正",
|
||||||
"prompt.example.2": "このプロジェクトの技術スタックは何ですか?",
|
"prompt.example.2": "このプロジェクトの技術スタックは何ですか?",
|
||||||
"prompt.example.3": "壊れたテストを修正",
|
"prompt.example.3": "壊れたテストを修正",
|
||||||
@@ -313,6 +325,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "削除",
|
"dialog.server.menu.delete": "削除",
|
||||||
"dialog.server.current": "現在のサーバー",
|
"dialog.server.current": "現在のサーバー",
|
||||||
"dialog.server.status.default": "デフォルト",
|
"dialog.server.status.default": "デフォルト",
|
||||||
|
"wsl.server.add": "WSLサーバーを追加",
|
||||||
|
"wsl.server.addShort": "WSLを追加",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "WSLサーバー",
|
||||||
|
"wsl.server.retryStart": "起動を再試行",
|
||||||
|
"wsl.server.updating": "更新中...",
|
||||||
|
"wsl.onboarding.step.distro": "ディストリビューションを選択",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "WSLを確認中...",
|
||||||
|
"wsl.onboarding.restartRequired": "WSLのインストールを完了するにはWindowsの再起動が必要です。",
|
||||||
|
"wsl.onboarding.ready": "WSLの準備ができました。",
|
||||||
|
"wsl.onboarding.required": "続行するにはWSLが必要です。",
|
||||||
|
"wsl.onboarding.checkingDistros": "ディストリビューションを確認中...",
|
||||||
|
"wsl.onboarding.installingDistro": "{{distro}}をインストール中...",
|
||||||
|
"wsl.onboarding.checkingDistro": "{{distro}}を確認中...",
|
||||||
|
"wsl.onboarding.listingDistros": "ディストリビューションの一覧を取得中...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}}の準備ができました。",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "{{distro}}はまだインストールされていません。",
|
||||||
|
"wsl.onboarding.openDistroOnce": "セットアップを完了するには、{{distro}}を一度開いてください。",
|
||||||
|
"wsl.onboarding.finishingDistro": "{{distro}}のセットアップを完了しています。",
|
||||||
|
"wsl.onboarding.pickDistro": "ディストリビューションを選択するか、以下からインストールしてください。",
|
||||||
|
"wsl.onboarding.checkingOpencode": "OpenCodeを確認中...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "{{distro}}内のOpenCodeを確認中...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "OpenCodeを更新中...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "{{distro}}内のOpenCodeを更新中...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "{{distro}}内のOpenCodeを更新してください。",
|
||||||
|
"wsl.onboarding.updateOpencode": "OpenCodeを更新",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "{{distro}}内のOpenCodeの準備ができました。",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCodeの準備ができました。",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "{{distro}}にOpenCodeをインストールしてください。",
|
||||||
|
"wsl.onboarding.installOpencode": "OpenCodeをインストール",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "先にディストリビューションを選択してください。",
|
||||||
|
"wsl.onboarding.loadFailed": "WSLの状態を読み込めませんでした。",
|
||||||
|
"wsl.onboarding.loading": "読み込み中...",
|
||||||
|
"wsl.onboarding.installedDistros": "インストール済みのディストリビューション",
|
||||||
|
"wsl.onboarding.checkAgain": "再確認",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "準備完了",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "確認中...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCodeがインストールされていません",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "bash、curlがありません",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "非対応 · WSL 2を使用してください",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "別のディストリビューションが必要ですか?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "WSLカタログからLinuxディストリビューションをインストール",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSLがインストールされていません",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"OpenCodeでWSLサーバーを追加するには、WSL (Windows Subsystem for Linux) が必要です",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSLを利用できません",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "このマシンのWSLを確認できませんでした。",
|
||||||
|
"wsl.onboarding.installWsl": "WSLをインストール",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"WSLのインストールを完了するにはWindowsを再起動し、OpenCodeをもう一度開いてください。",
|
||||||
|
"wsl.onboarding.next": "次へ",
|
||||||
|
"wsl.onboarding.refresh": "更新",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "インストール済みのディストリビューションはすべて追加済みです。",
|
||||||
|
"wsl.onboarding.noDistros": "ディストリビューションはまだ検出されていません。",
|
||||||
|
"wsl.onboarding.install": "インストール",
|
||||||
|
"wsl.onboarding.installing": "インストール中...",
|
||||||
|
"wsl.onboarding.installDistro": "ディストリビューションをインストール",
|
||||||
|
"wsl.onboarding.searchDistros": "ディストリビューションを検索",
|
||||||
|
"wsl.onboarding.wsl2Required": "WSL 2が必要です。",
|
||||||
|
"wsl.onboarding.toolsRequired": "このディストリビューションにはbashとcurlが必要です。",
|
||||||
|
"wsl.onboarding.openTerminal": "ターミナルを開く",
|
||||||
|
"wsl.onboarding.path": "パス: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "見つかりません",
|
||||||
|
"wsl.onboarding.version": "バージョン: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "不明",
|
||||||
|
"wsl.onboarding.desktopVersion": "デスクトップ {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "インストールされているバージョンがデスクトップアプリのバージョンと一致しません。",
|
||||||
|
"wsl.onboarding.adding": "追加中...",
|
||||||
"dialog.project.edit.title": "プロジェクトを編集",
|
"dialog.project.edit.title": "プロジェクトを編集",
|
||||||
"dialog.project.edit.name": "名前",
|
"dialog.project.edit.name": "名前",
|
||||||
"dialog.project.edit.icon": "アイコン",
|
"dialog.project.edit.icon": "アイコン",
|
||||||
@@ -370,6 +451,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
"toast.language.title": "言語",
|
"toast.language.title": "言語",
|
||||||
@@ -406,10 +488,12 @@ export const dict = {
|
|||||||
"toast.update.action.notYet": "今はしない",
|
"toast.update.action.notYet": "今はしない",
|
||||||
"error.page.title": "問題が発生しました",
|
"error.page.title": "問題が発生しました",
|
||||||
"error.page.description": "アプリケーションの読み込み中にエラーが発生しました。",
|
"error.page.description": "アプリケーションの読み込み中にエラーが発生しました。",
|
||||||
|
"error.page.description.localServerStartup": "ローカルサーバーの起動中にエラーが発生しました。",
|
||||||
"error.page.details.label": "エラー詳細",
|
"error.page.details.label": "エラー詳細",
|
||||||
"error.page.action.restart": "再起動",
|
"error.page.action.restart": "再起動",
|
||||||
"error.page.action.report": "エラーを報告",
|
"error.page.action.report": "エラーを報告",
|
||||||
"error.page.action.reported": "エラーを報告しました",
|
"error.page.action.reported": "エラーを報告しました",
|
||||||
|
"error.page.action.exportLogs": "ログをエクスポート",
|
||||||
"error.page.action.checking": "確認中...",
|
"error.page.action.checking": "確認中...",
|
||||||
"error.page.action.checkUpdates": "アップデートを確認",
|
"error.page.action.checkUpdates": "アップデートを確認",
|
||||||
"error.page.action.updateTo": "{{version}}にアップデート",
|
"error.page.action.updateTo": "{{version}}にアップデート",
|
||||||
@@ -451,10 +535,30 @@ export const dict = {
|
|||||||
"home.recentProjects": "最近のプロジェクト",
|
"home.recentProjects": "最近のプロジェクト",
|
||||||
"home.empty.title": "最近のプロジェクトはありません",
|
"home.empty.title": "最近のプロジェクトはありません",
|
||||||
"home.empty.description": "ローカルプロジェクトを開いて始めましょう",
|
"home.empty.description": "ローカルプロジェクトを開いて始めましょう",
|
||||||
|
"home.title": "ホーム",
|
||||||
|
"home.projects": "プロジェクト",
|
||||||
|
"home.project.add": "プロジェクトを追加",
|
||||||
|
"home.recentlyClosed": "最近閉じたプロジェクト",
|
||||||
|
"home.server.collapse": "サーバーのプロジェクトを折りたたむ",
|
||||||
|
"home.server.expand": "サーバーのプロジェクトを展開",
|
||||||
|
"home.sessions.search.placeholder": "セッションを検索",
|
||||||
|
"home.sessions.search.placeholder.scoped": "{{scope}}のセッションを検索",
|
||||||
|
"home.sessions.search.sessions": "セッション",
|
||||||
|
"home.sessions.search.noResults": "{{query}}に一致するセッションが見つかりません",
|
||||||
|
"home.sessions.empty": "まだ何もありません",
|
||||||
|
"home.sessions.empty.description": "セッションを作成して始めましょう",
|
||||||
|
"home.sessions.group.today": "今日",
|
||||||
|
"home.sessions.group.yesterday": "昨日",
|
||||||
|
"home.sessions.group.older": "それ以前",
|
||||||
"session.tab.session": "セッション",
|
"session.tab.session": "セッション",
|
||||||
"session.tab.review": "レビュー",
|
"session.tab.review": "レビュー",
|
||||||
"session.tab.context": "コンテキスト",
|
"session.tab.context": "コンテキスト",
|
||||||
|
"session.tab.unknown": "不明なセッション",
|
||||||
"session.panel.reviewAndFiles": "レビューとファイル",
|
"session.panel.reviewAndFiles": "レビューとファイル",
|
||||||
|
"session.error.notFound": "このセッションが見つかりません",
|
||||||
|
"session.error.notFound.description": "このタブが参照するセッションは、このサーバーには存在しません。",
|
||||||
|
"session.error.notFound.closeTab": "タブを閉じる",
|
||||||
|
"session.error.serverConnection": "このサーバーに接続できません",
|
||||||
"session.review.filesChanged": "{{count}} ファイル変更",
|
"session.review.filesChanged": "{{count}} ファイル変更",
|
||||||
"session.review.change.one": "変更",
|
"session.review.change.one": "変更",
|
||||||
"session.review.change.other": "変更",
|
"session.review.change.other": "変更",
|
||||||
@@ -463,6 +567,8 @@ export const dict = {
|
|||||||
"session.review.noVcs": "Gitバージョン管理システムが検出されないため、変更は表示されません",
|
"session.review.noVcs": "Gitバージョン管理システムが検出されないため、変更は表示されません",
|
||||||
"session.review.noSnapshot": "設定でスナップショット追跡が無効になっているため、セッションの変更は利用できません",
|
"session.review.noSnapshot": "設定でスナップショット追跡が無効になっているため、セッションの変更は利用できません",
|
||||||
"session.review.noChanges": "変更なし",
|
"session.review.noChanges": "変更なし",
|
||||||
|
"session.review.noUncommittedChanges": "コミットされていない変更はまだありません",
|
||||||
|
"session.review.noBranchChanges": "ブランチの変更はまだありません",
|
||||||
"session.files.selectToOpen": "開くファイルを選択",
|
"session.files.selectToOpen": "開くファイルを選択",
|
||||||
"session.files.all": "すべてのファイル",
|
"session.files.all": "すべてのファイル",
|
||||||
"session.files.empty": "ファイルなし",
|
"session.files.empty": "ファイルなし",
|
||||||
@@ -476,6 +582,10 @@ export const dict = {
|
|||||||
"session.todo.title": "ToDo",
|
"session.todo.title": "ToDo",
|
||||||
"session.todo.collapse": "折りたたむ",
|
"session.todo.collapse": "折りたたむ",
|
||||||
"session.todo.expand": "展開",
|
"session.todo.expand": "展開",
|
||||||
|
"session.question.minimize": "質問を最小化",
|
||||||
|
"session.question.restore": "質問を復元",
|
||||||
|
"session.question.pending.one": "{{count}}件の保留中の質問",
|
||||||
|
"session.question.pending.other": "{{count}}件の保留中の質問",
|
||||||
"session.followupDock.summary.one": "{{count}} 件のメッセージが待機中",
|
"session.followupDock.summary.one": "{{count}} 件のメッセージが待機中",
|
||||||
"session.followupDock.summary.other": "{{count}} 件のメッセージが待機中",
|
"session.followupDock.summary.other": "{{count}} 件のメッセージが待機中",
|
||||||
"session.followupDock.sendNow": "今すぐ送信",
|
"session.followupDock.sendNow": "今すぐ送信",
|
||||||
@@ -488,9 +598,16 @@ export const dict = {
|
|||||||
"session.revertDock.expand": "ロールバックされたメッセージを展開",
|
"session.revertDock.expand": "ロールバックされたメッセージを展開",
|
||||||
"session.revertDock.restore": "メッセージを復元",
|
"session.revertDock.restore": "メッセージを復元",
|
||||||
"session.new.title": "何でも作る",
|
"session.new.title": "何でも作る",
|
||||||
|
"session.new.project.new": "新しいプロジェクト",
|
||||||
|
"session.new.project.search": "プロジェクトを検索",
|
||||||
|
"session.new.project.add": "プロジェクトを追加",
|
||||||
"session.new.worktree.main": "メインブランチ",
|
"session.new.worktree.main": "メインブランチ",
|
||||||
"session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
|
"session.new.worktree.mainWithBranch": "メインブランチ ({{branch}})",
|
||||||
"session.new.worktree.create": "新しいワークツリーを作成",
|
"session.new.worktree.create": "新しいワークツリーを作成",
|
||||||
|
"session.new.workspace.runIn": "セッションの実行先",
|
||||||
|
"session.new.workspace.triggerLocal": "ローカル",
|
||||||
|
"session.new.workspace.local": "ローカルリポジトリ",
|
||||||
|
"session.new.workspace.existing": "ワークスペース…",
|
||||||
"session.new.lastModified": "最終更新",
|
"session.new.lastModified": "最終更新",
|
||||||
"session.header.search.placeholder": "{{project}}を検索",
|
"session.header.search.placeholder": "{{project}}を検索",
|
||||||
"session.header.searchFiles": "ファイルを検索",
|
"session.header.searchFiles": "ファイルを検索",
|
||||||
@@ -554,6 +671,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "最近のセッション",
|
"sidebar.project.recentSessions": "最近のセッション",
|
||||||
"sidebar.project.viewAllSessions": "すべてのセッションを表示",
|
"sidebar.project.viewAllSessions": "すべてのセッションを表示",
|
||||||
"sidebar.project.clearNotifications": "通知をクリア",
|
"sidebar.project.clearNotifications": "通知をクリア",
|
||||||
|
"sidebar.empty.title": "開いているプロジェクトはありません",
|
||||||
|
"sidebar.empty.description": "プロジェクトを開いて始めましょう",
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
"settings.section.desktop": "デスクトップ",
|
"settings.section.desktop": "デスクトップ",
|
||||||
"settings.section.server": "サーバー",
|
"settings.section.server": "サーバー",
|
||||||
@@ -563,6 +682,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.title": "WSL連携",
|
"settings.desktop.wsl.title": "WSL連携",
|
||||||
"settings.desktop.wsl.description": "WindowsのWSL環境でOpenCodeサーバーを実行します。",
|
"settings.desktop.wsl.description": "WindowsのWSL環境でOpenCodeサーバーを実行します。",
|
||||||
"settings.general.section.appearance": "外観",
|
"settings.general.section.appearance": "外観",
|
||||||
|
"settings.general.section.advanced": "詳細設定",
|
||||||
"settings.general.section.notifications": "システム通知",
|
"settings.general.section.notifications": "システム通知",
|
||||||
"settings.general.section.updates": "アップデート",
|
"settings.general.section.updates": "アップデート",
|
||||||
"settings.general.section.sounds": "効果音",
|
"settings.general.section.sounds": "効果音",
|
||||||
@@ -570,6 +690,11 @@ export const dict = {
|
|||||||
"settings.general.section.display": "ディスプレイ",
|
"settings.general.section.display": "ディスプレイ",
|
||||||
"settings.general.row.language.title": "言語",
|
"settings.general.row.language.title": "言語",
|
||||||
"settings.general.row.language.description": "OpenCodeの表示言語を変更します",
|
"settings.general.row.language.description": "OpenCodeの表示言語を変更します",
|
||||||
|
"settings.general.row.shell.title": "ターミナルシェル",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"ターミナルで使用するシェルを選択します。互換性のあるシェルはエージェントのツール呼び出しにも使用されます。",
|
||||||
|
"settings.general.row.shell.autoDefault": "自動 (デフォルト)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "ターミナルのみ",
|
||||||
"settings.general.row.appearance.title": "外観",
|
"settings.general.row.appearance.title": "外観",
|
||||||
"settings.general.row.appearance.description": "デバイスでのOpenCodeの表示をカスタマイズします",
|
"settings.general.row.appearance.description": "デバイスでのOpenCodeの表示をカスタマイズします",
|
||||||
"settings.general.row.colorScheme.title": "配色",
|
"settings.general.row.colorScheme.title": "配色",
|
||||||
@@ -578,8 +703,8 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
|
"settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
|
||||||
"settings.general.row.font.title": "コードフォント",
|
"settings.general.row.font.title": "コードフォント",
|
||||||
"settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします",
|
"settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "ターミナルのフォント",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "ターミナルで使用するフォントをカスタマイズ",
|
||||||
"settings.general.row.uiFont.title": "UIフォント",
|
"settings.general.row.uiFont.title": "UIフォント",
|
||||||
"settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします",
|
"settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします",
|
||||||
"settings.general.row.followup.title": "フォローアップの動作",
|
"settings.general.row.followup.title": "フォローアップの動作",
|
||||||
@@ -587,6 +712,21 @@ export const dict = {
|
|||||||
"フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します",
|
"フォローアッププロンプトを即座に実行するか、キューで待機させるかを選択します",
|
||||||
"settings.general.row.followup.option.queue": "キューに追加",
|
"settings.general.row.followup.option.queue": "キューに追加",
|
||||||
"settings.general.row.followup.option.steer": "即座に実行 (Steer)",
|
"settings.general.row.followup.option.steer": "即座に実行 (Steer)",
|
||||||
|
"settings.general.row.showFileTree.title": "ファイルツリー",
|
||||||
|
"settings.general.row.showFileTree.description": "セッションにファイルツリーパネルを表示します",
|
||||||
|
"settings.general.row.showNavigation.title": "ナビゲーションコントロール",
|
||||||
|
"settings.general.row.showNavigation.description": "デスクトップのタイトルバーに戻るボタンと進むボタンを表示します",
|
||||||
|
"settings.general.row.showSearch.title": "コマンドパレット",
|
||||||
|
"settings.general.row.showSearch.description": "タイトルバーに検索とコマンドパレットのボタンを表示します",
|
||||||
|
"settings.general.row.showTerminal.title": "ターミナル",
|
||||||
|
"settings.general.row.showTerminal.description": "デスクトップのタイトルバーにターミナルボタンを表示します",
|
||||||
|
"settings.general.row.showStatus.title": "サーバーステータス",
|
||||||
|
"settings.general.row.showStatus.description": "タイトルバーにサーバーステータスボタンを表示します",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "下部ナビゲーション",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"モバイルではタイトルバーとセッションタブを画面下部に配置します",
|
||||||
|
"settings.general.row.showCustomAgents.title": "カスタムエージェント",
|
||||||
|
"settings.general.row.showCustomAgents.description": "コンポーザーにエージェント選択を表示します",
|
||||||
"settings.general.row.reasoningSummaries.title": "推論の要約を表示",
|
"settings.general.row.reasoningSummaries.title": "推論の要約を表示",
|
||||||
"settings.general.row.reasoningSummaries.description": "タイムラインにモデルの推論の要約を表示します",
|
"settings.general.row.reasoningSummaries.description": "タイムラインにモデルの推論の要約を表示します",
|
||||||
"settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開",
|
"settings.general.row.shellToolPartsExpanded.title": "shell ツールパーツを展開",
|
||||||
@@ -595,6 +735,12 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開",
|
"settings.general.row.editToolPartsExpanded.title": "edit ツールパーツを展開",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します",
|
"タイムラインで edit、write、patch ツールパーツをデフォルトで展開して表示します",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "新しいレイアウトとデザイン",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"刷新されたレイアウト、ホーム、コンポーザー、セッションUIを有効にします",
|
||||||
|
"settings.general.row.pinchZoom.title": "ピンチでズーム",
|
||||||
|
"settings.general.row.pinchZoom.description":
|
||||||
|
"トラックパッドのピンチ操作とCtrl+スクロール操作によるズームを許可します",
|
||||||
"settings.general.row.wayland.title": "ネイティブWaylandを使用",
|
"settings.general.row.wayland.title": "ネイティブWaylandを使用",
|
||||||
"settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
|
"settings.general.row.wayland.description": "WaylandでのX11フォールバックを無効にします。再起動が必要です。",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
@@ -607,6 +753,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "手動でアップデートを確認し、利用可能ならインストールします",
|
"settings.updates.row.check.description": "手動でアップデートを確認し、利用可能ならインストールします",
|
||||||
"settings.updates.action.checkNow": "今すぐ確認",
|
"settings.updates.action.checkNow": "今すぐ確認",
|
||||||
"settings.updates.action.checking": "確認中...",
|
"settings.updates.action.checking": "確認中...",
|
||||||
|
"settings.updates.action.downloading": "ダウンロード中...",
|
||||||
|
"settings.updates.action.installing": "インストール中...",
|
||||||
"settings.updates.toast.latest.title": "最新です",
|
"settings.updates.toast.latest.title": "最新です",
|
||||||
"settings.updates.toast.latest.description": "OpenCode は最新バージョンです。",
|
"settings.updates.toast.latest.description": "OpenCode は最新バージョンです。",
|
||||||
"sound.option.none": "なし",
|
"sound.option.none": "なし",
|
||||||
|
|||||||
+153
-2
@@ -575,8 +575,8 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
|
"settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
|
||||||
"settings.general.row.font.title": "코드 글꼴",
|
"settings.general.row.font.title": "코드 글꼴",
|
||||||
"settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정",
|
"settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "터미널 글꼴",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "터미널에서 사용할 글꼴을 설정합니다",
|
||||||
"settings.general.row.uiFont.title": "UI 글꼴",
|
"settings.general.row.uiFont.title": "UI 글꼴",
|
||||||
"settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정",
|
"settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정",
|
||||||
"settings.general.row.followup.title": "후속 조치 동작",
|
"settings.general.row.followup.title": "후속 조치 동작",
|
||||||
@@ -848,4 +848,155 @@ export const dict = {
|
|||||||
"error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패",
|
"error.childStore.persistedProjectIconCreateFailed": "영구 프로젝트 아이콘 생성 실패",
|
||||||
"error.childStore.storeCreateFailed": "저장소 생성 실패",
|
"error.childStore.storeCreateFailed": "저장소 생성 실패",
|
||||||
"terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}",
|
"terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}",
|
||||||
|
|
||||||
|
"command.project.previous": "이전 프로젝트",
|
||||||
|
"command.project.next": "다음 프로젝트",
|
||||||
|
"command.project.index": "{{index}}번 프로젝트로 전환",
|
||||||
|
"command.tab.reopenClosed": "닫은 탭 다시 열기",
|
||||||
|
"model.tooltip.context.label": "컨텍스트",
|
||||||
|
"model.tooltip.inputs": "입력",
|
||||||
|
"model.tooltip.model": "모델",
|
||||||
|
"model.tooltip.provider": "공급자",
|
||||||
|
"model.tooltip.reasoning": "추론",
|
||||||
|
"common.clear": "지우기",
|
||||||
|
"session.child.promptDisabled": "하위 에이전트 세션에는 프롬프트를 입력할 수 없습니다.",
|
||||||
|
"session.child.backToParent": "메인 세션으로 돌아가기.",
|
||||||
|
|
||||||
|
"wsl.server.add": "WSL 서버 추가",
|
||||||
|
"wsl.server.addShort": "WSL 추가",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "WSL 서버",
|
||||||
|
"wsl.server.retryStart": "시작 재시도",
|
||||||
|
"wsl.server.updating": "업데이트 중...",
|
||||||
|
"wsl.onboarding.step.distro": "배포판 선택",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "WSL 확인 중...",
|
||||||
|
"wsl.onboarding.restartRequired": "WSL 설치를 완료하려면 Windows를 다시 시작해야 합니다.",
|
||||||
|
"wsl.onboarding.ready": "WSL이 준비되었습니다.",
|
||||||
|
"wsl.onboarding.required": "계속하려면 WSL이 필요합니다.",
|
||||||
|
"wsl.onboarding.checkingDistros": "배포판 확인 중...",
|
||||||
|
"wsl.onboarding.installingDistro": "{{distro}} 배포판 설치 중...",
|
||||||
|
"wsl.onboarding.checkingDistro": "{{distro}} 배포판 확인 중...",
|
||||||
|
"wsl.onboarding.listingDistros": "배포판 목록 확인 중...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} 배포판이 준비되었습니다.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "{{distro}} 배포판이 아직 설치되지 않았습니다.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "설정을 완료하려면 {{distro}} 배포판을 한 번 여세요.",
|
||||||
|
"wsl.onboarding.finishingDistro": "{{distro}} 배포판 설정을 마무리하는 중입니다.",
|
||||||
|
"wsl.onboarding.pickDistro": "배포판을 선택하거나 아래에서 설치하세요.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "OpenCode 확인 중...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "{{distro}}에서 OpenCode 확인 중...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "OpenCode 업데이트 중...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "{{distro}}에서 OpenCode 업데이트 중...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "{{distro}}에서 OpenCode를 업데이트하세요.",
|
||||||
|
"wsl.onboarding.updateOpencode": "OpenCode 업데이트",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "{{distro}}에서 OpenCode를 사용할 준비가 되었습니다.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode가 준비되었습니다.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "{{distro}}에 OpenCode를 설치하세요.",
|
||||||
|
"wsl.onboarding.installOpencode": "OpenCode 설치",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "먼저 배포판을 선택하세요.",
|
||||||
|
"wsl.onboarding.loadFailed": "WSL 상태를 불러오지 못했습니다.",
|
||||||
|
"wsl.onboarding.loading": "로딩 중...",
|
||||||
|
"wsl.onboarding.installedDistros": "설치된 배포판",
|
||||||
|
"wsl.onboarding.checkAgain": "다시 확인",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "준비됨",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "확인 중...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode가 설치되지 않음",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "bash, curl 없음",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "지원되지 않음 · WSL 2 사용",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "다른 배포판이 필요하신가요?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "WSL 카탈로그에서 Linux 배포판을 설치하세요",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL이 설치되지 않음",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"OpenCode에서 WSL 서버를 추가하려면 WSL (Windows Subsystem for Linux)이 필요합니다",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL을 사용할 수 없음",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode에서 이 컴퓨터의 WSL을 확인할 수 없습니다.",
|
||||||
|
"wsl.onboarding.installWsl": "WSL 설치",
|
||||||
|
"wsl.onboarding.windowsRestartRequired": "WSL 설치를 완료하려면 Windows를 다시 시작한 다음 OpenCode를 다시 여세요.",
|
||||||
|
"wsl.onboarding.next": "다음",
|
||||||
|
"wsl.onboarding.refresh": "새로 고침",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "설치된 모든 배포판이 이미 추가되었습니다.",
|
||||||
|
"wsl.onboarding.noDistros": "아직 감지된 배포판이 없습니다.",
|
||||||
|
"wsl.onboarding.install": "설치",
|
||||||
|
"wsl.onboarding.installing": "설치 중...",
|
||||||
|
"wsl.onboarding.installDistro": "배포판 설치",
|
||||||
|
"wsl.onboarding.searchDistros": "배포판 검색",
|
||||||
|
"wsl.onboarding.wsl2Required": "WSL 2가 필요합니다.",
|
||||||
|
"wsl.onboarding.toolsRequired": "이 배포판에는 bash와 curl이 필요합니다.",
|
||||||
|
"wsl.onboarding.openTerminal": "터미널 열기",
|
||||||
|
"wsl.onboarding.path": "경로: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "찾을 수 없음",
|
||||||
|
"wsl.onboarding.version": "버전: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "알 수 없음",
|
||||||
|
"wsl.onboarding.desktopVersion": "데스크톱 {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "설치된 버전이 데스크톱 앱 버전과 일치하지 않습니다.",
|
||||||
|
"wsl.onboarding.adding": "추가 중...",
|
||||||
|
|
||||||
|
"language.uk": "Українська",
|
||||||
|
"error.page.description.localServerStartup": "로컬 서버를 시작하는 동안 오류가 발생했습니다.",
|
||||||
|
"error.page.action.exportLogs": "로그 내보내기",
|
||||||
|
|
||||||
|
"home.title": "홈",
|
||||||
|
"home.projects": "프로젝트",
|
||||||
|
"home.project.add": "프로젝트 추가",
|
||||||
|
"home.recentlyClosed": "최근에 닫은 항목",
|
||||||
|
"home.server.collapse": "서버 프로젝트 접기",
|
||||||
|
"home.server.expand": "서버 프로젝트 펼치기",
|
||||||
|
"home.sessions.search.placeholder": "세션 검색",
|
||||||
|
"home.sessions.search.placeholder.scoped": "{{scope}}에서 세션 검색",
|
||||||
|
"home.sessions.search.sessions": "세션",
|
||||||
|
"home.sessions.search.noResults": "{{query}}에 해당하는 세션을 찾을 수 없습니다",
|
||||||
|
"home.sessions.empty": "아직 아무것도 없습니다",
|
||||||
|
"home.sessions.empty.description": "세션을 생성하여 시작하세요",
|
||||||
|
"home.sessions.group.today": "오늘",
|
||||||
|
"home.sessions.group.yesterday": "어제",
|
||||||
|
"home.sessions.group.older": "이전",
|
||||||
|
|
||||||
|
"session.tab.unknown": "알 수 없는 세션",
|
||||||
|
"session.error.notFound": "이 세션을 찾을 수 없습니다",
|
||||||
|
"session.error.notFound.description": "이 탭은 이 서버에 더 이상 존재하지 않는 세션을 가리킵니다.",
|
||||||
|
"session.error.notFound.closeTab": "탭 닫기",
|
||||||
|
"session.error.serverConnection": "이 서버에 연결할 수 없습니다",
|
||||||
|
"session.review.noUncommittedChanges": "아직 커밋되지 않은 변경 사항이 없습니다",
|
||||||
|
"session.review.noBranchChanges": "아직 브랜치 변경 사항이 없습니다",
|
||||||
|
"session.question.minimize": "질문 최소화",
|
||||||
|
"session.question.restore": "질문 복원",
|
||||||
|
"session.question.pending.one": "{{count}}개의 대기 중인 질문",
|
||||||
|
"session.question.pending.other": "{{count}}개의 대기 중인 질문",
|
||||||
|
"session.new.project.new": "새 프로젝트",
|
||||||
|
"session.new.project.search": "프로젝트 검색",
|
||||||
|
"session.new.project.add": "프로젝트 추가",
|
||||||
|
"session.new.workspace.runIn": "세션 실행 위치",
|
||||||
|
"session.new.workspace.triggerLocal": "로컬",
|
||||||
|
"session.new.workspace.local": "로컬 저장소",
|
||||||
|
"session.new.workspace.existing": "작업 공간…",
|
||||||
|
|
||||||
|
"sidebar.empty.title": "열린 프로젝트 없음",
|
||||||
|
"sidebar.empty.description": "프로젝트를 열어 시작하세요",
|
||||||
|
|
||||||
|
"settings.general.section.advanced": "고급",
|
||||||
|
"settings.general.row.shell.title": "터미널 셸",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"터미널에서 사용할 셸을 선택하세요. 호환되는 셸은 에이전트 도구 호출에도 사용됩니다.",
|
||||||
|
"settings.general.row.shell.autoDefault": "자동 (기본값)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "터미널 전용",
|
||||||
|
"settings.general.row.showFileTree.title": "파일 트리",
|
||||||
|
"settings.general.row.showFileTree.description": "세션에 파일 트리 패널 표시",
|
||||||
|
"settings.general.row.showNavigation.title": "탐색 컨트롤",
|
||||||
|
"settings.general.row.showNavigation.description": "데스크톱 제목 표시줄에 뒤로 및 앞으로 버튼 표시",
|
||||||
|
"settings.general.row.showSearch.title": "명령 팔레트",
|
||||||
|
"settings.general.row.showSearch.description": "제목 표시줄에 검색 및 명령 팔레트 버튼 표시",
|
||||||
|
"settings.general.row.showTerminal.title": "터미널",
|
||||||
|
"settings.general.row.showTerminal.description": "데스크톱 제목 표시줄에 터미널 버튼 표시",
|
||||||
|
"settings.general.row.showStatus.title": "서버 상태",
|
||||||
|
"settings.general.row.showStatus.description": "제목 표시줄에 서버 상태 버튼 표시",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "하단 탐색",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description": "모바일에서 제목 표시줄과 세션 탭을 화면 하단에 배치",
|
||||||
|
"settings.general.row.showCustomAgents.title": "사용자 지정 에이전트",
|
||||||
|
"settings.general.row.showCustomAgents.description": "입력창에 에이전트 선택기 표시",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "새 레이아웃 및 디자인",
|
||||||
|
"settings.general.row.newLayoutDesigns.description": "새롭게 디자인된 레이아웃, 홈, 입력창 및 세션 UI 활성화",
|
||||||
|
"settings.general.row.pinchZoom.title": "핀치 줌",
|
||||||
|
"settings.general.row.pinchZoom.description": "트랙패드 핀치 및 Ctrl-스크롤 제스처로 확대/축소 허용",
|
||||||
|
"settings.updates.action.downloading": "다운로드 중...",
|
||||||
|
"settings.updates.action.installing": "설치 중...",
|
||||||
}
|
}
|
||||||
|
|||||||
+163
-4
@@ -77,7 +77,7 @@ export const dict = {
|
|||||||
"command.permissions.autoaccept.enable": "Aksepter tillatelser automatisk",
|
"command.permissions.autoaccept.enable": "Aksepter tillatelser automatisk",
|
||||||
"command.permissions.autoaccept.disable": "Stopp automatisk akseptering av tillatelser",
|
"command.permissions.autoaccept.disable": "Stopp automatisk akseptering av tillatelser",
|
||||||
"command.workspace.toggle": "Veksle arbeidsområder",
|
"command.workspace.toggle": "Veksle arbeidsområder",
|
||||||
"command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar",
|
"command.workspace.toggle.description": "Aktiver eller deaktiver flere arbeidsområder i sidefeltet",
|
||||||
"command.session.undo": "Angre",
|
"command.session.undo": "Angre",
|
||||||
"command.session.undo.description": "Angre siste melding",
|
"command.session.undo.description": "Angre siste melding",
|
||||||
"command.session.redo": "Gjør om",
|
"command.session.redo": "Gjør om",
|
||||||
@@ -469,7 +469,7 @@ export const dict = {
|
|||||||
"Rotelement ikke funnet. Glemte du å legge det til i index.html? Eller kanskje id-attributten er feilstavet?",
|
"Rotelement ikke funnet. Glemte du å legge det til i index.html? Eller kanskje id-attributten er feilstavet?",
|
||||||
|
|
||||||
"error.serverSync.connectFailed": "Kunne ikke koble til server. Kjører det en server på `{{url}}`?",
|
"error.serverSync.connectFailed": "Kunne ikke koble til server. Kjører det en server på `{{url}}`?",
|
||||||
"directory.error.invalidUrl": "Invalid directory in URL.",
|
"directory.error.invalidUrl": "Ugyldig mappe i URL-en.",
|
||||||
|
|
||||||
"error.chain.unknown": "Ukjent feil",
|
"error.chain.unknown": "Ukjent feil",
|
||||||
"error.chain.causedBy": "Forårsaket av:",
|
"error.chain.causedBy": "Forårsaket av:",
|
||||||
@@ -649,8 +649,8 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
|
"settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
|
||||||
"settings.general.row.font.title": "Kodefont",
|
"settings.general.row.font.title": "Kodefont",
|
||||||
"settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker",
|
"settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "Terminalskrift",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "Tilpass skrifttypen som brukes i terminalen",
|
||||||
"settings.general.row.uiFont.title": "UI-skrift",
|
"settings.general.row.uiFont.title": "UI-skrift",
|
||||||
"settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet",
|
"settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet",
|
||||||
"settings.general.row.followup.title": "Oppfølgingsadferd",
|
"settings.general.row.followup.title": "Oppfølgingsadferd",
|
||||||
@@ -936,4 +936,163 @@ export const dict = {
|
|||||||
"error.childStore.persistedProjectIconCreateFailed": "Kunne ikke opprette vedvarende prosjektikon",
|
"error.childStore.persistedProjectIconCreateFailed": "Kunne ikke opprette vedvarende prosjektikon",
|
||||||
"error.childStore.storeCreateFailed": "Kunne ikke opprette lager",
|
"error.childStore.storeCreateFailed": "Kunne ikke opprette lager",
|
||||||
"terminal.connectionLost.abnormalClose": "WebSocket lukket unormalt: {{code}}",
|
"terminal.connectionLost.abnormalClose": "WebSocket lukket unormalt: {{code}}",
|
||||||
|
|
||||||
|
"command.project.previous": "Forrige prosjekt",
|
||||||
|
"command.project.next": "Neste prosjekt",
|
||||||
|
"command.project.index": "Bytt til prosjekt {{index}}",
|
||||||
|
"command.tab.reopenClosed": "Åpne lukket fane på nytt",
|
||||||
|
|
||||||
|
"model.tooltip.context.label": "Kontekst",
|
||||||
|
"model.tooltip.inputs": "Inndata",
|
||||||
|
"model.tooltip.model": "Modell",
|
||||||
|
"model.tooltip.provider": "Leverandør",
|
||||||
|
"model.tooltip.reasoning": "Resonnering",
|
||||||
|
|
||||||
|
"common.clear": "Tøm",
|
||||||
|
|
||||||
|
"session.child.promptDisabled": "Underagentsesjoner kan ikke motta forespørsler.",
|
||||||
|
"session.child.backToParent": "Tilbake til hovedsesjonen.",
|
||||||
|
|
||||||
|
"wsl.server.add": "Legg til WSL-server",
|
||||||
|
"wsl.server.addShort": "Legg til WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "WSL-server",
|
||||||
|
"wsl.server.retryStart": "Prøv å starte på nytt",
|
||||||
|
"wsl.server.updating": "Oppdaterer...",
|
||||||
|
"wsl.onboarding.step.distro": "Velg distribusjon",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "Sjekker WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "Windows må startes på nytt for å fullføre installasjonen av WSL.",
|
||||||
|
"wsl.onboarding.ready": "WSL er klart.",
|
||||||
|
"wsl.onboarding.required": "WSL kreves for å fortsette.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Sjekker distribusjoner...",
|
||||||
|
"wsl.onboarding.installingDistro": "Installerer {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "Sjekker {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "Lister distribusjoner...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} er klar.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "{{distro}} er ikke installert ennå.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Åpne {{distro}} én gang for å fullføre oppsettet.",
|
||||||
|
"wsl.onboarding.finishingDistro": "Fullfører oppsettet for {{distro}}.",
|
||||||
|
"wsl.onboarding.pickDistro": "Velg en distribusjon eller installer en nedenfor.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "Sjekker OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "Sjekker OpenCode i {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "Oppdaterer OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "Oppdaterer OpenCode i {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "Oppdater OpenCode i {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "Oppdater OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode er klar i {{distro}}.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode er klar.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "Installer OpenCode i {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "Installer OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Velg en distribusjon først.",
|
||||||
|
"wsl.onboarding.loadFailed": "Kunne ikke laste inn WSL-status.",
|
||||||
|
"wsl.onboarding.loading": "Laster...",
|
||||||
|
"wsl.onboarding.installedDistros": "Installerte distribusjoner",
|
||||||
|
"wsl.onboarding.checkAgain": "Sjekk på nytt",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Klar",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Sjekker...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode er ikke installert",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "Mangler bash, curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "Ikke støttet · Bruk WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "Trenger du en annen distribusjon?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "Installer en Linux-distribusjon fra WSL-katalogen",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL er ikke installert",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"WSL (Windows Subsystem for Linux) kreves før OpenCode kan legge til en WSL-server",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL er utilgjengelig",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode kunne ikke bekrefte WSL på denne maskinen.",
|
||||||
|
"wsl.onboarding.installWsl": "Installer WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"Start Windows på nytt for å fullføre installasjonen av WSL, og åpne deretter OpenCode på nytt.",
|
||||||
|
"wsl.onboarding.next": "Neste",
|
||||||
|
"wsl.onboarding.refresh": "Oppdater",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Alle installerte distribusjoner er allerede lagt til.",
|
||||||
|
"wsl.onboarding.noDistros": "Ingen distribusjoner oppdaget ennå.",
|
||||||
|
"wsl.onboarding.install": "Installer",
|
||||||
|
"wsl.onboarding.installing": "Installerer...",
|
||||||
|
"wsl.onboarding.installDistro": "Installer distribusjon",
|
||||||
|
"wsl.onboarding.searchDistros": "Søk etter distribusjoner",
|
||||||
|
"wsl.onboarding.wsl2Required": "WSL 2 er påkrevd.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Denne distribusjonen trenger bash og curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "Åpne terminal",
|
||||||
|
"wsl.onboarding.path": "Bane: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "ikke funnet",
|
||||||
|
"wsl.onboarding.version": "Versjon: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "ukjent",
|
||||||
|
"wsl.onboarding.desktopVersion": "skrivebordsapp {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "Den installerte versjonen samsvarer ikke med versjonen av skrivebordsappen.",
|
||||||
|
"wsl.onboarding.adding": "Legger til...",
|
||||||
|
|
||||||
|
"language.uk": "Українська",
|
||||||
|
|
||||||
|
"error.page.description.localServerStartup": "Det oppstod en feil under oppstart av den lokale serveren.",
|
||||||
|
"error.page.action.exportLogs": "Eksporter logger",
|
||||||
|
|
||||||
|
"home.title": "Hjem",
|
||||||
|
"home.projects": "Prosjekter",
|
||||||
|
"home.project.add": "Legg til prosjekt",
|
||||||
|
"home.recentlyClosed": "Nylig lukket",
|
||||||
|
"home.server.collapse": "Fold sammen serverprosjekter",
|
||||||
|
"home.server.expand": "Utvid serverprosjekter",
|
||||||
|
"home.sessions.search.placeholder": "Søk etter sesjoner",
|
||||||
|
"home.sessions.search.placeholder.scoped": "Søk etter sesjoner i {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "Sesjoner",
|
||||||
|
"home.sessions.search.noResults": "Ingen sesjoner funnet for {{query}}",
|
||||||
|
"home.sessions.empty": "Ingenting her ennå",
|
||||||
|
"home.sessions.empty.description": "Opprett en sesjon for å komme i gang",
|
||||||
|
"home.sessions.group.today": "I dag",
|
||||||
|
"home.sessions.group.yesterday": "I går",
|
||||||
|
"home.sessions.group.older": "Eldre",
|
||||||
|
|
||||||
|
"session.tab.unknown": "Ukjent sesjon",
|
||||||
|
"session.error.notFound": "Denne sesjonen finnes ikke",
|
||||||
|
"session.error.notFound.description": "Denne fanen peker til en sesjon som ikke lenger finnes på denne serveren.",
|
||||||
|
"session.error.notFound.closeTab": "Lukk fane",
|
||||||
|
"session.error.serverConnection": "Kan ikke koble til denne serveren",
|
||||||
|
"session.review.noUncommittedChanges": "Ingen ucommittede endringer ennå",
|
||||||
|
"session.review.noBranchChanges": "Ingen grenendringer ennå",
|
||||||
|
"session.question.minimize": "Minimer spørsmål",
|
||||||
|
"session.question.restore": "Gjenopprett spørsmål",
|
||||||
|
"session.question.pending.one": "{{count}} ventende spørsmål",
|
||||||
|
"session.question.pending.other": "{{count}} ventende spørsmål",
|
||||||
|
"session.new.project.new": "Nytt prosjekt",
|
||||||
|
"session.new.project.search": "Søk etter prosjekter",
|
||||||
|
"session.new.project.add": "Legg til prosjekt",
|
||||||
|
"session.new.workspace.runIn": "Kjør sesjonen i",
|
||||||
|
"session.new.workspace.triggerLocal": "Lokalt",
|
||||||
|
"session.new.workspace.local": "Lokalt depot",
|
||||||
|
"session.new.workspace.existing": "Arbeidsområde…",
|
||||||
|
|
||||||
|
"sidebar.empty.title": "Ingen åpne prosjekter",
|
||||||
|
"sidebar.empty.description": "Åpne et prosjekt for å komme i gang",
|
||||||
|
|
||||||
|
"settings.general.section.advanced": "Avansert",
|
||||||
|
"settings.general.row.shell.title": "Terminalskall",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"Velg skallet som skal brukes i terminalen. Kompatible skall brukes også til agentens verktøykall.",
|
||||||
|
"settings.general.row.shell.autoDefault": "Automatisk (standard)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "kun terminal",
|
||||||
|
"settings.general.row.showFileTree.title": "Filtre",
|
||||||
|
"settings.general.row.showFileTree.description": "Vis filtrepanelet i sesjoner",
|
||||||
|
"settings.general.row.showNavigation.title": "Navigasjonskontroller",
|
||||||
|
"settings.general.row.showNavigation.description":
|
||||||
|
"Vis tilbake- og fremoverknappene på tittellinjen i skrivebordsappen",
|
||||||
|
"settings.general.row.showSearch.title": "Kommandopalett",
|
||||||
|
"settings.general.row.showSearch.description": "Vis knappen for søk og kommandopalett på tittellinjen",
|
||||||
|
"settings.general.row.showTerminal.title": "Terminal",
|
||||||
|
"settings.general.row.showTerminal.description": "Vis terminalknappen på tittellinjen i skrivebordsappen",
|
||||||
|
"settings.general.row.showStatus.title": "Serverstatus",
|
||||||
|
"settings.general.row.showStatus.description": "Vis serverstatusknappen på tittellinjen",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Navigasjon nederst",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"Plasser tittellinjen og sesjonsfanene nederst på mobilskjermen",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Egendefinerte agenter",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Vis agentvelgeren i skrivefeltet",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Ny layout og utforming",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Aktiver ny layout og utforming for startsiden, skrivefeltet og sesjonsgrensesnittet",
|
||||||
|
"settings.general.row.pinchZoom.title": "Knip for å zoome",
|
||||||
|
"settings.general.row.pinchZoom.description": "Tillat knipebevegelser på styreflaten og Ctrl-rulling for å zoome",
|
||||||
|
"settings.updates.action.downloading": "Laster ned...",
|
||||||
|
"settings.updates.action.installing": "Installerer...",
|
||||||
} satisfies Partial<Record<Keys, string>>
|
} satisfies Partial<Record<Keys, string>>
|
||||||
|
|||||||
@@ -1,33 +1,118 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { dict as en } from "./en"
|
|
||||||
import { dict as ar } from "./ar"
|
|
||||||
import { dict as br } from "./br"
|
|
||||||
import { dict as bs } from "./bs"
|
|
||||||
import { dict as da } from "./da"
|
|
||||||
import { dict as de } from "./de"
|
|
||||||
import { dict as es } from "./es"
|
|
||||||
import { dict as fr } from "./fr"
|
|
||||||
import { dict as ja } from "./ja"
|
|
||||||
import { dict as ko } from "./ko"
|
|
||||||
import { dict as no } from "./no"
|
|
||||||
import { dict as pl } from "./pl"
|
|
||||||
import { dict as ru } from "./ru"
|
|
||||||
import { dict as uk } from "./uk"
|
|
||||||
import { dict as th } from "./th"
|
|
||||||
import { dict as zh } from "./zh"
|
|
||||||
import { dict as zht } from "./zht"
|
|
||||||
import { dict as tr } from "./tr"
|
|
||||||
|
|
||||||
const locales = [ar, br, bs, da, de, es, fr, ja, ko, no, pl, ru, uk, th, tr, zh, zht]
|
const appLocales = [
|
||||||
const keys = ["command.session.previous.unseen", "command.session.next.unseen"] as const
|
"ar",
|
||||||
|
"br",
|
||||||
|
"bs",
|
||||||
|
"da",
|
||||||
|
"de",
|
||||||
|
"es",
|
||||||
|
"fr",
|
||||||
|
"ja",
|
||||||
|
"ko",
|
||||||
|
"no",
|
||||||
|
"pl",
|
||||||
|
"ru",
|
||||||
|
"uk",
|
||||||
|
"th",
|
||||||
|
"tr",
|
||||||
|
"zh",
|
||||||
|
"zht",
|
||||||
|
] as const
|
||||||
|
const desktopLocales = appLocales.filter((locale) => locale !== "th" && locale !== "tr")
|
||||||
|
|
||||||
|
const domains = [
|
||||||
|
{
|
||||||
|
name: "app",
|
||||||
|
source: "./en.ts",
|
||||||
|
target: (locale: string) => `./${locale}.ts`,
|
||||||
|
locales: appLocales,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ui",
|
||||||
|
source: "../../../ui/src/i18n/en.ts",
|
||||||
|
target: (locale: string) => `../../../ui/src/i18n/${locale}.ts`,
|
||||||
|
locales: appLocales,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "desktop",
|
||||||
|
source: "../../../desktop/src/renderer/i18n/en.ts",
|
||||||
|
target: (locale: string) => `../../../desktop/src/renderer/i18n/${locale}.ts`,
|
||||||
|
locales: desktopLocales,
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
describe("i18n parity", () => {
|
describe("i18n parity", () => {
|
||||||
test("non-English locales translate targeted unseen session keys", () => {
|
test("non-English locales have every English key", async () => {
|
||||||
for (const locale of locales) {
|
for (const domain of domains) {
|
||||||
for (const key of keys) {
|
const source = await dictionary(domain.source)
|
||||||
expect(locale[key]).toBeDefined()
|
for (const locale of domain.locales) {
|
||||||
expect(locale[key]).not.toBe(en[key])
|
const target = await dictionary(domain.target(locale))
|
||||||
|
const missing = Object.keys(source).filter((key) => !Object.hasOwn(target, key))
|
||||||
|
const extra = Object.keys(target).filter((key) => !Object.hasOwn(source, key))
|
||||||
|
expect({ domain: domain.name, locale, missing, extra }).toEqual({
|
||||||
|
domain: domain.name,
|
||||||
|
locale,
|
||||||
|
missing: [],
|
||||||
|
extra: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("non-English locales preserve English placeholders", async () => {
|
||||||
|
for (const domain of domains) {
|
||||||
|
const source = await dictionary(domain.source)
|
||||||
|
for (const locale of domain.locales) {
|
||||||
|
const target = await dictionary(domain.target(locale))
|
||||||
|
const mismatched = Object.keys(source).filter(
|
||||||
|
(key) => Object.hasOwn(target, key) && placeholders(source[key]).join() !== placeholders(target[key]).join(),
|
||||||
|
)
|
||||||
|
expect({ domain: domain.name, locale, mismatched }).toEqual({ domain: domain.name, locale, mismatched: [] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("non-English locales translate targeted unseen session keys", async () => {
|
||||||
|
const source = await dictionary("./en.ts")
|
||||||
|
for (const locale of appLocales) {
|
||||||
|
const target = await dictionary(`./${locale}.ts`)
|
||||||
|
for (const key of ["command.session.previous.unseen", "command.session.next.unseen"]) {
|
||||||
|
expect(target[key]).toBeDefined()
|
||||||
|
expect(target[key]).not.toBe(source[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("changed-file summary keys preserve rendered English copy and localize complete phrases", async () => {
|
||||||
|
const source = await dictionary("../../../ui/src/i18n/en.ts")
|
||||||
|
expect(source["ui.sessionTurn.diffs.changed.one"].replace("{{count}}", "1")).toBe("1 Changed file")
|
||||||
|
expect(source["ui.sessionTurn.diffs.changed.other"].replace("{{count}}", "2")).toBe("2 Changed files")
|
||||||
|
expect(source["ui.sessionTurn.diffs.changed"]).toBeUndefined()
|
||||||
|
|
||||||
|
for (const locale of appLocales) {
|
||||||
|
const target = await dictionary(`../../../ui/src/i18n/${locale}.ts`)
|
||||||
|
for (const key of ["ui.sessionTurn.diffs.changed.one", "ui.sessionTurn.diffs.changed.other"]) {
|
||||||
|
expect(target[key].trim()).not.toBe("")
|
||||||
|
expect(placeholders(target[key])).toEqual(["count"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function dictionary(file: string) {
|
||||||
|
const module: unknown = await import(file)
|
||||||
|
if (typeof module !== "object" || module === null || !("dict" in module) || !isDictionary(module.dict)) {
|
||||||
|
throw new Error(`Invalid translation dictionary: ${file}`)
|
||||||
|
}
|
||||||
|
return module.dict
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDictionary(value: unknown): value is Record<string, string> {
|
||||||
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return false
|
||||||
|
return Object.values(value).every((item) => typeof item === "string")
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeholders(value: string) {
|
||||||
|
return Array.from(value.matchAll(/{{\s*([^}]+?)\s*}}/g), (match) => match[1]).sort()
|
||||||
|
}
|
||||||
|
|||||||
+157
-6
@@ -21,6 +21,9 @@ export const dict = {
|
|||||||
"theme.scheme.dark": "Ciemny",
|
"theme.scheme.dark": "Ciemny",
|
||||||
"command.sidebar.toggle": "Przełącz pasek boczny",
|
"command.sidebar.toggle": "Przełącz pasek boczny",
|
||||||
"command.project.open": "Otwórz projekt",
|
"command.project.open": "Otwórz projekt",
|
||||||
|
"command.project.previous": "Poprzedni projekt",
|
||||||
|
"command.project.next": "Następny projekt",
|
||||||
|
"command.project.index": "Przełącz na projekt {{index}}",
|
||||||
"command.provider.connect": "Połącz dostawcę",
|
"command.provider.connect": "Połącz dostawcę",
|
||||||
"command.server.switch": "Przełącz serwer",
|
"command.server.switch": "Przełącz serwer",
|
||||||
"command.settings.open": "Otwórz ustawienia",
|
"command.settings.open": "Otwórz ustawienia",
|
||||||
@@ -39,6 +42,7 @@ export const dict = {
|
|||||||
"command.session.new": "Nowa sesja",
|
"command.session.new": "Nowa sesja",
|
||||||
"command.file.open": "Otwórz plik",
|
"command.file.open": "Otwórz plik",
|
||||||
"command.tab.close": "Zamknij kartę",
|
"command.tab.close": "Zamknij kartę",
|
||||||
|
"command.tab.reopenClosed": "Otwórz ponownie zamkniętą kartę",
|
||||||
"command.context.addSelection": "Dodaj zaznaczenie do kontekstu",
|
"command.context.addSelection": "Dodaj zaznaczenie do kontekstu",
|
||||||
"command.context.addSelection.description": "Dodaj zaznaczone linie z bieżącego pliku",
|
"command.context.addSelection.description": "Dodaj zaznaczone linie z bieżącego pliku",
|
||||||
"command.input.focus": "Fokus na pole wejściowe",
|
"command.input.focus": "Fokus na pole wejściowe",
|
||||||
@@ -194,6 +198,11 @@ export const dict = {
|
|||||||
"model.input.video": "wideo",
|
"model.input.video": "wideo",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "Obsługuje: {{inputs}}",
|
"model.tooltip.allows": "Obsługuje: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Kontekst",
|
||||||
|
"model.tooltip.inputs": "Dane wejściowe",
|
||||||
|
"model.tooltip.model": "Model",
|
||||||
|
"model.tooltip.provider": "Dostawca",
|
||||||
|
"model.tooltip.reasoning": "Wnioskowanie",
|
||||||
"model.tooltip.reasoning.allowed": "Obsługuje wnioskowanie",
|
"model.tooltip.reasoning.allowed": "Obsługuje wnioskowanie",
|
||||||
"model.tooltip.reasoning.none": "Brak wnioskowania",
|
"model.tooltip.reasoning.none": "Brak wnioskowania",
|
||||||
"model.tooltip.context": "Limit kontekstu {{limit}}",
|
"model.tooltip.context": "Limit kontekstu {{limit}}",
|
||||||
@@ -203,6 +212,7 @@ export const dict = {
|
|||||||
"common.loading": "Ładowanie",
|
"common.loading": "Ładowanie",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "Anuluj",
|
"common.cancel": "Anuluj",
|
||||||
|
"common.clear": "Wyczyść",
|
||||||
"common.connect": "Połącz",
|
"common.connect": "Połącz",
|
||||||
"common.disconnect": "Rozłącz",
|
"common.disconnect": "Rozłącz",
|
||||||
"common.continue": "Prześlij",
|
"common.continue": "Prześlij",
|
||||||
@@ -219,6 +229,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Terminal",
|
"prompt.mode.shell": "Terminal",
|
||||||
"prompt.mode.normal": "Prompt",
|
"prompt.mode.normal": "Prompt",
|
||||||
"prompt.mode.shell.exit": "esc aby wyjść",
|
"prompt.mode.shell.exit": "esc aby wyjść",
|
||||||
|
"session.child.promptDisabled": "Nie można wysyłać promptów w sesjach podagentów.",
|
||||||
|
"session.child.backToParent": "Wróć do głównej sesji.",
|
||||||
"prompt.example.1": "Napraw TODO w bazie kodu",
|
"prompt.example.1": "Napraw TODO w bazie kodu",
|
||||||
"prompt.example.2": "Jaki jest stos technologiczny tego projektu?",
|
"prompt.example.2": "Jaki jest stos technologiczny tego projektu?",
|
||||||
"prompt.example.3": "Napraw zepsute testy",
|
"prompt.example.3": "Napraw zepsute testy",
|
||||||
@@ -315,6 +327,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "Usuń",
|
"dialog.server.menu.delete": "Usuń",
|
||||||
"dialog.server.current": "Obecny serwer",
|
"dialog.server.current": "Obecny serwer",
|
||||||
"dialog.server.status.default": "Domyślny",
|
"dialog.server.status.default": "Domyślny",
|
||||||
|
"wsl.server.add": "Dodaj serwer WSL",
|
||||||
|
"wsl.server.addShort": "Dodaj WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "Serwer WSL",
|
||||||
|
"wsl.server.retryStart": "Ponów uruchamianie",
|
||||||
|
"wsl.server.updating": "Aktualizowanie...",
|
||||||
|
"wsl.onboarding.step.distro": "Wybierz dystrybucję",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "Sprawdzanie WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "Aby dokończyć instalację WSL, uruchom ponownie system Windows.",
|
||||||
|
"wsl.onboarding.ready": "WSL jest gotowy.",
|
||||||
|
"wsl.onboarding.required": "Aby kontynuować, wymagany jest WSL.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Sprawdzanie dystrybucji...",
|
||||||
|
"wsl.onboarding.installingDistro": "Instalowanie {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "Sprawdzanie {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "Pobieranie listy dystrybucji...",
|
||||||
|
"wsl.onboarding.distroReady": "Dystrybucja {{distro}} jest gotowa.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "Dystrybucja {{distro}} nie jest jeszcze zainstalowana.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Uruchom raz {{distro}}, aby dokończyć konfigurację.",
|
||||||
|
"wsl.onboarding.finishingDistro": "Kończenie konfiguracji {{distro}}.",
|
||||||
|
"wsl.onboarding.pickDistro": "Wybierz dystrybucję lub zainstaluj jedną z poniższych.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "Sprawdzanie OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "Sprawdzanie OpenCode w dystrybucji {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "Aktualizowanie OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "Aktualizowanie OpenCode w dystrybucji {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "Zaktualizuj OpenCode w dystrybucji {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "Zaktualizuj OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode jest gotowy w dystrybucji {{distro}}.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode jest gotowy.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "Zainstaluj OpenCode w dystrybucji {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "Zainstaluj OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Najpierw wybierz dystrybucję.",
|
||||||
|
"wsl.onboarding.loadFailed": "Nie udało się załadować stanu WSL.",
|
||||||
|
"wsl.onboarding.loading": "Ładowanie...",
|
||||||
|
"wsl.onboarding.installedDistros": "Zainstalowane dystrybucje",
|
||||||
|
"wsl.onboarding.checkAgain": "Sprawdź ponownie",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Gotowa",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Sprawdzanie...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode nie jest zainstalowany",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "Brak bash i curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "Nieobsługiwana · Użyj WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "Potrzebujesz innej dystrybucji?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "Zainstaluj dystrybucję systemu Linux z katalogu WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL nie jest zainstalowany",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"WSL (Podsystem Windows dla systemu Linux) jest wymagany, zanim OpenCode będzie mógł dodać serwer WSL",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL jest niedostępny",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode nie mógł zweryfikować WSL na tym komputerze.",
|
||||||
|
"wsl.onboarding.installWsl": "Zainstaluj WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"Uruchom ponownie system Windows, aby dokończyć instalację WSL, a następnie ponownie otwórz OpenCode.",
|
||||||
|
"wsl.onboarding.next": "Dalej",
|
||||||
|
"wsl.onboarding.refresh": "Odśwież",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Wszystkie zainstalowane dystrybucje zostały już dodane.",
|
||||||
|
"wsl.onboarding.noDistros": "Nie wykryto jeszcze żadnych dystrybucji.",
|
||||||
|
"wsl.onboarding.install": "Zainstaluj",
|
||||||
|
"wsl.onboarding.installing": "Instalowanie...",
|
||||||
|
"wsl.onboarding.installDistro": "Zainstaluj dystrybucję",
|
||||||
|
"wsl.onboarding.searchDistros": "Szukaj dystrybucji",
|
||||||
|
"wsl.onboarding.wsl2Required": "Wymagany jest WSL 2.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Ta dystrybucja wymaga bash i curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "Otwórz terminal",
|
||||||
|
"wsl.onboarding.path": "Ścieżka: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "nie znaleziono",
|
||||||
|
"wsl.onboarding.version": "Wersja: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "nieznana",
|
||||||
|
"wsl.onboarding.desktopVersion": "aplikacja komputerowa {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "Zainstalowana wersja nie jest zgodna z wersją aplikacji komputerowej.",
|
||||||
|
"wsl.onboarding.adding": "Dodawanie...",
|
||||||
"dialog.project.edit.title": "Edytuj projekt",
|
"dialog.project.edit.title": "Edytuj projekt",
|
||||||
"dialog.project.edit.name": "Nazwa",
|
"dialog.project.edit.name": "Nazwa",
|
||||||
"dialog.project.edit.icon": "Ikona",
|
"dialog.project.edit.icon": "Ikona",
|
||||||
@@ -324,7 +405,8 @@ export const dict = {
|
|||||||
"dialog.project.edit.color": "Kolor",
|
"dialog.project.edit.color": "Kolor",
|
||||||
"dialog.project.edit.color.select": "Wybierz kolor {{color}}",
|
"dialog.project.edit.color.select": "Wybierz kolor {{color}}",
|
||||||
"dialog.project.edit.worktree.startup": "Skrypt uruchamiania przestrzeni roboczej",
|
"dialog.project.edit.worktree.startup": "Skrypt uruchamiania przestrzeni roboczej",
|
||||||
"dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).",
|
"dialog.project.edit.worktree.startup.description":
|
||||||
|
"Uruchamiany po utworzeniu nowej przestrzeni roboczej (worktree).",
|
||||||
"dialog.project.edit.worktree.startup.placeholder": "np. bun install",
|
"dialog.project.edit.worktree.startup.placeholder": "np. bun install",
|
||||||
"context.breakdown.title": "Podział kontekstu",
|
"context.breakdown.title": "Podział kontekstu",
|
||||||
"context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.',
|
"context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.',
|
||||||
@@ -371,6 +453,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
"toast.language.title": "Język",
|
"toast.language.title": "Język",
|
||||||
@@ -407,10 +490,12 @@ export const dict = {
|
|||||||
"toast.update.action.notYet": "Jeszcze nie",
|
"toast.update.action.notYet": "Jeszcze nie",
|
||||||
"error.page.title": "Coś poszło nie tak",
|
"error.page.title": "Coś poszło nie tak",
|
||||||
"error.page.description": "Wystąpił błąd podczas ładowania aplikacji.",
|
"error.page.description": "Wystąpił błąd podczas ładowania aplikacji.",
|
||||||
|
"error.page.description.localServerStartup": "Wystąpił błąd podczas uruchamiania serwera lokalnego.",
|
||||||
"error.page.details.label": "Szczegóły błędu",
|
"error.page.details.label": "Szczegóły błędu",
|
||||||
"error.page.action.restart": "Restartuj",
|
"error.page.action.restart": "Restartuj",
|
||||||
"error.page.action.report": "Zgłoś błąd",
|
"error.page.action.report": "Zgłoś błąd",
|
||||||
"error.page.action.reported": "Błąd zgłoszony",
|
"error.page.action.reported": "Błąd zgłoszony",
|
||||||
|
"error.page.action.exportLogs": "Eksportuj dzienniki",
|
||||||
"error.page.action.checking": "Sprawdzanie...",
|
"error.page.action.checking": "Sprawdzanie...",
|
||||||
"error.page.action.checkUpdates": "Sprawdź aktualizacje",
|
"error.page.action.checkUpdates": "Sprawdź aktualizacje",
|
||||||
"error.page.action.updateTo": "Zaktualizuj do {{version}}",
|
"error.page.action.updateTo": "Zaktualizuj do {{version}}",
|
||||||
@@ -430,7 +515,8 @@ export const dict = {
|
|||||||
"error.chain.didYouMean": "Czy miałeś na myśli: {{suggestions}}",
|
"error.chain.didYouMean": "Czy miałeś na myśli: {{suggestions}}",
|
||||||
"error.chain.modelNotFound": "Model nie znaleziony: {{provider}}/{{model}}",
|
"error.chain.modelNotFound": "Model nie znaleziony: {{provider}}/{{model}}",
|
||||||
"error.chain.checkConfig": "Sprawdź swoją konfigurację (opencode.json) nazwy dostawców/modeli",
|
"error.chain.checkConfig": "Sprawdź swoją konfigurację (opencode.json) nazwy dostawców/modeli",
|
||||||
"error.chain.mcpFailed": 'MCP server "{{name}}" failed. Note, OpenCode does not support MCP authentication yet.',
|
"error.chain.mcpFailed":
|
||||||
|
"Serwer MCP „{{name}}” nie uruchomił się. Uwaga: OpenCode nie obsługuje jeszcze uwierzytelniania MCP.",
|
||||||
"error.chain.providerAuthFailed": "Uwierzytelnianie dostawcy nie powiodło się ({{provider}}): {{message}}",
|
"error.chain.providerAuthFailed": "Uwierzytelnianie dostawcy nie powiodło się ({{provider}}): {{message}}",
|
||||||
"error.chain.providerInitFailed":
|
"error.chain.providerInitFailed":
|
||||||
'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.',
|
'Nie udało się zainicjować dostawcy "{{provider}}". Sprawdź poświadczenia i konfigurację.',
|
||||||
@@ -452,10 +538,30 @@ export const dict = {
|
|||||||
"home.recentProjects": "Ostatnie projekty",
|
"home.recentProjects": "Ostatnie projekty",
|
||||||
"home.empty.title": "Brak ostatnich projektów",
|
"home.empty.title": "Brak ostatnich projektów",
|
||||||
"home.empty.description": "Zacznij od otwarcia lokalnego projektu",
|
"home.empty.description": "Zacznij od otwarcia lokalnego projektu",
|
||||||
|
"home.title": "Strona główna",
|
||||||
|
"home.projects": "Projekty",
|
||||||
|
"home.project.add": "Dodaj projekt",
|
||||||
|
"home.recentlyClosed": "Ostatnio zamknięte",
|
||||||
|
"home.server.collapse": "Zwiń projekty serwera",
|
||||||
|
"home.server.expand": "Rozwiń projekty serwera",
|
||||||
|
"home.sessions.search.placeholder": "Szukaj sesji",
|
||||||
|
"home.sessions.search.placeholder.scoped": "Szukaj sesji w {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "Sesje",
|
||||||
|
"home.sessions.search.noResults": "Nie znaleziono sesji dla {{query}}",
|
||||||
|
"home.sessions.empty": "Jeszcze nic tu nie ma",
|
||||||
|
"home.sessions.empty.description": "Utwórz sesję, aby rozpocząć",
|
||||||
|
"home.sessions.group.today": "Dzisiaj",
|
||||||
|
"home.sessions.group.yesterday": "Wczoraj",
|
||||||
|
"home.sessions.group.older": "Starsze",
|
||||||
"session.tab.session": "Sesja",
|
"session.tab.session": "Sesja",
|
||||||
"session.tab.review": "Przegląd",
|
"session.tab.review": "Przegląd",
|
||||||
"session.tab.context": "Kontekst",
|
"session.tab.context": "Kontekst",
|
||||||
|
"session.tab.unknown": "Nieznana sesja",
|
||||||
"session.panel.reviewAndFiles": "Przegląd i pliki",
|
"session.panel.reviewAndFiles": "Przegląd i pliki",
|
||||||
|
"session.error.notFound": "Nie można znaleźć tej sesji",
|
||||||
|
"session.error.notFound.description": "Ta karta wskazuje sesję, która nie istnieje już na tym serwerze.",
|
||||||
|
"session.error.notFound.closeTab": "Zamknij kartę",
|
||||||
|
"session.error.serverConnection": "Nie można połączyć się z tym serwerem",
|
||||||
"session.review.filesChanged": "Zmieniono {{count}} plików",
|
"session.review.filesChanged": "Zmieniono {{count}} plików",
|
||||||
"session.review.change.one": "Zmiana",
|
"session.review.change.one": "Zmiana",
|
||||||
"session.review.change.other": "Zmiany",
|
"session.review.change.other": "Zmiany",
|
||||||
@@ -464,6 +570,8 @@ export const dict = {
|
|||||||
"session.review.noVcs": "Nie wykryto systemu kontroli wersji Git, zmiany nie są wyświetlane",
|
"session.review.noVcs": "Nie wykryto systemu kontroli wersji Git, zmiany nie są wyświetlane",
|
||||||
"session.review.noSnapshot": "Śledzenie migawek jest wyłączone w konfiguracji, więc zmiany w sesji są niedostępne",
|
"session.review.noSnapshot": "Śledzenie migawek jest wyłączone w konfiguracji, więc zmiany w sesji są niedostępne",
|
||||||
"session.review.noChanges": "Brak zmian",
|
"session.review.noChanges": "Brak zmian",
|
||||||
|
"session.review.noUncommittedChanges": "Brak jeszcze niezatwierdzonych zmian",
|
||||||
|
"session.review.noBranchChanges": "Brak jeszcze zmian w gałęzi",
|
||||||
"session.files.selectToOpen": "Wybierz plik do otwarcia",
|
"session.files.selectToOpen": "Wybierz plik do otwarcia",
|
||||||
"session.files.all": "Wszystkie pliki",
|
"session.files.all": "Wszystkie pliki",
|
||||||
"session.files.empty": "Brak plików",
|
"session.files.empty": "Brak plików",
|
||||||
@@ -477,6 +585,10 @@ export const dict = {
|
|||||||
"session.todo.title": "Zadania",
|
"session.todo.title": "Zadania",
|
||||||
"session.todo.collapse": "Zwiń",
|
"session.todo.collapse": "Zwiń",
|
||||||
"session.todo.expand": "Rozwiń",
|
"session.todo.expand": "Rozwiń",
|
||||||
|
"session.question.minimize": "Zminimalizuj pytanie",
|
||||||
|
"session.question.restore": "Przywróć pytanie",
|
||||||
|
"session.question.pending.one": "{{count}} oczekujące pytanie",
|
||||||
|
"session.question.pending.other": "Pytania oczekujące: {{count}}",
|
||||||
"session.followupDock.summary.one": "{{count}} wiadomość w kolejce",
|
"session.followupDock.summary.one": "{{count}} wiadomość w kolejce",
|
||||||
"session.followupDock.summary.other": "{{count}} wiadomości w kolejce",
|
"session.followupDock.summary.other": "{{count}} wiadomości w kolejce",
|
||||||
"session.followupDock.sendNow": "Wyślij teraz",
|
"session.followupDock.sendNow": "Wyślij teraz",
|
||||||
@@ -489,9 +601,16 @@ export const dict = {
|
|||||||
"session.revertDock.expand": "Rozwiń cofnięte wiadomości",
|
"session.revertDock.expand": "Rozwiń cofnięte wiadomości",
|
||||||
"session.revertDock.restore": "Przywróć wiadomość",
|
"session.revertDock.restore": "Przywróć wiadomość",
|
||||||
"session.new.title": "Zbuduj cokolwiek",
|
"session.new.title": "Zbuduj cokolwiek",
|
||||||
|
"session.new.project.new": "Nowy projekt",
|
||||||
|
"session.new.project.search": "Szukaj projektów",
|
||||||
|
"session.new.project.add": "Dodaj projekt",
|
||||||
"session.new.worktree.main": "Główna gałąź",
|
"session.new.worktree.main": "Główna gałąź",
|
||||||
"session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Główna gałąź ({{branch}})",
|
||||||
"session.new.worktree.create": "Utwórz nowe drzewo robocze",
|
"session.new.worktree.create": "Utwórz nowe drzewo robocze",
|
||||||
|
"session.new.workspace.runIn": "Uruchom sesję w",
|
||||||
|
"session.new.workspace.triggerLocal": "Lokalnie",
|
||||||
|
"session.new.workspace.local": "Lokalne repozytorium",
|
||||||
|
"session.new.workspace.existing": "Przestrzeń robocza…",
|
||||||
"session.new.lastModified": "Ostatnio zmodyfikowano",
|
"session.new.lastModified": "Ostatnio zmodyfikowano",
|
||||||
"session.header.search.placeholder": "Szukaj {{project}}",
|
"session.header.search.placeholder": "Szukaj {{project}}",
|
||||||
"session.header.searchFiles": "Szukaj plików",
|
"session.header.searchFiles": "Szukaj plików",
|
||||||
@@ -555,15 +674,18 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "Ostatnie sesje",
|
"sidebar.project.recentSessions": "Ostatnie sesje",
|
||||||
"sidebar.project.viewAllSessions": "Zobacz wszystkie sesje",
|
"sidebar.project.viewAllSessions": "Zobacz wszystkie sesje",
|
||||||
"sidebar.project.clearNotifications": "Wyczyść powiadomienia",
|
"sidebar.project.clearNotifications": "Wyczyść powiadomienia",
|
||||||
|
"sidebar.empty.title": "Brak otwartych projektów",
|
||||||
|
"sidebar.empty.description": "Otwórz projekt, aby rozpocząć",
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
"settings.section.desktop": "Pulpit",
|
"settings.section.desktop": "Pulpit",
|
||||||
"settings.section.server": "Serwer",
|
"settings.section.server": "Serwer",
|
||||||
"settings.tab.general": "Ogólne",
|
"settings.tab.general": "Ogólne",
|
||||||
"settings.tab.shortcuts": "Skróty",
|
"settings.tab.shortcuts": "Skróty",
|
||||||
"settings.desktop.section.wsl": "WSL",
|
"settings.desktop.section.wsl": "WSL",
|
||||||
"settings.desktop.wsl.title": "WSL integration",
|
"settings.desktop.wsl.title": "Integracja z WSL",
|
||||||
"settings.desktop.wsl.description": "Run the OpenCode server inside WSL on Windows.",
|
"settings.desktop.wsl.description": "Uruchamiaj serwer OpenCode wewnątrz WSL w systemie Windows.",
|
||||||
"settings.general.section.appearance": "Wygląd",
|
"settings.general.section.appearance": "Wygląd",
|
||||||
|
"settings.general.section.advanced": "Zaawansowane",
|
||||||
"settings.general.section.notifications": "Powiadomienia systemowe",
|
"settings.general.section.notifications": "Powiadomienia systemowe",
|
||||||
"settings.general.section.updates": "Aktualizacje",
|
"settings.general.section.updates": "Aktualizacje",
|
||||||
"settings.general.section.sounds": "Efekty dźwiękowe",
|
"settings.general.section.sounds": "Efekty dźwiękowe",
|
||||||
@@ -571,6 +693,11 @@ export const dict = {
|
|||||||
"settings.general.section.display": "Ekran",
|
"settings.general.section.display": "Ekran",
|
||||||
"settings.general.row.language.title": "Język",
|
"settings.general.row.language.title": "Język",
|
||||||
"settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
|
"settings.general.row.language.description": "Zmień język wyświetlania dla OpenCode",
|
||||||
|
"settings.general.row.shell.title": "Powłoka terminala",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"Wybierz powłokę używaną w terminalu. Zgodne powłoki są również używane przez wywołania narzędzi agenta.",
|
||||||
|
"settings.general.row.shell.autoDefault": "Automatycznie (domyślna)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "tylko terminal",
|
||||||
"settings.general.row.appearance.title": "Wygląd",
|
"settings.general.row.appearance.title": "Wygląd",
|
||||||
"settings.general.row.appearance.description": "Dostosuj wygląd OpenCode na swoim urządzeniu",
|
"settings.general.row.appearance.description": "Dostosuj wygląd OpenCode na swoim urządzeniu",
|
||||||
"settings.general.row.colorScheme.title": "Schemat kolorów",
|
"settings.general.row.colorScheme.title": "Schemat kolorów",
|
||||||
@@ -580,14 +707,30 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
|
"settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
|
||||||
"settings.general.row.font.title": "Czcionka kodu",
|
"settings.general.row.font.title": "Czcionka kodu",
|
||||||
"settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu",
|
"settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "Czcionka terminala",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "Dostosuj czcionkę używaną w terminalu",
|
||||||
"settings.general.row.uiFont.title": "Czcionka interfejsu",
|
"settings.general.row.uiFont.title": "Czcionka interfejsu",
|
||||||
"settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie",
|
"settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie",
|
||||||
"settings.general.row.followup.title": "Zachowanie kontynuacji",
|
"settings.general.row.followup.title": "Zachowanie kontynuacji",
|
||||||
"settings.general.row.followup.description": "Wybierz, czy kontynuacja ma być natychmiastowa, czy czekać w kolejce",
|
"settings.general.row.followup.description": "Wybierz, czy kontynuacja ma być natychmiastowa, czy czekać w kolejce",
|
||||||
"settings.general.row.followup.option.queue": "Kolejka",
|
"settings.general.row.followup.option.queue": "Kolejka",
|
||||||
"settings.general.row.followup.option.steer": "Sterowanie",
|
"settings.general.row.followup.option.steer": "Sterowanie",
|
||||||
|
"settings.general.row.showFileTree.title": "Drzewo plików",
|
||||||
|
"settings.general.row.showFileTree.description": "Pokaż panel drzewa plików w sesjach",
|
||||||
|
"settings.general.row.showNavigation.title": "Elementy nawigacji",
|
||||||
|
"settings.general.row.showNavigation.description":
|
||||||
|
"Pokaż przyciski Wstecz i Dalej na pasku tytułu aplikacji komputerowej",
|
||||||
|
"settings.general.row.showSearch.title": "Paleta poleceń",
|
||||||
|
"settings.general.row.showSearch.description": "Pokaż przycisk wyszukiwania i palety poleceń na pasku tytułu",
|
||||||
|
"settings.general.row.showTerminal.title": "Terminal",
|
||||||
|
"settings.general.row.showTerminal.description": "Pokaż przycisk terminala na pasku tytułu aplikacji komputerowej",
|
||||||
|
"settings.general.row.showStatus.title": "Status serwera",
|
||||||
|
"settings.general.row.showStatus.description": "Pokaż przycisk statusu serwera na pasku tytułu",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Dolna nawigacja",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"Umieść pasek tytułu i karty sesji u dołu ekranu na urządzeniach mobilnych",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Agenci niestandardowi",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Pokaż wybór agenta w edytorze wiadomości",
|
||||||
"settings.general.row.reasoningSummaries.title": "Pokaż podsumowania wnioskowania",
|
"settings.general.row.reasoningSummaries.title": "Pokaż podsumowania wnioskowania",
|
||||||
"settings.general.row.reasoningSummaries.description": "Wyświetlaj podsumowania wnioskowania modelu na osi czasu",
|
"settings.general.row.reasoningSummaries.description": "Wyświetlaj podsumowania wnioskowania modelu na osi czasu",
|
||||||
"settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell",
|
"settings.general.row.shellToolPartsExpanded.title": "Rozwijaj elementy narzędzia shell",
|
||||||
@@ -596,6 +739,12 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit",
|
"settings.general.row.editToolPartsExpanded.title": "Rozwijaj elementy narzędzia edit",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu",
|
"Domyślnie pokazuj rozwinięte elementy narzędzi edit, write i patch na osi czasu",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Nowy układ i wygląd",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Włącz przeprojektowany układ, stronę główną, edytor wiadomości i interfejs sesji",
|
||||||
|
"settings.general.row.pinchZoom.title": "Powiększanie gestem szczypania",
|
||||||
|
"settings.general.row.pinchZoom.description":
|
||||||
|
"Zezwalaj na powiększanie gestem szczypania na gładziku i przewijaniem z klawiszem Ctrl",
|
||||||
"settings.general.row.wayland.title": "Użyj natywnego Wayland",
|
"settings.general.row.wayland.title": "Użyj natywnego Wayland",
|
||||||
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
|
"settings.general.row.wayland.description": "Wyłącz fallback X11 na Wayland. Wymaga restartu.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
@@ -608,6 +757,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "Ręcznie sprawdź aktualizacje i zainstaluj, jeśli są dostępne",
|
"settings.updates.row.check.description": "Ręcznie sprawdź aktualizacje i zainstaluj, jeśli są dostępne",
|
||||||
"settings.updates.action.checkNow": "Sprawdź teraz",
|
"settings.updates.action.checkNow": "Sprawdź teraz",
|
||||||
"settings.updates.action.checking": "Sprawdzanie...",
|
"settings.updates.action.checking": "Sprawdzanie...",
|
||||||
|
"settings.updates.action.downloading": "Pobieranie...",
|
||||||
|
"settings.updates.action.installing": "Instalowanie...",
|
||||||
"settings.updates.toast.latest.title": "Masz najnowszą wersję",
|
"settings.updates.toast.latest.title": "Masz najnowszą wersję",
|
||||||
"settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji OpenCode.",
|
"settings.updates.toast.latest.description": "Korzystasz z najnowszej wersji OpenCode.",
|
||||||
"sound.option.none": "Brak",
|
"sound.option.none": "Brak",
|
||||||
|
|||||||
+151
-2
@@ -23,6 +23,9 @@ export const dict = {
|
|||||||
|
|
||||||
"command.sidebar.toggle": "Переключить боковую панель",
|
"command.sidebar.toggle": "Переключить боковую панель",
|
||||||
"command.project.open": "Открыть проект",
|
"command.project.open": "Открыть проект",
|
||||||
|
"command.project.previous": "Предыдущий проект",
|
||||||
|
"command.project.next": "Следующий проект",
|
||||||
|
"command.project.index": "Переключиться на проект {{index}}",
|
||||||
"command.provider.connect": "Подключить провайдера",
|
"command.provider.connect": "Подключить провайдера",
|
||||||
"command.server.switch": "Переключить сервер",
|
"command.server.switch": "Переключить сервер",
|
||||||
"command.settings.open": "Открыть настройки",
|
"command.settings.open": "Открыть настройки",
|
||||||
@@ -45,6 +48,7 @@ export const dict = {
|
|||||||
"command.session.new": "Новая сессия",
|
"command.session.new": "Новая сессия",
|
||||||
"command.file.open": "Открыть файл",
|
"command.file.open": "Открыть файл",
|
||||||
"command.tab.close": "Закрыть вкладку",
|
"command.tab.close": "Закрыть вкладку",
|
||||||
|
"command.tab.reopenClosed": "Повторно открыть закрытую вкладку",
|
||||||
"command.context.addSelection": "Добавить выделение в контекст",
|
"command.context.addSelection": "Добавить выделение в контекст",
|
||||||
"command.context.addSelection.description": "Добавить выбранные строки из текущего файла",
|
"command.context.addSelection.description": "Добавить выбранные строки из текущего файла",
|
||||||
"command.input.focus": "Фокус на поле ввода",
|
"command.input.focus": "Фокус на поле ввода",
|
||||||
@@ -208,6 +212,11 @@ export const dict = {
|
|||||||
"model.input.video": "видео",
|
"model.input.video": "видео",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "Разрешено: {{inputs}}",
|
"model.tooltip.allows": "Разрешено: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Контекст",
|
||||||
|
"model.tooltip.inputs": "Входные данные",
|
||||||
|
"model.tooltip.model": "Модель",
|
||||||
|
"model.tooltip.provider": "Провайдер",
|
||||||
|
"model.tooltip.reasoning": "Рассуждение",
|
||||||
"model.tooltip.reasoning.allowed": "Разрешает рассуждение",
|
"model.tooltip.reasoning.allowed": "Разрешает рассуждение",
|
||||||
"model.tooltip.reasoning.none": "Без рассуждения",
|
"model.tooltip.reasoning.none": "Без рассуждения",
|
||||||
"model.tooltip.context": "Лимит контекста {{limit}}",
|
"model.tooltip.context": "Лимит контекста {{limit}}",
|
||||||
@@ -218,6 +227,7 @@ export const dict = {
|
|||||||
"common.loading": "Загрузка",
|
"common.loading": "Загрузка",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "Отмена",
|
"common.cancel": "Отмена",
|
||||||
|
"common.clear": "Очистить",
|
||||||
"common.connect": "Подключить",
|
"common.connect": "Подключить",
|
||||||
"common.disconnect": "Отключить",
|
"common.disconnect": "Отключить",
|
||||||
"common.continue": "Отправить",
|
"common.continue": "Отправить",
|
||||||
@@ -235,6 +245,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Оболочка",
|
"prompt.mode.shell": "Оболочка",
|
||||||
"prompt.mode.normal": "Промпт",
|
"prompt.mode.normal": "Промпт",
|
||||||
"prompt.mode.shell.exit": "esc для выхода",
|
"prompt.mode.shell.exit": "esc для выхода",
|
||||||
|
"session.child.promptDisabled": "В сессиях субагентов нельзя отправлять запросы.",
|
||||||
|
"session.child.backToParent": "Вернуться к основной сессии.",
|
||||||
|
|
||||||
"prompt.example.1": "Исправить TODO в коде",
|
"prompt.example.1": "Исправить TODO в коде",
|
||||||
"prompt.example.2": "Какой технологический стек этого проекта?",
|
"prompt.example.2": "Какой технологический стек этого проекта?",
|
||||||
@@ -341,6 +353,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "Удалить",
|
"dialog.server.menu.delete": "Удалить",
|
||||||
"dialog.server.current": "Текущий сервер",
|
"dialog.server.current": "Текущий сервер",
|
||||||
"dialog.server.status.default": "По умолч.",
|
"dialog.server.status.default": "По умолч.",
|
||||||
|
"wsl.server.add": "Добавить сервер WSL",
|
||||||
|
"wsl.server.addShort": "Добавить WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "Сервер WSL",
|
||||||
|
"wsl.server.retryStart": "Повторить запуск",
|
||||||
|
"wsl.server.updating": "Обновление...",
|
||||||
|
"wsl.onboarding.step.distro": "Выбрать дистрибутив",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "Проверка WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "Чтобы завершить установку WSL, перезапустите Windows.",
|
||||||
|
"wsl.onboarding.ready": "WSL готова к работе.",
|
||||||
|
"wsl.onboarding.required": "Для продолжения требуется WSL.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Проверка дистрибутивов...",
|
||||||
|
"wsl.onboarding.installingDistro": "Установка {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "Проверка {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "Получение списка дистрибутивов...",
|
||||||
|
"wsl.onboarding.distroReady": "Дистрибутив {{distro}} готов к работе.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "Дистрибутив {{distro}} пока не установлен.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Откройте {{distro}} один раз, чтобы завершить настройку.",
|
||||||
|
"wsl.onboarding.finishingDistro": "Завершение настройки {{distro}}.",
|
||||||
|
"wsl.onboarding.pickDistro": "Выберите дистрибутив или установите его ниже.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "Проверка OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "Проверка OpenCode в {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "Обновление OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "Обновление OpenCode в {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "Обновите OpenCode в {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "Обновить OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode в {{distro}} готов к работе.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode готов к работе.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "Установите OpenCode в {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "Установить OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Сначала выберите дистрибутив.",
|
||||||
|
"wsl.onboarding.loadFailed": "Не удалось загрузить состояние WSL.",
|
||||||
|
"wsl.onboarding.loading": "Загрузка...",
|
||||||
|
"wsl.onboarding.installedDistros": "Установленные дистрибутивы",
|
||||||
|
"wsl.onboarding.checkAgain": "Проверить снова",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Готов",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Проверка...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode не установлен",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "Отсутствуют bash и curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "Не поддерживается · Используйте WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "Нужен другой дистрибутив?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "Установите дистрибутив Linux из каталога WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL не установлена",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"Для добавления сервера WSL в OpenCode требуется WSL (Windows Subsystem for Linux)",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL недоступна",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode не смог проверить WSL на этом компьютере.",
|
||||||
|
"wsl.onboarding.installWsl": "Установить WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"Перезапустите Windows, чтобы завершить установку WSL, затем снова откройте OpenCode.",
|
||||||
|
"wsl.onboarding.next": "Далее",
|
||||||
|
"wsl.onboarding.refresh": "Обновить",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Все установленные дистрибутивы уже добавлены.",
|
||||||
|
"wsl.onboarding.noDistros": "Дистрибутивы пока не обнаружены.",
|
||||||
|
"wsl.onboarding.install": "Установить",
|
||||||
|
"wsl.onboarding.installing": "Установка...",
|
||||||
|
"wsl.onboarding.installDistro": "Установить дистрибутив",
|
||||||
|
"wsl.onboarding.searchDistros": "Поиск дистрибутивов",
|
||||||
|
"wsl.onboarding.wsl2Required": "Требуется WSL 2.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Для этого дистрибутива нужны bash и curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "Открыть терминал",
|
||||||
|
"wsl.onboarding.path": "Путь: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "не найден",
|
||||||
|
"wsl.onboarding.version": "Версия: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "неизвестно",
|
||||||
|
"wsl.onboarding.desktopVersion": "приложение {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "Установленная версия не соответствует версии приложения.",
|
||||||
|
"wsl.onboarding.adding": "Добавление...",
|
||||||
|
|
||||||
"dialog.project.edit.title": "Редактировать проект",
|
"dialog.project.edit.title": "Редактировать проект",
|
||||||
"dialog.project.edit.name": "Название",
|
"dialog.project.edit.name": "Название",
|
||||||
@@ -405,6 +486,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
|
|
||||||
@@ -452,10 +534,12 @@ export const dict = {
|
|||||||
|
|
||||||
"error.page.title": "Что-то пошло не так",
|
"error.page.title": "Что-то пошло не так",
|
||||||
"error.page.description": "Произошла ошибка при загрузке приложения.",
|
"error.page.description": "Произошла ошибка при загрузке приложения.",
|
||||||
|
"error.page.description.localServerStartup": "Произошла ошибка при запуске локального сервера.",
|
||||||
"error.page.details.label": "Детали ошибки",
|
"error.page.details.label": "Детали ошибки",
|
||||||
"error.page.action.restart": "Перезапустить",
|
"error.page.action.restart": "Перезапустить",
|
||||||
"error.page.action.report": "Сообщить об ошибке",
|
"error.page.action.report": "Сообщить об ошибке",
|
||||||
"error.page.action.reported": "Об ошибке сообщено",
|
"error.page.action.reported": "Об ошибке сообщено",
|
||||||
|
"error.page.action.exportLogs": "Экспортировать журналы",
|
||||||
"error.page.action.checking": "Проверка...",
|
"error.page.action.checking": "Проверка...",
|
||||||
"error.page.action.checkUpdates": "Проверить обновления",
|
"error.page.action.checkUpdates": "Проверить обновления",
|
||||||
"error.page.action.updateTo": "Обновить до {{version}}",
|
"error.page.action.updateTo": "Обновить до {{version}}",
|
||||||
@@ -505,11 +589,31 @@ export const dict = {
|
|||||||
"home.recentProjects": "Недавние проекты",
|
"home.recentProjects": "Недавние проекты",
|
||||||
"home.empty.title": "Нет недавних проектов",
|
"home.empty.title": "Нет недавних проектов",
|
||||||
"home.empty.description": "Начните с открытия локального проекта",
|
"home.empty.description": "Начните с открытия локального проекта",
|
||||||
|
"home.title": "Главная",
|
||||||
|
"home.projects": "Проекты",
|
||||||
|
"home.project.add": "Добавить проект",
|
||||||
|
"home.recentlyClosed": "Недавно закрытые",
|
||||||
|
"home.server.collapse": "Свернуть проекты сервера",
|
||||||
|
"home.server.expand": "Развернуть проекты сервера",
|
||||||
|
"home.sessions.search.placeholder": "Поиск сессий",
|
||||||
|
"home.sessions.search.placeholder.scoped": "Поиск сессий в {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "Сессии",
|
||||||
|
"home.sessions.search.noResults": "Сессии по запросу {{query}} не найдены",
|
||||||
|
"home.sessions.empty": "Здесь пока ничего нет",
|
||||||
|
"home.sessions.empty.description": "Создайте сессию, чтобы начать",
|
||||||
|
"home.sessions.group.today": "Сегодня",
|
||||||
|
"home.sessions.group.yesterday": "Вчера",
|
||||||
|
"home.sessions.group.older": "Ранее",
|
||||||
|
|
||||||
"session.tab.session": "Сессия",
|
"session.tab.session": "Сессия",
|
||||||
"session.tab.review": "Обзор",
|
"session.tab.review": "Обзор",
|
||||||
"session.tab.context": "Контекст",
|
"session.tab.context": "Контекст",
|
||||||
|
"session.tab.unknown": "Неизвестная сессия",
|
||||||
"session.panel.reviewAndFiles": "Обзор и файлы",
|
"session.panel.reviewAndFiles": "Обзор и файлы",
|
||||||
|
"session.error.notFound": "Эта сессия не найдена",
|
||||||
|
"session.error.notFound.description": "Эта вкладка указывает на сессию, которой больше нет на этом сервере.",
|
||||||
|
"session.error.notFound.closeTab": "Закрыть вкладку",
|
||||||
|
"session.error.serverConnection": "Не удаётся подключиться к этому серверу",
|
||||||
"session.review.filesChanged": "{{count}} файлов изменено",
|
"session.review.filesChanged": "{{count}} файлов изменено",
|
||||||
"session.review.change.one": "Изменение",
|
"session.review.change.one": "Изменение",
|
||||||
"session.review.change.other": "Изменения",
|
"session.review.change.other": "Изменения",
|
||||||
@@ -518,6 +622,8 @@ export const dict = {
|
|||||||
"session.review.noVcs": "Система контроля версий Git не обнаружена, изменения не отображаются",
|
"session.review.noVcs": "Система контроля версий Git не обнаружена, изменения не отображаются",
|
||||||
"session.review.noSnapshot": "Отслеживание снимков отключено в настройках, поэтому изменения сессии недоступны",
|
"session.review.noSnapshot": "Отслеживание снимков отключено в настройках, поэтому изменения сессии недоступны",
|
||||||
"session.review.noChanges": "Нет изменений",
|
"session.review.noChanges": "Нет изменений",
|
||||||
|
"session.review.noUncommittedChanges": "Пока нет незафиксированных изменений",
|
||||||
|
"session.review.noBranchChanges": "Пока нет изменений в ветке",
|
||||||
"session.files.selectToOpen": "Выберите файл, чтобы открыть",
|
"session.files.selectToOpen": "Выберите файл, чтобы открыть",
|
||||||
"session.files.all": "Все файлы",
|
"session.files.all": "Все файлы",
|
||||||
"session.files.empty": "Нет файлов",
|
"session.files.empty": "Нет файлов",
|
||||||
@@ -532,6 +638,10 @@ export const dict = {
|
|||||||
"session.todo.title": "Задачи",
|
"session.todo.title": "Задачи",
|
||||||
"session.todo.collapse": "Свернуть",
|
"session.todo.collapse": "Свернуть",
|
||||||
"session.todo.expand": "Развернуть",
|
"session.todo.expand": "Развернуть",
|
||||||
|
"session.question.minimize": "Свернуть вопрос",
|
||||||
|
"session.question.restore": "Восстановить вопрос",
|
||||||
|
"session.question.pending.one": "{{count}} вопрос без ответа",
|
||||||
|
"session.question.pending.other": "Вопросы без ответа: {{count}}",
|
||||||
"session.followupDock.summary.one": "{{count}} сообщение в очереди",
|
"session.followupDock.summary.one": "{{count}} сообщение в очереди",
|
||||||
"session.followupDock.summary.other": "{{count}} сообщений в очереди",
|
"session.followupDock.summary.other": "{{count}} сообщений в очереди",
|
||||||
"session.followupDock.sendNow": "Отправить сейчас",
|
"session.followupDock.sendNow": "Отправить сейчас",
|
||||||
@@ -545,9 +655,16 @@ export const dict = {
|
|||||||
"session.revertDock.restore": "Восстановить сообщение",
|
"session.revertDock.restore": "Восстановить сообщение",
|
||||||
|
|
||||||
"session.new.title": "Создавайте что угодно",
|
"session.new.title": "Создавайте что угодно",
|
||||||
|
"session.new.project.new": "Новый проект",
|
||||||
|
"session.new.project.search": "Поиск проектов",
|
||||||
|
"session.new.project.add": "Добавить проект",
|
||||||
"session.new.worktree.main": "Основная ветка",
|
"session.new.worktree.main": "Основная ветка",
|
||||||
"session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Основная ветка ({{branch}})",
|
||||||
"session.new.worktree.create": "Создать новый worktree",
|
"session.new.worktree.create": "Создать новый worktree",
|
||||||
|
"session.new.workspace.runIn": "Запустить сессию в",
|
||||||
|
"session.new.workspace.triggerLocal": "Локально",
|
||||||
|
"session.new.workspace.local": "Локальный репозиторий",
|
||||||
|
"session.new.workspace.existing": "Рабочее пространство…",
|
||||||
"session.new.lastModified": "Последнее изменение",
|
"session.new.lastModified": "Последнее изменение",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Поиск {{project}}",
|
"session.header.search.placeholder": "Поиск {{project}}",
|
||||||
@@ -619,6 +736,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "Недавние сессии",
|
"sidebar.project.recentSessions": "Недавние сессии",
|
||||||
"sidebar.project.viewAllSessions": "Посмотреть все сессии",
|
"sidebar.project.viewAllSessions": "Посмотреть все сессии",
|
||||||
"sidebar.project.clearNotifications": "Очистить уведомления",
|
"sidebar.project.clearNotifications": "Очистить уведомления",
|
||||||
|
"sidebar.empty.title": "Нет открытых проектов",
|
||||||
|
"sidebar.empty.description": "Откройте проект, чтобы начать",
|
||||||
|
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
"settings.section.desktop": "Приложение",
|
"settings.section.desktop": "Приложение",
|
||||||
@@ -630,6 +749,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.description": "Запускать сервер OpenCode внутри WSL на Windows.",
|
"settings.desktop.wsl.description": "Запускать сервер OpenCode внутри WSL на Windows.",
|
||||||
|
|
||||||
"settings.general.section.appearance": "Внешний вид",
|
"settings.general.section.appearance": "Внешний вид",
|
||||||
|
"settings.general.section.advanced": "Дополнительно",
|
||||||
"settings.general.section.notifications": "Системные уведомления",
|
"settings.general.section.notifications": "Системные уведомления",
|
||||||
"settings.general.section.updates": "Обновления",
|
"settings.general.section.updates": "Обновления",
|
||||||
"settings.general.section.sounds": "Звуковые эффекты",
|
"settings.general.section.sounds": "Звуковые эффекты",
|
||||||
@@ -638,6 +758,11 @@ export const dict = {
|
|||||||
|
|
||||||
"settings.general.row.language.title": "Язык",
|
"settings.general.row.language.title": "Язык",
|
||||||
"settings.general.row.language.description": "Изменить язык отображения OpenCode",
|
"settings.general.row.language.description": "Изменить язык отображения OpenCode",
|
||||||
|
"settings.general.row.shell.title": "Оболочка терминала",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"Выберите оболочку для терминала. Совместимые оболочки также используются агентом при вызове инструментов.",
|
||||||
|
"settings.general.row.shell.autoDefault": "Авто (по умолчанию)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "только терминал",
|
||||||
"settings.general.row.appearance.title": "Внешний вид",
|
"settings.general.row.appearance.title": "Внешний вид",
|
||||||
"settings.general.row.appearance.description": "Настройте как OpenCode выглядит на вашем устройстве",
|
"settings.general.row.appearance.description": "Настройте как OpenCode выглядит на вашем устройстве",
|
||||||
"settings.general.row.colorScheme.title": "Цветовая схема",
|
"settings.general.row.colorScheme.title": "Цветовая схема",
|
||||||
@@ -646,8 +771,8 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "Настройте оформление OpenCode.",
|
"settings.general.row.theme.description": "Настройте оформление OpenCode.",
|
||||||
"settings.general.row.font.title": "Шрифт кода",
|
"settings.general.row.font.title": "Шрифт кода",
|
||||||
"settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода",
|
"settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "Шрифт терминала",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "Настройте шрифт, используемый в терминале",
|
||||||
"settings.general.row.uiFont.title": "Шрифт интерфейса",
|
"settings.general.row.uiFont.title": "Шрифт интерфейса",
|
||||||
"settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе",
|
"settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе",
|
||||||
"settings.general.row.followup.title": "Поведение уточняющих вопросов",
|
"settings.general.row.followup.title": "Поведение уточняющих вопросов",
|
||||||
@@ -655,6 +780,22 @@ export const dict = {
|
|||||||
"Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь",
|
"Выберите, отправлять ли уточняющие вопросы сразу или помещать их в очередь",
|
||||||
"settings.general.row.followup.option.queue": "Очередь",
|
"settings.general.row.followup.option.queue": "Очередь",
|
||||||
"settings.general.row.followup.option.steer": "Направлять",
|
"settings.general.row.followup.option.steer": "Направлять",
|
||||||
|
"settings.general.row.showFileTree.title": "Дерево файлов",
|
||||||
|
"settings.general.row.showFileTree.description": "Показывать панель дерева файлов в сессиях",
|
||||||
|
"settings.general.row.showNavigation.title": "Элементы навигации",
|
||||||
|
"settings.general.row.showNavigation.description":
|
||||||
|
"Показывать кнопки «Назад» и «Вперёд» в строке заголовка приложения",
|
||||||
|
"settings.general.row.showSearch.title": "Палитра команд",
|
||||||
|
"settings.general.row.showSearch.description": "Показывать кнопку поиска и палитры команд в строке заголовка",
|
||||||
|
"settings.general.row.showTerminal.title": "Терминал",
|
||||||
|
"settings.general.row.showTerminal.description": "Показывать кнопку терминала в строке заголовка приложения",
|
||||||
|
"settings.general.row.showStatus.title": "Состояние сервера",
|
||||||
|
"settings.general.row.showStatus.description": "Показывать кнопку состояния сервера в строке заголовка",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Нижняя навигация",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"На мобильных устройствах размещать строку заголовка и вкладки сессии внизу экрана",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Пользовательские агенты",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Показывать выбор агента в редакторе запросов",
|
||||||
"settings.general.row.reasoningSummaries.title": "Показывать сводки рассуждений",
|
"settings.general.row.reasoningSummaries.title": "Показывать сводки рассуждений",
|
||||||
"settings.general.row.reasoningSummaries.description": "Отображать сводки рассуждений модели в ленте",
|
"settings.general.row.reasoningSummaries.description": "Отображать сводки рассуждений модели в ленте",
|
||||||
|
|
||||||
@@ -664,6 +805,12 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit",
|
"settings.general.row.editToolPartsExpanded.title": "Разворачивать элементы инструмента edit",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию",
|
"Показывать элементы инструментов edit, write и patch в ленте развернутыми по умолчанию",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Новая компоновка и оформление",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Включить обновлённую компоновку, главную страницу, редактор запросов и интерфейс сессии",
|
||||||
|
"settings.general.row.pinchZoom.title": "Масштабирование щипком",
|
||||||
|
"settings.general.row.pinchZoom.description":
|
||||||
|
"Разрешить масштабирование жестом щипка на трекпаде и прокруткой с Ctrl",
|
||||||
"settings.general.row.wayland.title": "Использовать нативный Wayland",
|
"settings.general.row.wayland.title": "Использовать нативный Wayland",
|
||||||
"settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
|
"settings.general.row.wayland.description": "Отключить X11 fallback на Wayland. Требуется перезапуск.",
|
||||||
"settings.general.row.wayland.tooltip":
|
"settings.general.row.wayland.tooltip":
|
||||||
@@ -678,6 +825,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "Проверить обновления вручную и установить, если доступны",
|
"settings.updates.row.check.description": "Проверить обновления вручную и установить, если доступны",
|
||||||
"settings.updates.action.checkNow": "Проверить сейчас",
|
"settings.updates.action.checkNow": "Проверить сейчас",
|
||||||
"settings.updates.action.checking": "Проверка...",
|
"settings.updates.action.checking": "Проверка...",
|
||||||
|
"settings.updates.action.downloading": "Загрузка...",
|
||||||
|
"settings.updates.action.installing": "Установка...",
|
||||||
"settings.updates.toast.latest.title": "У вас последняя версия",
|
"settings.updates.toast.latest.title": "У вас последняя версия",
|
||||||
"settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.",
|
"settings.updates.toast.latest.description": "Вы используете последнюю версию OpenCode.",
|
||||||
"sound.option.none": "Нет",
|
"sound.option.none": "Нет",
|
||||||
|
|||||||
+154
-7
@@ -23,6 +23,9 @@ export const dict = {
|
|||||||
|
|
||||||
"command.sidebar.toggle": "สลับแถบข้าง",
|
"command.sidebar.toggle": "สลับแถบข้าง",
|
||||||
"command.project.open": "เปิดโปรเจกต์",
|
"command.project.open": "เปิดโปรเจกต์",
|
||||||
|
"command.project.previous": "โปรเจกต์ก่อนหน้า",
|
||||||
|
"command.project.next": "โปรเจกต์ถัดไป",
|
||||||
|
"command.project.index": "สลับไปยังโปรเจกต์ {{index}}",
|
||||||
"command.provider.connect": "เชื่อมต่อผู้ให้บริการ",
|
"command.provider.connect": "เชื่อมต่อผู้ให้บริการ",
|
||||||
"command.server.switch": "สลับเซิร์ฟเวอร์",
|
"command.server.switch": "สลับเซิร์ฟเวอร์",
|
||||||
"command.settings.open": "เปิดการตั้งค่า",
|
"command.settings.open": "เปิดการตั้งค่า",
|
||||||
@@ -32,7 +35,7 @@ export const dict = {
|
|||||||
"command.session.next.unseen": "เซสชันที่ยังไม่ได้อ่านถัดไป",
|
"command.session.next.unseen": "เซสชันที่ยังไม่ได้อ่านถัดไป",
|
||||||
"command.session.archive": "จัดเก็บเซสชัน",
|
"command.session.archive": "จัดเก็บเซสชัน",
|
||||||
|
|
||||||
"command.palette": "คำสั่งค้นหา",
|
"command.palette": "ชุดคำสั่ง",
|
||||||
|
|
||||||
"command.theme.cycle": "เปลี่ยนธีม",
|
"command.theme.cycle": "เปลี่ยนธีม",
|
||||||
"command.theme.set": "ใช้ธีม: {{theme}}",
|
"command.theme.set": "ใช้ธีม: {{theme}}",
|
||||||
@@ -45,6 +48,7 @@ export const dict = {
|
|||||||
"command.session.new": "เซสชันใหม่",
|
"command.session.new": "เซสชันใหม่",
|
||||||
"command.file.open": "เปิดไฟล์",
|
"command.file.open": "เปิดไฟล์",
|
||||||
"command.tab.close": "ปิดแท็บ",
|
"command.tab.close": "ปิดแท็บ",
|
||||||
|
"command.tab.reopenClosed": "เปิดแท็บที่ปิดไปอีกครั้ง",
|
||||||
"command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท",
|
"command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท",
|
||||||
"command.context.addSelection.description": "เพิ่มบรรทัดที่เลือกจากไฟล์ปัจจุบัน",
|
"command.context.addSelection.description": "เพิ่มบรรทัดที่เลือกจากไฟล์ปัจจุบัน",
|
||||||
"command.input.focus": "โฟกัสช่องป้อนข้อมูล",
|
"command.input.focus": "โฟกัสช่องป้อนข้อมูล",
|
||||||
@@ -163,7 +167,7 @@ export const dict = {
|
|||||||
"provider.custom.field.providerID.placeholder": "myprovider",
|
"provider.custom.field.providerID.placeholder": "myprovider",
|
||||||
"provider.custom.field.providerID.description": "ตัวอักษรพิมพ์เล็ก ตัวเลข ยัติภังค์ หรือขีดล่าง",
|
"provider.custom.field.providerID.description": "ตัวอักษรพิมพ์เล็ก ตัวเลข ยัติภังค์ หรือขีดล่าง",
|
||||||
"provider.custom.field.name.label": "ชื่อที่แสดง",
|
"provider.custom.field.name.label": "ชื่อที่แสดง",
|
||||||
"provider.custom.field.name.placeholder": "My AI Provider",
|
"provider.custom.field.name.placeholder": "ผู้ให้บริการ AI ของฉัน",
|
||||||
"provider.custom.field.baseURL.label": "URL พื้นฐาน",
|
"provider.custom.field.baseURL.label": "URL พื้นฐาน",
|
||||||
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
|
"provider.custom.field.baseURL.placeholder": "https://api.myprovider.com/v1",
|
||||||
"provider.custom.field.apiKey.label": "คีย์ API",
|
"provider.custom.field.apiKey.label": "คีย์ API",
|
||||||
@@ -208,6 +212,11 @@ export const dict = {
|
|||||||
"model.input.video": "วิดีโอ",
|
"model.input.video": "วิดีโอ",
|
||||||
"model.input.pdf": "PDF",
|
"model.input.pdf": "PDF",
|
||||||
"model.tooltip.allows": "อนุญาต: {{inputs}}",
|
"model.tooltip.allows": "อนุญาต: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "บริบท",
|
||||||
|
"model.tooltip.inputs": "ข้อมูลนำเข้า",
|
||||||
|
"model.tooltip.model": "โมเดล",
|
||||||
|
"model.tooltip.provider": "ผู้ให้บริการ",
|
||||||
|
"model.tooltip.reasoning": "การใช้เหตุผล",
|
||||||
"model.tooltip.reasoning.allowed": "อนุญาตการใช้เหตุผล",
|
"model.tooltip.reasoning.allowed": "อนุญาตการใช้เหตุผล",
|
||||||
"model.tooltip.reasoning.none": "ไม่มีการใช้เหตุผล",
|
"model.tooltip.reasoning.none": "ไม่มีการใช้เหตุผล",
|
||||||
"model.tooltip.context": "ขีดจำกัดบริบท {{limit}}",
|
"model.tooltip.context": "ขีดจำกัดบริบท {{limit}}",
|
||||||
@@ -218,6 +227,7 @@ export const dict = {
|
|||||||
"common.loading": "กำลังโหลด",
|
"common.loading": "กำลังโหลด",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "ยกเลิก",
|
"common.cancel": "ยกเลิก",
|
||||||
|
"common.clear": "ล้าง",
|
||||||
"common.connect": "เชื่อมต่อ",
|
"common.connect": "เชื่อมต่อ",
|
||||||
"common.disconnect": "ยกเลิกการเชื่อมต่อ",
|
"common.disconnect": "ยกเลิกการเชื่อมต่อ",
|
||||||
"common.continue": "ส่ง",
|
"common.continue": "ส่ง",
|
||||||
@@ -235,6 +245,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "เชลล์",
|
"prompt.mode.shell": "เชลล์",
|
||||||
"prompt.mode.normal": "พรอมต์",
|
"prompt.mode.normal": "พรอมต์",
|
||||||
"prompt.mode.shell.exit": "กด esc เพื่อออก",
|
"prompt.mode.shell.exit": "กด esc เพื่อออก",
|
||||||
|
"session.child.promptDisabled": "ไม่สามารถส่งพรอมต์ในเซสชันเอเจนต์ย่อยได้.",
|
||||||
|
"session.child.backToParent": "กลับไปยังเซสชันหลัก.",
|
||||||
|
|
||||||
"prompt.example.1": "แก้ไข TODO ในโค้ดเบส",
|
"prompt.example.1": "แก้ไข TODO ในโค้ดเบส",
|
||||||
"prompt.example.2": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?",
|
"prompt.example.2": "เทคโนโลยีของโปรเจกต์นี้คืออะไร?",
|
||||||
@@ -341,6 +353,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "ลบ",
|
"dialog.server.menu.delete": "ลบ",
|
||||||
"dialog.server.current": "เซิร์ฟเวอร์ปัจจุบัน",
|
"dialog.server.current": "เซิร์ฟเวอร์ปัจจุบัน",
|
||||||
"dialog.server.status.default": "ค่าเริ่มต้น",
|
"dialog.server.status.default": "ค่าเริ่มต้น",
|
||||||
|
"wsl.server.add": "เพิ่มเซิร์ฟเวอร์ WSL",
|
||||||
|
"wsl.server.addShort": "เพิ่ม WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "เซิร์ฟเวอร์ WSL",
|
||||||
|
"wsl.server.retryStart": "ลองเริ่มอีกครั้ง",
|
||||||
|
"wsl.server.updating": "กำลังอัปเดต...",
|
||||||
|
"wsl.onboarding.step.distro": "เลือกดิสโทร",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "กำลังตรวจสอบ WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "ต้องรีสตาร์ท Windows เพื่อให้การติดตั้ง WSL เสร็จสมบูรณ์.",
|
||||||
|
"wsl.onboarding.ready": "WSL พร้อมใช้งานแล้ว.",
|
||||||
|
"wsl.onboarding.required": "ต้องมี WSL เพื่อดำเนินการต่อ.",
|
||||||
|
"wsl.onboarding.checkingDistros": "กำลังตรวจสอบดิสโทร...",
|
||||||
|
"wsl.onboarding.installingDistro": "กำลังติดตั้ง {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "กำลังตรวจสอบ {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "กำลังแสดงรายการดิสโทร...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} พร้อมใช้งานแล้ว.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "ยังไม่ได้ติดตั้ง {{distro}}.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "เปิด {{distro}} หนึ่งครั้งเพื่อให้การตั้งค่าเสร็จสมบูรณ์.",
|
||||||
|
"wsl.onboarding.finishingDistro": "กำลังตั้งค่า {{distro}} ให้เสร็จสมบูรณ์.",
|
||||||
|
"wsl.onboarding.pickDistro": "เลือกดิสโทรหรือติดตั้งจากรายการด้านล่าง.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "กำลังตรวจสอบ OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "กำลังตรวจสอบ OpenCode ใน {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "กำลังอัปเดต OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "กำลังอัปเดต OpenCode ใน {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "อัปเดต OpenCode ใน {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "อัปเดต OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode พร้อมใช้งานใน {{distro}} แล้ว.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode พร้อมใช้งานแล้ว.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "ติดตั้ง OpenCode ใน {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "ติดตั้ง OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "เลือกดิสโทรก่อน.",
|
||||||
|
"wsl.onboarding.loadFailed": "ไม่สามารถโหลดสถานะ WSL ได้.",
|
||||||
|
"wsl.onboarding.loading": "กำลังโหลด...",
|
||||||
|
"wsl.onboarding.installedDistros": "ดิสโทรที่ติดตั้งแล้ว",
|
||||||
|
"wsl.onboarding.checkAgain": "ตรวจสอบอีกครั้ง",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "พร้อมใช้งาน",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "กำลังตรวจสอบ...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "ยังไม่ได้ติดตั้ง OpenCode",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "ไม่มี bash, curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "ไม่รองรับ · ใช้ WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "ต้องการดิสโทรอื่น?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "ติดตั้งดิสทริบิวชัน Linux จากแค็ตตาล็อก WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "ยังไม่ได้ติดตั้ง WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"ต้องมี WSL (Windows Subsystem for Linux) ก่อนที่ OpenCode จะเพิ่มเซิร์ฟเวอร์ WSL ได้",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL ไม่พร้อมใช้งาน",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode ไม่สามารถตรวจสอบ WSL บนเครื่องนี้ได้.",
|
||||||
|
"wsl.onboarding.installWsl": "ติดตั้ง WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"รีสตาร์ท Windows เพื่อให้การติดตั้ง WSL เสร็จสมบูรณ์ แล้วเปิด OpenCode อีกครั้ง.",
|
||||||
|
"wsl.onboarding.next": "ถัดไป",
|
||||||
|
"wsl.onboarding.refresh": "รีเฟรช",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "เพิ่มดิสโทรที่ติดตั้งไว้ทั้งหมดแล้ว.",
|
||||||
|
"wsl.onboarding.noDistros": "ยังไม่พบดิสโทร.",
|
||||||
|
"wsl.onboarding.install": "ติดตั้ง",
|
||||||
|
"wsl.onboarding.installing": "กำลังติดตั้ง...",
|
||||||
|
"wsl.onboarding.installDistro": "ติดตั้งดิสโทร",
|
||||||
|
"wsl.onboarding.searchDistros": "ค้นหาดิสโทร",
|
||||||
|
"wsl.onboarding.wsl2Required": "ต้องใช้ WSL 2.",
|
||||||
|
"wsl.onboarding.toolsRequired": "ดิสโทรนี้ต้องมี bash และ curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "เปิดเทอร์มินัล",
|
||||||
|
"wsl.onboarding.path": "เส้นทาง: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "ไม่พบ",
|
||||||
|
"wsl.onboarding.version": "เวอร์ชัน: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "ไม่ทราบ",
|
||||||
|
"wsl.onboarding.desktopVersion": "เดสก์ท็อป {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "เวอร์ชันที่ติดตั้งไม่ตรงกับเวอร์ชันของแอปเดสก์ท็อป.",
|
||||||
|
"wsl.onboarding.adding": "กำลังเพิ่ม...",
|
||||||
|
|
||||||
"dialog.project.edit.title": "แก้ไขโปรเจกต์",
|
"dialog.project.edit.title": "แก้ไขโปรเจกต์",
|
||||||
"dialog.project.edit.name": "ชื่อ",
|
"dialog.project.edit.name": "ชื่อ",
|
||||||
@@ -403,6 +484,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
|
|
||||||
@@ -451,10 +533,12 @@ export const dict = {
|
|||||||
|
|
||||||
"error.page.title": "เกิดข้อผิดพลาด",
|
"error.page.title": "เกิดข้อผิดพลาด",
|
||||||
"error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน",
|
"error.page.description": "เกิดข้อผิดพลาดระหว่างการโหลดแอปพลิเคชัน",
|
||||||
|
"error.page.description.localServerStartup": "เกิดข้อผิดพลาดขณะเริ่มเซิร์ฟเวอร์ในเครื่อง.",
|
||||||
"error.page.details.label": "รายละเอียดข้อผิดพลาด",
|
"error.page.details.label": "รายละเอียดข้อผิดพลาด",
|
||||||
"error.page.action.restart": "รีสตาร์ท",
|
"error.page.action.restart": "รีสตาร์ท",
|
||||||
"error.page.action.report": "รายงานข้อผิดพลาด",
|
"error.page.action.report": "รายงานข้อผิดพลาด",
|
||||||
"error.page.action.reported": "รายงานข้อผิดพลาดแล้ว",
|
"error.page.action.reported": "รายงานข้อผิดพลาดแล้ว",
|
||||||
|
"error.page.action.exportLogs": "ส่งออกบันทึก",
|
||||||
"error.page.action.checking": "กำลังตรวจสอบ...",
|
"error.page.action.checking": "กำลังตรวจสอบ...",
|
||||||
"error.page.action.checkUpdates": "ตรวจสอบการอัปเดต",
|
"error.page.action.checkUpdates": "ตรวจสอบการอัปเดต",
|
||||||
"error.page.action.updateTo": "อัปเดตเป็น {{version}}",
|
"error.page.action.updateTo": "อัปเดตเป็น {{version}}",
|
||||||
@@ -500,11 +584,31 @@ export const dict = {
|
|||||||
"home.recentProjects": "โปรเจกต์ล่าสุด",
|
"home.recentProjects": "โปรเจกต์ล่าสุด",
|
||||||
"home.empty.title": "ไม่มีโปรเจกต์ล่าสุด",
|
"home.empty.title": "ไม่มีโปรเจกต์ล่าสุด",
|
||||||
"home.empty.description": "เริ่มต้นโดยเปิดโปรเจกต์ในเครื่อง",
|
"home.empty.description": "เริ่มต้นโดยเปิดโปรเจกต์ในเครื่อง",
|
||||||
|
"home.title": "หน้าหลัก",
|
||||||
|
"home.projects": "โปรเจกต์",
|
||||||
|
"home.project.add": "เพิ่มโปรเจกต์",
|
||||||
|
"home.recentlyClosed": "ที่ปิดไปล่าสุด",
|
||||||
|
"home.server.collapse": "ย่อโปรเจกต์ของเซิร์ฟเวอร์",
|
||||||
|
"home.server.expand": "ขยายโปรเจกต์ของเซิร์ฟเวอร์",
|
||||||
|
"home.sessions.search.placeholder": "ค้นหาเซสชัน",
|
||||||
|
"home.sessions.search.placeholder.scoped": "ค้นหาเซสชันใน {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "เซสชัน",
|
||||||
|
"home.sessions.search.noResults": "ไม่พบเซสชันสำหรับ {{query}}",
|
||||||
|
"home.sessions.empty": "ยังไม่มีอะไรที่นี่",
|
||||||
|
"home.sessions.empty.description": "สร้างเซสชันเพื่อเริ่มต้น",
|
||||||
|
"home.sessions.group.today": "วันนี้",
|
||||||
|
"home.sessions.group.yesterday": "เมื่อวาน",
|
||||||
|
"home.sessions.group.older": "ก่อนหน้านี้",
|
||||||
|
|
||||||
"session.tab.session": "เซสชัน",
|
"session.tab.session": "เซสชัน",
|
||||||
"session.tab.review": "ตรวจสอบ",
|
"session.tab.review": "ตรวจสอบ",
|
||||||
"session.tab.context": "บริบท",
|
"session.tab.context": "บริบท",
|
||||||
|
"session.tab.unknown": "เซสชันที่ไม่รู้จัก",
|
||||||
"session.panel.reviewAndFiles": "ตรวจสอบและไฟล์",
|
"session.panel.reviewAndFiles": "ตรวจสอบและไฟล์",
|
||||||
|
"session.error.notFound": "ไม่พบเซสชันนี้",
|
||||||
|
"session.error.notFound.description": "แท็บนี้อ้างถึงเซสชันที่ไม่มีอยู่บนเซิร์ฟเวอร์นี้แล้ว.",
|
||||||
|
"session.error.notFound.closeTab": "ปิดแท็บ",
|
||||||
|
"session.error.serverConnection": "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์นี้",
|
||||||
"session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง",
|
"session.review.filesChanged": "{{count}} ไฟล์ที่เปลี่ยนแปลง",
|
||||||
"session.review.change.one": "การเปลี่ยนแปลง",
|
"session.review.change.one": "การเปลี่ยนแปลง",
|
||||||
"session.review.change.other": "การเปลี่ยนแปลง",
|
"session.review.change.other": "การเปลี่ยนแปลง",
|
||||||
@@ -513,6 +617,8 @@ export const dict = {
|
|||||||
"session.review.noVcs": "ไม่ตรวจพบระบบควบคุมเวอร์ชัน Git การเปลี่ยนแปลงจะไม่แสดง",
|
"session.review.noVcs": "ไม่ตรวจพบระบบควบคุมเวอร์ชัน Git การเปลี่ยนแปลงจะไม่แสดง",
|
||||||
"session.review.noSnapshot": "การติดตามสแนปชอตถูกปิดใช้งานในการกำหนดค่า ดังนั้นการเปลี่ยนแปลงเซสชันจึงไม่พร้อมใช้งาน",
|
"session.review.noSnapshot": "การติดตามสแนปชอตถูกปิดใช้งานในการกำหนดค่า ดังนั้นการเปลี่ยนแปลงเซสชันจึงไม่พร้อมใช้งาน",
|
||||||
"session.review.noChanges": "ไม่มีการเปลี่ยนแปลง",
|
"session.review.noChanges": "ไม่มีการเปลี่ยนแปลง",
|
||||||
|
"session.review.noUncommittedChanges": "ยังไม่มีการเปลี่ยนแปลงที่รอคอมมิต",
|
||||||
|
"session.review.noBranchChanges": "ยังไม่มีการเปลี่ยนแปลงในสาขา",
|
||||||
|
|
||||||
"session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด",
|
"session.files.selectToOpen": "เลือกไฟล์เพื่อเปิด",
|
||||||
"session.files.empty": "ไม่มีไฟล์",
|
"session.files.empty": "ไม่มีไฟล์",
|
||||||
@@ -529,6 +635,10 @@ export const dict = {
|
|||||||
"session.todo.title": "สิ่งที่ต้องทำ",
|
"session.todo.title": "สิ่งที่ต้องทำ",
|
||||||
"session.todo.collapse": "ย่อ",
|
"session.todo.collapse": "ย่อ",
|
||||||
"session.todo.expand": "ขยาย",
|
"session.todo.expand": "ขยาย",
|
||||||
|
"session.question.minimize": "ย่อคำถาม",
|
||||||
|
"session.question.restore": "คืนค่าคำถาม",
|
||||||
|
"session.question.pending.one": "คำถามที่รอดำเนินการ {{count}} ข้อ",
|
||||||
|
"session.question.pending.other": "คำถามที่รอดำเนินการ {{count}} ข้อ",
|
||||||
"session.followupDock.summary.one": "{{count}} ข้อความในคิว",
|
"session.followupDock.summary.one": "{{count}} ข้อความในคิว",
|
||||||
"session.followupDock.summary.other": "{{count}} ข้อความในคิว",
|
"session.followupDock.summary.other": "{{count}} ข้อความในคิว",
|
||||||
"session.followupDock.sendNow": "ส่งทันที",
|
"session.followupDock.sendNow": "ส่งทันที",
|
||||||
@@ -542,9 +652,16 @@ export const dict = {
|
|||||||
"session.revertDock.restore": "กู้คืนข้อความ",
|
"session.revertDock.restore": "กู้คืนข้อความ",
|
||||||
|
|
||||||
"session.new.title": "สร้างอะไรก็ได้",
|
"session.new.title": "สร้างอะไรก็ได้",
|
||||||
|
"session.new.project.new": "โปรเจกต์ใหม่",
|
||||||
|
"session.new.project.search": "ค้นหาโปรเจกต์",
|
||||||
|
"session.new.project.add": "เพิ่มโปรเจกต์",
|
||||||
"session.new.worktree.main": "สาขาหลัก",
|
"session.new.worktree.main": "สาขาหลัก",
|
||||||
"session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})",
|
"session.new.worktree.mainWithBranch": "สาขาหลัก ({{branch}})",
|
||||||
"session.new.worktree.create": "สร้าง worktree ใหม่",
|
"session.new.worktree.create": "สร้าง worktree ใหม่",
|
||||||
|
"session.new.workspace.runIn": "เรียกใช้เซสชันใน",
|
||||||
|
"session.new.workspace.triggerLocal": "ในเครื่อง",
|
||||||
|
"session.new.workspace.local": "รีโพซิทอรีในเครื่อง",
|
||||||
|
"session.new.workspace.existing": "พื้นที่ทำงาน…",
|
||||||
"session.new.lastModified": "แก้ไขล่าสุด",
|
"session.new.lastModified": "แก้ไขล่าสุด",
|
||||||
|
|
||||||
"session.header.search.placeholder": "ค้นหา {{project}}",
|
"session.header.search.placeholder": "ค้นหา {{project}}",
|
||||||
@@ -612,6 +729,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "เซสชันล่าสุด",
|
"sidebar.project.recentSessions": "เซสชันล่าสุด",
|
||||||
"sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด",
|
"sidebar.project.viewAllSessions": "ดูเซสชันทั้งหมด",
|
||||||
"sidebar.project.clearNotifications": "ล้างการแจ้งเตือน",
|
"sidebar.project.clearNotifications": "ล้างการแจ้งเตือน",
|
||||||
|
"sidebar.empty.title": "ไม่มีโปรเจกต์ที่เปิดอยู่",
|
||||||
|
"sidebar.empty.description": "เปิดโปรเจกต์เพื่อเริ่มต้น",
|
||||||
|
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
|
|
||||||
@@ -624,6 +743,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ OpenCode ภายใน WSL บน Windows",
|
"settings.desktop.wsl.description": "เรียกใช้เซิร์ฟเวอร์ OpenCode ภายใน WSL บน Windows",
|
||||||
|
|
||||||
"settings.general.section.appearance": "รูปลักษณ์",
|
"settings.general.section.appearance": "รูปลักษณ์",
|
||||||
|
"settings.general.section.advanced": "ขั้นสูง",
|
||||||
"settings.general.section.notifications": "การแจ้งเตือนระบบ",
|
"settings.general.section.notifications": "การแจ้งเตือนระบบ",
|
||||||
"settings.general.section.updates": "การอัปเดต",
|
"settings.general.section.updates": "การอัปเดต",
|
||||||
"settings.general.section.sounds": "เสียงเอฟเฟกต์",
|
"settings.general.section.sounds": "เสียงเอฟเฟกต์",
|
||||||
@@ -632,6 +752,11 @@ export const dict = {
|
|||||||
|
|
||||||
"settings.general.row.language.title": "ภาษา",
|
"settings.general.row.language.title": "ภาษา",
|
||||||
"settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode",
|
"settings.general.row.language.description": "เปลี่ยนภาษาที่แสดงสำหรับ OpenCode",
|
||||||
|
"settings.general.row.shell.title": "เชลล์เทอร์มินัล",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"เลือกเชลล์ที่จะใช้กับเทอร์มินัล เชลล์ที่เข้ากันได้จะใช้สำหรับการเรียกเครื่องมือของเอเจนต์ด้วย",
|
||||||
|
"settings.general.row.shell.autoDefault": "อัตโนมัติ (ค่าเริ่มต้น)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "เทอร์มินัลเท่านั้น",
|
||||||
"settings.general.row.appearance.title": "รูปลักษณ์",
|
"settings.general.row.appearance.title": "รูปลักษณ์",
|
||||||
"settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ",
|
"settings.general.row.appearance.description": "ปรับแต่งวิธีการที่ OpenCode มีลักษณะบนอุปกรณ์ของคุณ",
|
||||||
"settings.general.row.colorScheme.title": "โทนสี",
|
"settings.general.row.colorScheme.title": "โทนสี",
|
||||||
@@ -640,14 +765,29 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
|
"settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
|
||||||
"settings.general.row.font.title": "ฟอนต์โค้ด",
|
"settings.general.row.font.title": "ฟอนต์โค้ด",
|
||||||
"settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด",
|
"settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "ฟอนต์เทอร์มินัล",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "ปรับแต่งฟอนต์ที่ใช้ในเทอร์มินัล",
|
||||||
"settings.general.row.uiFont.title": "ฟอนต์ UI",
|
"settings.general.row.uiFont.title": "ฟอนต์ UI",
|
||||||
"settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ",
|
"settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ",
|
||||||
"settings.general.row.followup.title": "พฤติกรรมการติดตามผล",
|
"settings.general.row.followup.title": "พฤติกรรมการติดตามผล",
|
||||||
"settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว",
|
"settings.general.row.followup.description": "เลือกว่าจะให้พร้อมท์ติดตามผลทำงานทันทีหรือรอในคิว",
|
||||||
"settings.general.row.followup.option.queue": "คิว",
|
"settings.general.row.followup.option.queue": "คิว",
|
||||||
"settings.general.row.followup.option.steer": "นำทาง",
|
"settings.general.row.followup.option.steer": "นำทาง",
|
||||||
|
"settings.general.row.showFileTree.title": "ต้นไม้ไฟล์",
|
||||||
|
"settings.general.row.showFileTree.description": "แสดงแผงต้นไม้ไฟล์ในเซสชัน",
|
||||||
|
"settings.general.row.showNavigation.title": "ตัวควบคุมการนำทาง",
|
||||||
|
"settings.general.row.showNavigation.description": "แสดงปุ่มย้อนกลับและไปข้างหน้าในแถบชื่อเรื่องเดสก์ท็อป",
|
||||||
|
"settings.general.row.showSearch.title": "ชุดคำสั่ง",
|
||||||
|
"settings.general.row.showSearch.description": "แสดงปุ่มค้นหาและชุดคำสั่งในแถบชื่อเรื่อง",
|
||||||
|
"settings.general.row.showTerminal.title": "เทอร์มินัล",
|
||||||
|
"settings.general.row.showTerminal.description": "แสดงปุ่มเทอร์มินัลในแถบชื่อเรื่องเดสก์ท็อป",
|
||||||
|
"settings.general.row.showStatus.title": "สถานะเซิร์ฟเวอร์",
|
||||||
|
"settings.general.row.showStatus.description": "แสดงปุ่มสถานะเซิร์ฟเวอร์ในแถบชื่อเรื่อง",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "การนำทางด้านล่าง",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"วางแถบชื่อเรื่องและแท็บเซสชันไว้ด้านล่างของหน้าจอบนอุปกรณ์เคลื่อนที่",
|
||||||
|
"settings.general.row.showCustomAgents.title": "เอเจนต์ที่กำหนดเอง",
|
||||||
|
"settings.general.row.showCustomAgents.description": "แสดงตัวเลือกเอเจนต์ในช่องเขียนข้อความ",
|
||||||
"settings.general.row.reasoningSummaries.title": "แสดงสรุปการใช้เหตุผล",
|
"settings.general.row.reasoningSummaries.title": "แสดงสรุปการใช้เหตุผล",
|
||||||
"settings.general.row.reasoningSummaries.description": "แสดงสรุปการใช้เหตุผลของโมเดลในไทม์ไลน์",
|
"settings.general.row.reasoningSummaries.description": "แสดงสรุปการใช้เหตุผลของโมเดลในไทม์ไลน์",
|
||||||
"settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell",
|
"settings.general.row.shellToolPartsExpanded.title": "ขยายส่วนเครื่องมือ shell",
|
||||||
@@ -655,6 +795,11 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit",
|
"settings.general.row.editToolPartsExpanded.title": "ขยายส่วนเครื่องมือ edit",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
|
"แสดงส่วนเครื่องมือ edit, write และ patch แบบขยายตามค่าเริ่มต้นในไทม์ไลน์",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "เลย์เอาต์และดีไซน์ใหม่",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"เปิดใช้เลย์เอาต์ หน้าแรก ช่องเขียนข้อความ และ UI เซสชันที่ออกแบบใหม่",
|
||||||
|
"settings.general.row.pinchZoom.title": "บีบนิ้วเพื่อซูม",
|
||||||
|
"settings.general.row.pinchZoom.description": "อนุญาตให้ใช้ท่าบีบนิ้วบนแทร็คแพดและ Ctrl-scroll เพื่อซูม",
|
||||||
"settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ",
|
"settings.general.row.wayland.title": "ใช้ Wayland แบบเนทีฟ",
|
||||||
"settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
|
"settings.general.row.wayland.description": "ปิดใช้งาน X11 fallback บน Wayland ต้องรีสตาร์ท",
|
||||||
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
|
"settings.general.row.wayland.tooltip": "บน Linux ที่มีจอภาพรีเฟรชเรตแบบผสม Wayland แบบเนทีฟอาจเสถียรกว่า",
|
||||||
@@ -668,6 +813,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "ตรวจสอบการอัปเดตด้วยตนเองและติดตั้งหากมี",
|
"settings.updates.row.check.description": "ตรวจสอบการอัปเดตด้วยตนเองและติดตั้งหากมี",
|
||||||
"settings.updates.action.checkNow": "ตรวจสอบทันที",
|
"settings.updates.action.checkNow": "ตรวจสอบทันที",
|
||||||
"settings.updates.action.checking": "กำลังตรวจสอบ...",
|
"settings.updates.action.checking": "กำลังตรวจสอบ...",
|
||||||
|
"settings.updates.action.downloading": "กำลังดาวน์โหลด...",
|
||||||
|
"settings.updates.action.installing": "กำลังติดตั้ง...",
|
||||||
"settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว",
|
"settings.updates.toast.latest.title": "คุณเป็นเวอร์ชันล่าสุดแล้ว",
|
||||||
"settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด",
|
"settings.updates.toast.latest.description": "คุณกำลังใช้งาน OpenCode เวอร์ชันล่าสุด",
|
||||||
|
|
||||||
@@ -857,10 +1004,10 @@ export const dict = {
|
|||||||
"dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้",
|
"dialog.server.add.usernamePlaceholder": "ชื่อผู้ใช้",
|
||||||
"dialog.server.add.passwordPlaceholder": "รหัสผ่าน",
|
"dialog.server.add.passwordPlaceholder": "รหัสผ่าน",
|
||||||
"server.row.noUsername": "ไม่มีชื่อผู้ใช้",
|
"server.row.noUsername": "ไม่มีชื่อผู้ใช้",
|
||||||
"session.review.noVcs.createGit.title": "สร้าง Git repository",
|
"session.review.noVcs.createGit.title": "สร้าง Git รีโพซิทอรี",
|
||||||
"session.review.noVcs.createGit.description": "ติดตาม ตรวจสอบ และเลิกทำสิ่งเปลี่ยนแปลงในโปรเจกต์นี้",
|
"session.review.noVcs.createGit.description": "ติดตาม ตรวจสอบ และเลิกทำสิ่งเปลี่ยนแปลงในโปรเจกต์นี้",
|
||||||
"session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git repository...",
|
"session.review.noVcs.createGit.actionLoading": "กำลังสร้าง Git รีโพซิทอรี...",
|
||||||
"session.review.noVcs.createGit.action": "สร้าง Git repository",
|
"session.review.noVcs.createGit.action": "สร้าง Git รีโพซิทอรี",
|
||||||
"session.todo.progress": "เสร็จสิ้น {{done}} จาก {{total}} รายการ",
|
"session.todo.progress": "เสร็จสิ้น {{done}} จาก {{total}} รายการ",
|
||||||
"session.question.progress": "{{current}} จาก {{total}} คำถาม",
|
"session.question.progress": "{{current}} จาก {{total}} คำถาม",
|
||||||
"session.header.open.finder": "Finder",
|
"session.header.open.finder": "Finder",
|
||||||
|
|||||||
+154
-6
@@ -27,6 +27,9 @@ export const dict = {
|
|||||||
|
|
||||||
"command.sidebar.toggle": "Kenar çubuğunu aç/kapat",
|
"command.sidebar.toggle": "Kenar çubuğunu aç/kapat",
|
||||||
"command.project.open": "Proje aç",
|
"command.project.open": "Proje aç",
|
||||||
|
"command.project.previous": "Önceki proje",
|
||||||
|
"command.project.next": "Sonraki proje",
|
||||||
|
"command.project.index": "{{index}} numaralı projeye geç",
|
||||||
"command.provider.connect": "Sağlayıcı bağla",
|
"command.provider.connect": "Sağlayıcı bağla",
|
||||||
"command.server.switch": "Sunucu değiştir",
|
"command.server.switch": "Sunucu değiştir",
|
||||||
"command.settings.open": "Ayarları aç",
|
"command.settings.open": "Ayarları aç",
|
||||||
@@ -49,6 +52,7 @@ export const dict = {
|
|||||||
"command.session.new": "Yeni oturum",
|
"command.session.new": "Yeni oturum",
|
||||||
"command.file.open": "Dosya aç",
|
"command.file.open": "Dosya aç",
|
||||||
"command.tab.close": "Sekmeyi kapat",
|
"command.tab.close": "Sekmeyi kapat",
|
||||||
|
"command.tab.reopenClosed": "Kapatılan sekmeyi yeniden aç",
|
||||||
"command.context.addSelection": "Seçimi bağlama ekle",
|
"command.context.addSelection": "Seçimi bağlama ekle",
|
||||||
"command.context.addSelection.description": "Mevcut dosyadan seçili satırları ekle",
|
"command.context.addSelection.description": "Mevcut dosyadan seçili satırları ekle",
|
||||||
"command.input.focus": "Girişi odakla",
|
"command.input.focus": "Girişi odakla",
|
||||||
@@ -132,7 +136,7 @@ export const dict = {
|
|||||||
"provider.connect.status.waiting": "Yetkilendirme bekleniyor...",
|
"provider.connect.status.waiting": "Yetkilendirme bekleniyor...",
|
||||||
"provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}",
|
"provider.connect.status.failed": "Yetkilendirme başarısız: {{error}}",
|
||||||
"provider.connect.apiKey.description":
|
"provider.connect.apiKey.description":
|
||||||
"{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için {{provider}} API anahtarınızı girin.",
|
"{{provider}} hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için API anahtarınızı girin.",
|
||||||
"provider.connect.apiKey.label": "{{provider}} API anahtarı",
|
"provider.connect.apiKey.label": "{{provider}} API anahtarı",
|
||||||
"provider.connect.apiKey.placeholder": "API anahtarı",
|
"provider.connect.apiKey.placeholder": "API anahtarı",
|
||||||
"provider.connect.apiKey.required": "API anahtarı gerekli",
|
"provider.connect.apiKey.required": "API anahtarı gerekli",
|
||||||
@@ -143,10 +147,10 @@ export const dict = {
|
|||||||
"provider.connect.opencodeZen.visit.prefix": "",
|
"provider.connect.opencodeZen.visit.prefix": "",
|
||||||
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
|
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
|
||||||
"provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.",
|
"provider.connect.opencodeZen.visit.suffix": " adresini ziyaret ederek API anahtarınızı alın.",
|
||||||
"provider.connect.oauth.code.visit.prefix":
|
"provider.connect.oauth.code.visit.prefix": "Yetkilendirme kodunuzu almak için ",
|
||||||
"Hesabınızı bağlamak ve OpenCode'da {{provider}} modellerini kullanmak için ",
|
|
||||||
"provider.connect.oauth.code.visit.link": "bu bağlantıya",
|
"provider.connect.oauth.code.visit.link": "bu bağlantıya",
|
||||||
"provider.connect.oauth.code.visit.suffix": " tıklayarak yetkilendirme kodunuzu alın.",
|
"provider.connect.oauth.code.visit.suffix":
|
||||||
|
" gidin; ardından hesabınızı bağlayarak OpenCode'da {{provider}} modellerini kullanın.",
|
||||||
"provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu",
|
"provider.connect.oauth.code.label": "{{method}} yetkilendirme kodu",
|
||||||
"provider.connect.oauth.code.placeholder": "Yetkilendirme kodu",
|
"provider.connect.oauth.code.placeholder": "Yetkilendirme kodu",
|
||||||
"provider.connect.oauth.code.required": "Yetkilendirme kodu gerekli",
|
"provider.connect.oauth.code.required": "Yetkilendirme kodu gerekli",
|
||||||
@@ -213,6 +217,11 @@ export const dict = {
|
|||||||
"model.input.video": "video",
|
"model.input.video": "video",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "Kabul eder: {{inputs}}",
|
"model.tooltip.allows": "Kabul eder: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Bağlam",
|
||||||
|
"model.tooltip.inputs": "Girdiler",
|
||||||
|
"model.tooltip.model": "Model",
|
||||||
|
"model.tooltip.provider": "Sağlayıcı",
|
||||||
|
"model.tooltip.reasoning": "Akıl yürütme",
|
||||||
"model.tooltip.reasoning.allowed": "Akıl yürütme destekler",
|
"model.tooltip.reasoning.allowed": "Akıl yürütme destekler",
|
||||||
"model.tooltip.reasoning.none": "Akıl yürütme yok",
|
"model.tooltip.reasoning.none": "Akıl yürütme yok",
|
||||||
"model.tooltip.context": "Bağlam limiti {{limit}}",
|
"model.tooltip.context": "Bağlam limiti {{limit}}",
|
||||||
@@ -223,6 +232,7 @@ export const dict = {
|
|||||||
"common.loading": "Yükleniyor",
|
"common.loading": "Yükleniyor",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "İptal",
|
"common.cancel": "İptal",
|
||||||
|
"common.clear": "Temizle",
|
||||||
"common.connect": "Bağlan",
|
"common.connect": "Bağlan",
|
||||||
"common.disconnect": "Bağlantı Kes",
|
"common.disconnect": "Bağlantı Kes",
|
||||||
"common.continue": "Gönder",
|
"common.continue": "Gönder",
|
||||||
@@ -240,6 +250,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Kabuk",
|
"prompt.mode.shell": "Kabuk",
|
||||||
"prompt.mode.normal": "Komut",
|
"prompt.mode.normal": "Komut",
|
||||||
"prompt.mode.shell.exit": "çıkmak için esc",
|
"prompt.mode.shell.exit": "çıkmak için esc",
|
||||||
|
"session.child.promptDisabled": "Alt ajan oturumlarına istem gönderilemez.",
|
||||||
|
"session.child.backToParent": "Ana oturuma dön.",
|
||||||
|
|
||||||
"prompt.example.1": "Kod tabanındaki bir TODO'yu düzelt",
|
"prompt.example.1": "Kod tabanındaki bir TODO'yu düzelt",
|
||||||
"prompt.example.2": "Bu projenin teknoloji yığını nedir?",
|
"prompt.example.2": "Bu projenin teknoloji yığını nedir?",
|
||||||
@@ -346,6 +358,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "Sil",
|
"dialog.server.menu.delete": "Sil",
|
||||||
"dialog.server.current": "Mevcut Sunucu",
|
"dialog.server.current": "Mevcut Sunucu",
|
||||||
"dialog.server.status.default": "Varsayılan",
|
"dialog.server.status.default": "Varsayılan",
|
||||||
|
"wsl.server.add": "WSL sunucusu ekle",
|
||||||
|
"wsl.server.addShort": "WSL ekle",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "WSL sunucusu",
|
||||||
|
"wsl.server.retryStart": "Başlatmayı yeniden dene",
|
||||||
|
"wsl.server.updating": "Güncelleniyor...",
|
||||||
|
"wsl.onboarding.step.distro": "Dağıtım seç",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "WSL kontrol ediliyor...",
|
||||||
|
"wsl.onboarding.restartRequired": "WSL kurulumunu tamamlamak için Windows'un yeniden başlatılması gerekiyor.",
|
||||||
|
"wsl.onboarding.ready": "WSL hazır.",
|
||||||
|
"wsl.onboarding.required": "Devam etmek için WSL gerekli.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Dağıtımlar kontrol ediliyor...",
|
||||||
|
"wsl.onboarding.installingDistro": "{{distro}} kuruluyor...",
|
||||||
|
"wsl.onboarding.checkingDistro": "{{distro}} kontrol ediliyor...",
|
||||||
|
"wsl.onboarding.listingDistros": "Dağıtımlar listeleniyor...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} hazır.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "{{distro}} henüz kurulu değil.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Kurulumu tamamlamak için {{distro}} dağıtımını bir kez açın.",
|
||||||
|
"wsl.onboarding.finishingDistro": "{{distro}} kurulumu tamamlanıyor.",
|
||||||
|
"wsl.onboarding.pickDistro": "Bir dağıtım seçin veya aşağıdan bir dağıtım kurun.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "OpenCode kontrol ediliyor...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "{{distro}} içinde OpenCode kontrol ediliyor...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "OpenCode güncelleniyor...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "{{distro}} içinde OpenCode güncelleniyor...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "{{distro}} içindeki OpenCode'u güncelleyin.",
|
||||||
|
"wsl.onboarding.updateOpencode": "OpenCode'u güncelle",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "{{distro}} içinde OpenCode hazır.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode hazır.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "OpenCode'u {{distro}} içine kurun.",
|
||||||
|
"wsl.onboarding.installOpencode": "OpenCode'u kur",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Önce bir dağıtım seçin.",
|
||||||
|
"wsl.onboarding.loadFailed": "WSL durumu yüklenemedi.",
|
||||||
|
"wsl.onboarding.loading": "Yükleniyor...",
|
||||||
|
"wsl.onboarding.installedDistros": "Kurulu dağıtımlar",
|
||||||
|
"wsl.onboarding.checkAgain": "Tekrar kontrol et",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Hazır",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Kontrol ediliyor...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode kurulu değil",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "bash ve curl eksik",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "Desteklenmiyor · WSL 2 kullanın",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "Başka bir dağıtım mı gerekiyor?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "WSL kataloğundan bir Linux dağıtımı kurun",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL kurulu değil",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"OpenCode'un WSL sunucusu ekleyebilmesi için önce WSL (Linux için Windows Alt Sistemi) kurulmalıdır",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL kullanılamıyor",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode bu makinede WSL'yi doğrulayamadı.",
|
||||||
|
"wsl.onboarding.installWsl": "WSL'yi kur",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"WSL kurulumunu tamamlamak için Windows'u yeniden başlatın, ardından OpenCode'u tekrar açın.",
|
||||||
|
"wsl.onboarding.next": "İleri",
|
||||||
|
"wsl.onboarding.refresh": "Yenile",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Kurulu tüm dağıtımlar zaten eklenmiş.",
|
||||||
|
"wsl.onboarding.noDistros": "Henüz dağıtım algılanmadı.",
|
||||||
|
"wsl.onboarding.install": "Kur",
|
||||||
|
"wsl.onboarding.installing": "Kuruluyor...",
|
||||||
|
"wsl.onboarding.installDistro": "Dağıtım kur",
|
||||||
|
"wsl.onboarding.searchDistros": "Dağıtım ara",
|
||||||
|
"wsl.onboarding.wsl2Required": "WSL 2 gerekli.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Bu dağıtım için bash ve curl gerekli.",
|
||||||
|
"wsl.onboarding.openTerminal": "Terminali aç",
|
||||||
|
"wsl.onboarding.path": "Yol: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "bulunamadı",
|
||||||
|
"wsl.onboarding.version": "Sürüm: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "bilinmiyor",
|
||||||
|
"wsl.onboarding.desktopVersion": "masaüstü {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "Kurulu sürüm masaüstü uygulamasının sürümüyle eşleşmiyor.",
|
||||||
|
"wsl.onboarding.adding": "Ekleniyor...",
|
||||||
|
|
||||||
"dialog.project.edit.title": "Projeyi düzenle",
|
"dialog.project.edit.title": "Projeyi düzenle",
|
||||||
"dialog.project.edit.name": "Ad",
|
"dialog.project.edit.name": "Ad",
|
||||||
@@ -408,6 +489,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
|
|
||||||
@@ -456,10 +538,12 @@ export const dict = {
|
|||||||
|
|
||||||
"error.page.title": "Bir şeyler yanlış gitti",
|
"error.page.title": "Bir şeyler yanlış gitti",
|
||||||
"error.page.description": "Uygulama yüklenirken bir hata oluştu.",
|
"error.page.description": "Uygulama yüklenirken bir hata oluştu.",
|
||||||
|
"error.page.description.localServerStartup": "Yerel sunucu başlatılırken bir hata oluştu.",
|
||||||
"error.page.details.label": "Hata Detayları",
|
"error.page.details.label": "Hata Detayları",
|
||||||
"error.page.action.restart": "Yeniden Başlat",
|
"error.page.action.restart": "Yeniden Başlat",
|
||||||
"error.page.action.report": "Hatayı Bildir",
|
"error.page.action.report": "Hatayı Bildir",
|
||||||
"error.page.action.reported": "Hata Bildirildi",
|
"error.page.action.reported": "Hata Bildirildi",
|
||||||
|
"error.page.action.exportLogs": "Günlükleri Dışa Aktar",
|
||||||
"error.page.action.checking": "Kontrol ediliyor...",
|
"error.page.action.checking": "Kontrol ediliyor...",
|
||||||
"error.page.action.checkUpdates": "Güncellemeleri kontrol et",
|
"error.page.action.checkUpdates": "Güncellemeleri kontrol et",
|
||||||
"error.page.action.updateTo": "{{version}} sürümüne güncelle",
|
"error.page.action.updateTo": "{{version}} sürümüne güncelle",
|
||||||
@@ -509,11 +593,31 @@ export const dict = {
|
|||||||
"home.recentProjects": "Son projeler",
|
"home.recentProjects": "Son projeler",
|
||||||
"home.empty.title": "Son proje yok",
|
"home.empty.title": "Son proje yok",
|
||||||
"home.empty.description": "Yerel bir proje açarak başlayın",
|
"home.empty.description": "Yerel bir proje açarak başlayın",
|
||||||
|
"home.title": "Ana Sayfa",
|
||||||
|
"home.projects": "Projeler",
|
||||||
|
"home.project.add": "Proje ekle",
|
||||||
|
"home.recentlyClosed": "Yakın zamanda kapatılanlar",
|
||||||
|
"home.server.collapse": "Sunucu projelerini daralt",
|
||||||
|
"home.server.expand": "Sunucu projelerini genişlet",
|
||||||
|
"home.sessions.search.placeholder": "Oturum ara",
|
||||||
|
"home.sessions.search.placeholder.scoped": "{{scope}} kapsamında oturum ara",
|
||||||
|
"home.sessions.search.sessions": "Oturumlar",
|
||||||
|
"home.sessions.search.noResults": "{{query}} için oturum bulunamadı",
|
||||||
|
"home.sessions.empty": "Henüz burada bir şey yok",
|
||||||
|
"home.sessions.empty.description": "Başlamak için bir oturum oluşturun",
|
||||||
|
"home.sessions.group.today": "Bugün",
|
||||||
|
"home.sessions.group.yesterday": "Dün",
|
||||||
|
"home.sessions.group.older": "Daha eski",
|
||||||
|
|
||||||
"session.tab.session": "Oturum",
|
"session.tab.session": "Oturum",
|
||||||
"session.tab.review": "İnceleme",
|
"session.tab.review": "İnceleme",
|
||||||
"session.tab.context": "Bağlam",
|
"session.tab.context": "Bağlam",
|
||||||
|
"session.tab.unknown": "Bilinmeyen Oturum",
|
||||||
"session.panel.reviewAndFiles": "İnceleme ve dosyalar",
|
"session.panel.reviewAndFiles": "İnceleme ve dosyalar",
|
||||||
|
"session.error.notFound": "Bu oturum bulunamıyor",
|
||||||
|
"session.error.notFound.description": "Bu sekme, artık bu sunucuda bulunmayan bir oturuma yönlendiriyor.",
|
||||||
|
"session.error.notFound.closeTab": "Sekmeyi Kapat",
|
||||||
|
"session.error.serverConnection": "Bu sunucuya bağlanılamıyor",
|
||||||
"session.review.filesChanged": "{{count}} Dosya Değişti",
|
"session.review.filesChanged": "{{count}} Dosya Değişti",
|
||||||
"session.review.change.one": "Değişiklik",
|
"session.review.change.one": "Değişiklik",
|
||||||
"session.review.change.other": "Değişiklik",
|
"session.review.change.other": "Değişiklik",
|
||||||
@@ -523,6 +627,8 @@ export const dict = {
|
|||||||
"session.review.noSnapshot":
|
"session.review.noSnapshot":
|
||||||
"Yapılandırmada anlık görüntü takibi devre dışı bırakıldı, bu nedenle oturum değişiklikleri kullanılamıyor",
|
"Yapılandırmada anlık görüntü takibi devre dışı bırakıldı, bu nedenle oturum değişiklikleri kullanılamıyor",
|
||||||
"session.review.noChanges": "Değişiklik yok",
|
"session.review.noChanges": "Değişiklik yok",
|
||||||
|
"session.review.noUncommittedChanges": "Henüz commit edilmemiş değişiklik yok",
|
||||||
|
"session.review.noBranchChanges": "Henüz dal değişikliği yok",
|
||||||
|
|
||||||
"session.files.selectToOpen": "Açmak için bir dosya seçin",
|
"session.files.selectToOpen": "Açmak için bir dosya seçin",
|
||||||
"session.files.all": "Tüm dosyalar",
|
"session.files.all": "Tüm dosyalar",
|
||||||
@@ -539,6 +645,10 @@ export const dict = {
|
|||||||
"session.todo.title": "Görevler",
|
"session.todo.title": "Görevler",
|
||||||
"session.todo.collapse": "Daralt",
|
"session.todo.collapse": "Daralt",
|
||||||
"session.todo.expand": "Genişlet",
|
"session.todo.expand": "Genişlet",
|
||||||
|
"session.question.minimize": "Soruyu küçült",
|
||||||
|
"session.question.restore": "Soruyu geri yükle",
|
||||||
|
"session.question.pending.one": "{{count}} bekleyen soru",
|
||||||
|
"session.question.pending.other": "{{count}} bekleyen soru",
|
||||||
"session.followupDock.summary.one": "{{count}} sıradaki mesaj",
|
"session.followupDock.summary.one": "{{count}} sıradaki mesaj",
|
||||||
"session.followupDock.summary.other": "{{count}} sıradaki mesaj",
|
"session.followupDock.summary.other": "{{count}} sıradaki mesaj",
|
||||||
"session.followupDock.sendNow": "Şimdi gönder",
|
"session.followupDock.sendNow": "Şimdi gönder",
|
||||||
@@ -552,9 +662,16 @@ export const dict = {
|
|||||||
"session.revertDock.restore": "Mesajı geri yükle",
|
"session.revertDock.restore": "Mesajı geri yükle",
|
||||||
|
|
||||||
"session.new.title": "İstediğini yap",
|
"session.new.title": "İstediğini yap",
|
||||||
|
"session.new.project.new": "Yeni proje",
|
||||||
|
"session.new.project.search": "Proje ara",
|
||||||
|
"session.new.project.add": "Proje ekle",
|
||||||
"session.new.worktree.main": "Ana dal",
|
"session.new.worktree.main": "Ana dal",
|
||||||
"session.new.worktree.mainWithBranch": "Ana dal ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Ana dal ({{branch}})",
|
||||||
"session.new.worktree.create": "Yeni çalışma ağacı oluştur",
|
"session.new.worktree.create": "Yeni çalışma ağacı oluştur",
|
||||||
|
"session.new.workspace.runIn": "Oturumu şurada çalıştır",
|
||||||
|
"session.new.workspace.triggerLocal": "Yerel",
|
||||||
|
"session.new.workspace.local": "Yerel depo",
|
||||||
|
"session.new.workspace.existing": "Çalışma alanı…",
|
||||||
"session.new.lastModified": "Son değişiklik",
|
"session.new.lastModified": "Son değişiklik",
|
||||||
|
|
||||||
"session.header.search.placeholder": "{{project}} ara",
|
"session.header.search.placeholder": "{{project}} ara",
|
||||||
@@ -624,6 +741,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "Son oturumlar",
|
"sidebar.project.recentSessions": "Son oturumlar",
|
||||||
"sidebar.project.viewAllSessions": "Tüm oturumları görüntüle",
|
"sidebar.project.viewAllSessions": "Tüm oturumları görüntüle",
|
||||||
"sidebar.project.clearNotifications": "Bildirimleri temizle",
|
"sidebar.project.clearNotifications": "Bildirimleri temizle",
|
||||||
|
"sidebar.empty.title": "Açık proje yok",
|
||||||
|
"sidebar.empty.description": "Başlamak için bir proje açın",
|
||||||
|
|
||||||
"app.name.desktop": "OpenCode Masaüstü",
|
"app.name.desktop": "OpenCode Masaüstü",
|
||||||
|
|
||||||
@@ -636,6 +755,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.description": "OpenCode sunucusunu Windows'ta WSL içinde çalıştırın.",
|
"settings.desktop.wsl.description": "OpenCode sunucusunu Windows'ta WSL içinde çalıştırın.",
|
||||||
|
|
||||||
"settings.general.section.appearance": "Görünüm",
|
"settings.general.section.appearance": "Görünüm",
|
||||||
|
"settings.general.section.advanced": "Gelişmiş",
|
||||||
"settings.general.section.notifications": "Sistem bildirimleri",
|
"settings.general.section.notifications": "Sistem bildirimleri",
|
||||||
"settings.general.section.updates": "Güncellemeler",
|
"settings.general.section.updates": "Güncellemeler",
|
||||||
"settings.general.section.sounds": "Ses efektleri",
|
"settings.general.section.sounds": "Ses efektleri",
|
||||||
@@ -644,6 +764,11 @@ export const dict = {
|
|||||||
|
|
||||||
"settings.general.row.language.title": "Dil",
|
"settings.general.row.language.title": "Dil",
|
||||||
"settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin",
|
"settings.general.row.language.description": "OpenCode'un görünüm dilini değiştirin",
|
||||||
|
"settings.general.row.shell.title": "Terminal Kabuğu",
|
||||||
|
"settings.general.row.shell.description":
|
||||||
|
"Terminalinizde kullanılacak kabuğu seçin. Uyumlu kabuklar, ajan araç çağrılarında da kullanılır.",
|
||||||
|
"settings.general.row.shell.autoDefault": "Otomatik (Varsayılan)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "yalnızca terminal",
|
||||||
"settings.general.row.appearance.title": "Görünüm",
|
"settings.general.row.appearance.title": "Görünüm",
|
||||||
"settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin",
|
"settings.general.row.appearance.description": "OpenCode'un cihazınızdaki görünümünü özelleştirin",
|
||||||
"settings.general.row.colorScheme.title": "Renk şeması",
|
"settings.general.row.colorScheme.title": "Renk şeması",
|
||||||
@@ -653,8 +778,8 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
|
"settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
|
||||||
"settings.general.row.font.title": "Kod Yazı Tipi",
|
"settings.general.row.font.title": "Kod Yazı Tipi",
|
||||||
"settings.general.row.font.description": "Kod bloklarında kullanılan yazı tipini özelleştirin",
|
"settings.general.row.font.description": "Kod bloklarında kullanılan yazı tipini özelleştirin",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "Terminal yazı tipi",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "Terminalde kullanılan yazı tipini özelleştirin",
|
||||||
"settings.general.row.uiFont.title": "Arayüz Yazı Tipi",
|
"settings.general.row.uiFont.title": "Arayüz Yazı Tipi",
|
||||||
"settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin",
|
"settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin",
|
||||||
"settings.general.row.followup.title": "Takip davranışı",
|
"settings.general.row.followup.title": "Takip davranışı",
|
||||||
@@ -662,6 +787,21 @@ export const dict = {
|
|||||||
"Takip komutlarının hemen yönlendirilmesini mi yoksa sırada beklemesini mi istediğinizi seçin",
|
"Takip komutlarının hemen yönlendirilmesini mi yoksa sırada beklemesini mi istediğinizi seçin",
|
||||||
"settings.general.row.followup.option.queue": "Sıra",
|
"settings.general.row.followup.option.queue": "Sıra",
|
||||||
"settings.general.row.followup.option.steer": "Yönlendir",
|
"settings.general.row.followup.option.steer": "Yönlendir",
|
||||||
|
"settings.general.row.showFileTree.title": "Dosya ağacı",
|
||||||
|
"settings.general.row.showFileTree.description": "Oturumlarda dosya ağacı panelini göster",
|
||||||
|
"settings.general.row.showNavigation.title": "Gezinme denetimleri",
|
||||||
|
"settings.general.row.showNavigation.description": "Masaüstü başlık çubuğunda geri ve ileri düğmelerini göster",
|
||||||
|
"settings.general.row.showSearch.title": "Komut paleti",
|
||||||
|
"settings.general.row.showSearch.description": "Başlık çubuğunda arama ve komut paleti düğmesini göster",
|
||||||
|
"settings.general.row.showTerminal.title": "Terminal",
|
||||||
|
"settings.general.row.showTerminal.description": "Masaüstü başlık çubuğunda terminal düğmesini göster",
|
||||||
|
"settings.general.row.showStatus.title": "Sunucu durumu",
|
||||||
|
"settings.general.row.showStatus.description": "Başlık çubuğunda sunucu durumu düğmesini göster",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Alt gezinme",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"Mobil cihazlarda başlık çubuğunu ve oturum sekmelerini ekranın altına yerleştir",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Özel ajanlar",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Yazma alanında ajan seçiciyi göster",
|
||||||
"settings.general.row.reasoningSummaries.title": "Akıl yürütme özetlerini göster",
|
"settings.general.row.reasoningSummaries.title": "Akıl yürütme özetlerini göster",
|
||||||
"settings.general.row.reasoningSummaries.description": "Zaman çizelgesinde model akıl yürütme özetlerini görüntüle",
|
"settings.general.row.reasoningSummaries.description": "Zaman çizelgesinde model akıl yürütme özetlerini görüntüle",
|
||||||
"settings.general.row.shellToolPartsExpanded.title": "Kabuk araç bileşenlerini genişlet",
|
"settings.general.row.shellToolPartsExpanded.title": "Kabuk araç bileşenlerini genişlet",
|
||||||
@@ -670,6 +810,12 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "Düzenleme araç bileşenlerini genişlet",
|
"settings.general.row.editToolPartsExpanded.title": "Düzenleme araç bileşenlerini genişlet",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster",
|
"Zaman çizelgesinde düzenleme, yazma ve yama araç bileşenlerini varsayılan olarak genişletilmiş göster",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Yeni düzen ve tasarımlar",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Yeniden tasarlanan düzeni, ana sayfayı, yazma alanını ve oturum arayüzünü etkinleştir",
|
||||||
|
"settings.general.row.pinchZoom.title": "Sıkıştırarak yakınlaştır",
|
||||||
|
"settings.general.row.pinchZoom.description":
|
||||||
|
"İzleme dörtgeninde sıkıştırma ve Ctrl-kaydırma hareketleriyle yakınlaştırmaya izin ver",
|
||||||
|
|
||||||
"settings.general.row.wayland.title": "Yerel Wayland kullan",
|
"settings.general.row.wayland.title": "Yerel Wayland kullan",
|
||||||
"settings.general.row.wayland.description":
|
"settings.general.row.wayland.description":
|
||||||
@@ -686,6 +832,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle",
|
"settings.updates.row.check.description": "Elle güncelleme kontrolü yap ve varsa yükle",
|
||||||
"settings.updates.action.checkNow": "Şimdi kontrol et",
|
"settings.updates.action.checkNow": "Şimdi kontrol et",
|
||||||
"settings.updates.action.checking": "Kontrol ediliyor...",
|
"settings.updates.action.checking": "Kontrol ediliyor...",
|
||||||
|
"settings.updates.action.downloading": "İndiriliyor...",
|
||||||
|
"settings.updates.action.installing": "Kuruluyor...",
|
||||||
"settings.updates.toast.latest.title": "Güncelsiniz",
|
"settings.updates.toast.latest.title": "Güncelsiniz",
|
||||||
"settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.",
|
"settings.updates.toast.latest.description": "OpenCode'un en son sürümünü kullanıyorsunuz.",
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const dict = {
|
|||||||
"command.session.new": "Нова сесія",
|
"command.session.new": "Нова сесія",
|
||||||
"command.file.open": "Відкрити файл",
|
"command.file.open": "Відкрити файл",
|
||||||
"command.tab.close": "Закрити вкладку",
|
"command.tab.close": "Закрити вкладку",
|
||||||
|
"command.tab.reopenClosed": "Повторно відкрити закриту вкладку",
|
||||||
"command.context.addSelection": "Додати виділення до контексту",
|
"command.context.addSelection": "Додати виділення до контексту",
|
||||||
"command.context.addSelection.description": "Додати вибрані рядки з поточного файлу",
|
"command.context.addSelection.description": "Додати вибрані рядки з поточного файлу",
|
||||||
"command.input.focus": "Фокус на полі введення",
|
"command.input.focus": "Фокус на полі введення",
|
||||||
@@ -212,6 +213,11 @@ export const dict = {
|
|||||||
"model.input.video": "відео",
|
"model.input.video": "відео",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "Дозволяє: {{inputs}}",
|
"model.tooltip.allows": "Дозволяє: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "Контекст",
|
||||||
|
"model.tooltip.inputs": "Вхідні дані",
|
||||||
|
"model.tooltip.model": "Модель",
|
||||||
|
"model.tooltip.provider": "Провайдер",
|
||||||
|
"model.tooltip.reasoning": "Мислення",
|
||||||
"model.tooltip.reasoning.allowed": "Підтримує мислення",
|
"model.tooltip.reasoning.allowed": "Підтримує мислення",
|
||||||
"model.tooltip.reasoning.none": "Без мислення",
|
"model.tooltip.reasoning.none": "Без мислення",
|
||||||
"model.tooltip.context": "Ліміт контексту {{limit}}",
|
"model.tooltip.context": "Ліміт контексту {{limit}}",
|
||||||
@@ -222,6 +228,7 @@ export const dict = {
|
|||||||
"common.loading": "Завантаження",
|
"common.loading": "Завантаження",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "Скасувати",
|
"common.cancel": "Скасувати",
|
||||||
|
"common.clear": "Очистити",
|
||||||
"common.open": "Відкрити",
|
"common.open": "Відкрити",
|
||||||
"common.connect": "Підключити",
|
"common.connect": "Підключити",
|
||||||
"common.disconnect": "Відключити",
|
"common.disconnect": "Відключити",
|
||||||
@@ -354,6 +361,75 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "Видалити",
|
"dialog.server.menu.delete": "Видалити",
|
||||||
"dialog.server.current": "Поточний сервер",
|
"dialog.server.current": "Поточний сервер",
|
||||||
"dialog.server.status.default": "За замовчуванням",
|
"dialog.server.status.default": "За замовчуванням",
|
||||||
|
"wsl.server.add": "Додати сервер WSL",
|
||||||
|
"wsl.server.addShort": "Додати WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "Сервер WSL",
|
||||||
|
"wsl.server.retryStart": "Повторити запуск",
|
||||||
|
"wsl.server.updating": "Оновлення...",
|
||||||
|
"wsl.onboarding.step.distro": "Вибрати дистрибутив",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "Перевірка WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "Щоб завершити встановлення WSL, потрібно перезапустити Windows.",
|
||||||
|
"wsl.onboarding.ready": "WSL готова до роботи.",
|
||||||
|
"wsl.onboarding.required": "Для продовження потрібна WSL.",
|
||||||
|
"wsl.onboarding.checkingDistros": "Перевірка дистрибутивів...",
|
||||||
|
"wsl.onboarding.installingDistro": "Встановлення {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "Перевірка {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "Отримання списку дистрибутивів...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} готовий до роботи.",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "{{distro}} ще не встановлено.",
|
||||||
|
"wsl.onboarding.openDistroOnce": "Відкрийте {{distro}} один раз, щоб завершити налаштування.",
|
||||||
|
"wsl.onboarding.finishingDistro": "Завершення налаштування {{distro}}.",
|
||||||
|
"wsl.onboarding.pickDistro": "Виберіть дистрибутив або встановіть один із наведених нижче.",
|
||||||
|
"wsl.onboarding.checkingOpencode": "Перевірка OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "Перевірка OpenCode у {{distro}}...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "Оновлення OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "Оновлення OpenCode у {{distro}}...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "Оновіть OpenCode у {{distro}}.",
|
||||||
|
"wsl.onboarding.updateOpencode": "Оновити OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "OpenCode готовий до роботи у {{distro}}.",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode готовий до роботи.",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "Встановіть OpenCode у {{distro}}.",
|
||||||
|
"wsl.onboarding.installOpencode": "Встановити OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "Спочатку виберіть дистрибутив.",
|
||||||
|
"wsl.onboarding.loadFailed": "Не вдалося завантажити стан WSL.",
|
||||||
|
"wsl.onboarding.loading": "Завантаження...",
|
||||||
|
"wsl.onboarding.installedDistros": "Встановлені дистрибутиви",
|
||||||
|
"wsl.onboarding.checkAgain": "Перевірити ще раз",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "Готовий",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "Перевірка...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "OpenCode не встановлено",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "Немає bash і curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "Не підтримується · Використовуйте WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "Потрібен інший дистрибутив?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "Встановіть дистрибутив Linux із каталогу WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "WSL не встановлено",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description":
|
||||||
|
"Для додавання сервера WSL в OpenCode потрібна WSL (Підсистема Windows для Linux)",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL недоступна",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode не вдалося перевірити WSL на цьому комп'ютері.",
|
||||||
|
"wsl.onboarding.installWsl": "Встановити WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired":
|
||||||
|
"Перезапустіть Windows, щоб завершити встановлення WSL, а потім знову відкрийте OpenCode.",
|
||||||
|
"wsl.onboarding.next": "Далі",
|
||||||
|
"wsl.onboarding.refresh": "Оновити",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "Усі встановлені дистрибутиви вже додано.",
|
||||||
|
"wsl.onboarding.noDistros": "Дистрибутивів поки не виявлено.",
|
||||||
|
"wsl.onboarding.install": "Встановити",
|
||||||
|
"wsl.onboarding.installing": "Встановлення...",
|
||||||
|
"wsl.onboarding.installDistro": "Встановити дистрибутив",
|
||||||
|
"wsl.onboarding.searchDistros": "Пошук дистрибутивів",
|
||||||
|
"wsl.onboarding.wsl2Required": "Для роботи потрібна WSL 2.",
|
||||||
|
"wsl.onboarding.toolsRequired": "Для цього дистрибутива потрібні bash і curl.",
|
||||||
|
"wsl.onboarding.openTerminal": "Відкрити термінал",
|
||||||
|
"wsl.onboarding.path": "Шлях: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "не знайдено",
|
||||||
|
"wsl.onboarding.version": "Версія: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "невідомо",
|
||||||
|
"wsl.onboarding.desktopVersion": "десктопний застосунок {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "Встановлена версія не відповідає версії десктопного застосунку.",
|
||||||
|
"wsl.onboarding.adding": "Додавання...",
|
||||||
"server.row.noUsername": "без імені користувача",
|
"server.row.noUsername": "без імені користувача",
|
||||||
|
|
||||||
"dialog.project.edit.title": "Редагувати проєкт",
|
"dialog.project.edit.title": "Редагувати проєкт",
|
||||||
@@ -473,10 +549,12 @@ export const dict = {
|
|||||||
|
|
||||||
"error.page.title": "Щось пішло не так",
|
"error.page.title": "Щось пішло не так",
|
||||||
"error.page.description": "Під час завантаження програми сталася помилка.",
|
"error.page.description": "Під час завантаження програми сталася помилка.",
|
||||||
|
"error.page.description.localServerStartup": "Під час запуску локального сервера сталася помилка.",
|
||||||
"error.page.details.label": "Деталі помилки",
|
"error.page.details.label": "Деталі помилки",
|
||||||
"error.page.action.restart": "Перезапустити",
|
"error.page.action.restart": "Перезапустити",
|
||||||
"error.page.action.report": "Повідомити про помилку",
|
"error.page.action.report": "Повідомити про помилку",
|
||||||
"error.page.action.reported": "Помилку повідомлено",
|
"error.page.action.reported": "Помилку повідомлено",
|
||||||
|
"error.page.action.exportLogs": "Експортувати журнали",
|
||||||
"error.page.action.checking": "Перевірка...",
|
"error.page.action.checking": "Перевірка...",
|
||||||
"error.page.action.checkUpdates": "Перевірити оновлення",
|
"error.page.action.checkUpdates": "Перевірити оновлення",
|
||||||
"error.page.action.updateTo": "Оновити до {{version}}",
|
"error.page.action.updateTo": "Оновити до {{version}}",
|
||||||
@@ -533,11 +611,31 @@ export const dict = {
|
|||||||
"home.recentProjects": "Нещодавні проєкти",
|
"home.recentProjects": "Нещодавні проєкти",
|
||||||
"home.empty.title": "Немає нещодавніх проєктів",
|
"home.empty.title": "Немає нещодавніх проєктів",
|
||||||
"home.empty.description": "Почніть, відкривши локальний проєкт",
|
"home.empty.description": "Почніть, відкривши локальний проєкт",
|
||||||
|
"home.title": "Головна",
|
||||||
|
"home.projects": "Проєкти",
|
||||||
|
"home.project.add": "Додати проєкт",
|
||||||
|
"home.recentlyClosed": "Нещодавно закриті",
|
||||||
|
"home.server.collapse": "Згорнути проєкти сервера",
|
||||||
|
"home.server.expand": "Розгорнути проєкти сервера",
|
||||||
|
"home.sessions.search.placeholder": "Пошук сесій",
|
||||||
|
"home.sessions.search.placeholder.scoped": "Пошук сесій у {{scope}}",
|
||||||
|
"home.sessions.search.sessions": "Сесії",
|
||||||
|
"home.sessions.search.noResults": "Сесій за запитом {{query}} не знайдено",
|
||||||
|
"home.sessions.empty": "Тут поки нічого немає",
|
||||||
|
"home.sessions.empty.description": "Створіть сесію, щоб почати",
|
||||||
|
"home.sessions.group.today": "Сьогодні",
|
||||||
|
"home.sessions.group.yesterday": "Учора",
|
||||||
|
"home.sessions.group.older": "Раніше",
|
||||||
|
|
||||||
"session.tab.session": "Сесія",
|
"session.tab.session": "Сесія",
|
||||||
"session.tab.review": "Огляд",
|
"session.tab.review": "Огляд",
|
||||||
"session.tab.context": "Контекст",
|
"session.tab.context": "Контекст",
|
||||||
|
"session.tab.unknown": "Невідома сесія",
|
||||||
"session.panel.reviewAndFiles": "Огляд і файли",
|
"session.panel.reviewAndFiles": "Огляд і файли",
|
||||||
|
"session.error.notFound": "Цю сесію не знайдено",
|
||||||
|
"session.error.notFound.description": "Ця вкладка посилається на сесію, якої більше немає на цьому сервері.",
|
||||||
|
"session.error.notFound.closeTab": "Закрити вкладку",
|
||||||
|
"session.error.serverConnection": "Не вдається підключитися до цього сервера",
|
||||||
"session.review.filesChanged": "Змінено файлів: {{count}}",
|
"session.review.filesChanged": "Змінено файлів: {{count}}",
|
||||||
"session.review.change.one": "Зміна",
|
"session.review.change.one": "Зміна",
|
||||||
"session.review.change.other": "Зміни",
|
"session.review.change.other": "Зміни",
|
||||||
@@ -570,6 +668,10 @@ export const dict = {
|
|||||||
"session.todo.expand": "Розгорнути",
|
"session.todo.expand": "Розгорнути",
|
||||||
"session.todo.progress": "Виконано {{done}} з {{total}} завдань",
|
"session.todo.progress": "Виконано {{done}} з {{total}} завдань",
|
||||||
"session.question.progress": "{{current}} з {{total}} запитань",
|
"session.question.progress": "{{current}} з {{total}} запитань",
|
||||||
|
"session.question.minimize": "Згорнути запитання",
|
||||||
|
"session.question.restore": "Відновити запитання",
|
||||||
|
"session.question.pending.one": "{{count}} запитання очікує відповіді",
|
||||||
|
"session.question.pending.other": "Запитання, що очікують відповіді: {{count}}",
|
||||||
"session.followupDock.summary.one": "{{count}} повідомлення в черзі",
|
"session.followupDock.summary.one": "{{count}} повідомлення в черзі",
|
||||||
"session.followupDock.summary.other": "{{count}} повідомлень у черзі",
|
"session.followupDock.summary.other": "{{count}} повідомлень у черзі",
|
||||||
"session.followupDock.sendNow": "Надіслати зараз",
|
"session.followupDock.sendNow": "Надіслати зараз",
|
||||||
@@ -583,9 +685,16 @@ export const dict = {
|
|||||||
"session.revertDock.restore": "Відновити повідомлення",
|
"session.revertDock.restore": "Відновити повідомлення",
|
||||||
|
|
||||||
"session.new.title": "Створити що завгодно",
|
"session.new.title": "Створити що завгодно",
|
||||||
|
"session.new.project.new": "Новий проєкт",
|
||||||
|
"session.new.project.search": "Пошук проєктів",
|
||||||
|
"session.new.project.add": "Додати проєкт",
|
||||||
"session.new.worktree.main": "Основна гілка",
|
"session.new.worktree.main": "Основна гілка",
|
||||||
"session.new.worktree.mainWithBranch": "Основна гілка ({{branch}})",
|
"session.new.worktree.mainWithBranch": "Основна гілка ({{branch}})",
|
||||||
"session.new.worktree.create": "Створити нове робоче дерево",
|
"session.new.worktree.create": "Створити нове робоче дерево",
|
||||||
|
"session.new.workspace.runIn": "Запустити сесію в",
|
||||||
|
"session.new.workspace.triggerLocal": "Локально",
|
||||||
|
"session.new.workspace.local": "Локальний репозиторій",
|
||||||
|
"session.new.workspace.existing": "Робоча область…",
|
||||||
"session.new.lastModified": "Востаннє змінено",
|
"session.new.lastModified": "Востаннє змінено",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Пошук {{project}}",
|
"session.header.search.placeholder": "Пошук {{project}}",
|
||||||
@@ -773,6 +882,11 @@ export const dict = {
|
|||||||
"settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу",
|
"settings.general.row.showTerminal.description": "Показувати кнопку термінала в заголовку робочого столу",
|
||||||
"settings.general.row.showStatus.title": "Статус сервера",
|
"settings.general.row.showStatus.title": "Статус сервера",
|
||||||
"settings.general.row.showStatus.description": "Показувати кнопку статусу сервера в заголовку",
|
"settings.general.row.showStatus.description": "Показувати кнопку статусу сервера в заголовку",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "Нижня навігація",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description":
|
||||||
|
"Розмістити заголовок і вкладки сесії внизу екрана на мобільних пристроях",
|
||||||
|
"settings.general.row.showCustomAgents.title": "Користувацькі агенти",
|
||||||
|
"settings.general.row.showCustomAgents.description": "Показувати вибір агента в редакторі запиту",
|
||||||
"settings.general.row.reasoningSummaries.title": "Показувати підсумки мислення",
|
"settings.general.row.reasoningSummaries.title": "Показувати підсумки мислення",
|
||||||
"settings.general.row.reasoningSummaries.description": "Відображати підсумки мислення моделі на часовій шкалі",
|
"settings.general.row.reasoningSummaries.description": "Відображати підсумки мислення моделі на часовій шкалі",
|
||||||
"settings.general.row.shellToolPartsExpanded.title": "Розгортати частини інструменту оболонки",
|
"settings.general.row.shellToolPartsExpanded.title": "Розгортати частини інструменту оболонки",
|
||||||
@@ -781,6 +895,12 @@ export const dict = {
|
|||||||
"settings.general.row.editToolPartsExpanded.title": "Розгортати частини інструменту редагування",
|
"settings.general.row.editToolPartsExpanded.title": "Розгортати частини інструменту редагування",
|
||||||
"settings.general.row.editToolPartsExpanded.description":
|
"settings.general.row.editToolPartsExpanded.description":
|
||||||
"Показувати частини інструментів редагування, запису та патчів розгорнутими за замовчуванням на часовій шкалі",
|
"Показувати частини інструментів редагування, запису та патчів розгорнутими за замовчуванням на часовій шкалі",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "Новий макет і дизайн",
|
||||||
|
"settings.general.row.newLayoutDesigns.description":
|
||||||
|
"Увімкнути оновлений макет та інтерфейси головної сторінки, редактора запитів і сесії",
|
||||||
|
"settings.general.row.pinchZoom.title": "Масштабування щипком",
|
||||||
|
"settings.general.row.pinchZoom.description":
|
||||||
|
"Дозволити масштабування жестом щипка на трекпаді та прокручуванням із Ctrl",
|
||||||
|
|
||||||
"settings.general.row.wayland.title": "Використовувати нативний Wayland",
|
"settings.general.row.wayland.title": "Використовувати нативний Wayland",
|
||||||
"settings.general.row.wayland.description": "Вимкнути резервний X11 на Wayland. Потребує перезапуску.",
|
"settings.general.row.wayland.description": "Вимкнути резервний X11 на Wayland. Потребує перезапуску.",
|
||||||
@@ -796,6 +916,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "Вручну перевірити наявність оновлень і встановити, якщо доступні",
|
"settings.updates.row.check.description": "Вручну перевірити наявність оновлень і встановити, якщо доступні",
|
||||||
"settings.updates.action.checkNow": "Перевірити зараз",
|
"settings.updates.action.checkNow": "Перевірити зараз",
|
||||||
"settings.updates.action.checking": "Перевірка...",
|
"settings.updates.action.checking": "Перевірка...",
|
||||||
|
"settings.updates.action.downloading": "Завантаження...",
|
||||||
|
"settings.updates.action.installing": "Встановлення...",
|
||||||
"settings.updates.toast.latest.title": "У вас актуальна версія",
|
"settings.updates.toast.latest.title": "У вас актуальна версія",
|
||||||
"settings.updates.toast.latest.description": "Ви використовуєте останню версію OpenCode.",
|
"settings.updates.toast.latest.description": "Ви використовуєте останню версію OpenCode.",
|
||||||
"sound.option.none": "Немає",
|
"sound.option.none": "Немає",
|
||||||
|
|||||||
+132
-2
@@ -28,6 +28,9 @@ export const dict = {
|
|||||||
"command.sidebar.toggle": "切换侧边栏",
|
"command.sidebar.toggle": "切换侧边栏",
|
||||||
|
|
||||||
"command.project.open": "打开项目",
|
"command.project.open": "打开项目",
|
||||||
|
"command.project.previous": "上一个项目",
|
||||||
|
"command.project.next": "下一个项目",
|
||||||
|
"command.project.index": "切换到项目 {{index}}",
|
||||||
|
|
||||||
"command.provider.connect": "连接提供商",
|
"command.provider.connect": "连接提供商",
|
||||||
|
|
||||||
@@ -56,6 +59,7 @@ export const dict = {
|
|||||||
"command.file.open": "打开文件",
|
"command.file.open": "打开文件",
|
||||||
|
|
||||||
"command.tab.close": "关闭标签页",
|
"command.tab.close": "关闭标签页",
|
||||||
|
"command.tab.reopenClosed": "重新打开已关闭的标签页",
|
||||||
|
|
||||||
"command.context.addSelection": "将所选内容添加到上下文",
|
"command.context.addSelection": "将所选内容添加到上下文",
|
||||||
"command.context.addSelection.description": "添加当前文件中选中的行",
|
"command.context.addSelection.description": "添加当前文件中选中的行",
|
||||||
@@ -230,6 +234,11 @@ export const dict = {
|
|||||||
"model.input.video": "视频",
|
"model.input.video": "视频",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "支持:{{inputs}}",
|
"model.tooltip.allows": "支持:{{inputs}}",
|
||||||
|
"model.tooltip.context.label": "上下文",
|
||||||
|
"model.tooltip.inputs": "输入",
|
||||||
|
"model.tooltip.model": "模型",
|
||||||
|
"model.tooltip.provider": "提供商",
|
||||||
|
"model.tooltip.reasoning": "推理",
|
||||||
"model.tooltip.reasoning.allowed": "支持推理",
|
"model.tooltip.reasoning.allowed": "支持推理",
|
||||||
"model.tooltip.reasoning.none": "不支持推理",
|
"model.tooltip.reasoning.none": "不支持推理",
|
||||||
"model.tooltip.context": "上下文上限 {{limit}}",
|
"model.tooltip.context": "上下文上限 {{limit}}",
|
||||||
@@ -240,6 +249,7 @@ export const dict = {
|
|||||||
"common.loading": "加载中",
|
"common.loading": "加载中",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "取消",
|
"common.cancel": "取消",
|
||||||
|
"common.clear": "清除",
|
||||||
"common.connect": "连接",
|
"common.connect": "连接",
|
||||||
"common.disconnect": "断开连接",
|
"common.disconnect": "断开连接",
|
||||||
"common.continue": "提交",
|
"common.continue": "提交",
|
||||||
@@ -257,6 +267,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.normal": "Prompt",
|
"prompt.mode.normal": "Prompt",
|
||||||
"prompt.mode.shell.exit": "按 esc 退出",
|
"prompt.mode.shell.exit": "按 esc 退出",
|
||||||
|
"session.child.promptDisabled": "无法向子智能体会话发送提示词。",
|
||||||
|
"session.child.backToParent": "返回主会话。",
|
||||||
"prompt.example.1": "修复代码库中的一个 TODO",
|
"prompt.example.1": "修复代码库中的一个 TODO",
|
||||||
"prompt.example.2": "这个项目的技术栈是什么?",
|
"prompt.example.2": "这个项目的技术栈是什么?",
|
||||||
"prompt.example.3": "修复失败的测试",
|
"prompt.example.3": "修复失败的测试",
|
||||||
@@ -359,6 +371,73 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "删除",
|
"dialog.server.menu.delete": "删除",
|
||||||
"dialog.server.current": "当前服务器",
|
"dialog.server.current": "当前服务器",
|
||||||
"dialog.server.status.default": "默认",
|
"dialog.server.status.default": "默认",
|
||||||
|
"wsl.server.add": "添加 WSL 服务器",
|
||||||
|
"wsl.server.addShort": "添加 WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "WSL 服务器",
|
||||||
|
"wsl.server.retryStart": "重试启动",
|
||||||
|
"wsl.server.updating": "正在更新...",
|
||||||
|
"wsl.onboarding.step.distro": "选择发行版",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "正在检查 WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "Windows 需要重启才能完成 WSL 安装。",
|
||||||
|
"wsl.onboarding.ready": "WSL 已就绪。",
|
||||||
|
"wsl.onboarding.required": "需要 WSL 才能继续。",
|
||||||
|
"wsl.onboarding.checkingDistros": "正在检查发行版...",
|
||||||
|
"wsl.onboarding.installingDistro": "正在安装 {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "正在检查 {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "正在列出发行版...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} 已就绪。",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "{{distro}} 尚未安装。",
|
||||||
|
"wsl.onboarding.openDistroOnce": "打开一次 {{distro}} 以完成设置。",
|
||||||
|
"wsl.onboarding.finishingDistro": "正在完成 {{distro}} 的设置。",
|
||||||
|
"wsl.onboarding.pickDistro": "选择一个发行版,或在下方安装。",
|
||||||
|
"wsl.onboarding.checkingOpencode": "正在检查 OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "正在检查 {{distro}} 中的 OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "正在更新 OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "正在更新 {{distro}} 中的 OpenCode...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "更新 {{distro}} 中的 OpenCode。",
|
||||||
|
"wsl.onboarding.updateOpencode": "更新 OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "{{distro}} 中的 OpenCode 已就绪。",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode 已就绪。",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "在 {{distro}} 中安装 OpenCode。",
|
||||||
|
"wsl.onboarding.installOpencode": "安装 OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "请先选择发行版。",
|
||||||
|
"wsl.onboarding.loadFailed": "加载 WSL 状态失败。",
|
||||||
|
"wsl.onboarding.loading": "加载中...",
|
||||||
|
"wsl.onboarding.installedDistros": "已安装的发行版",
|
||||||
|
"wsl.onboarding.checkAgain": "重新检查",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "已就绪",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "检查中...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "未安装 OpenCode",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "缺少 bash、curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "不支持 · 请使用 WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "需要其他发行版?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "从 WSL 目录安装 Linux 发行版",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "未安装 WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description": "OpenCode 需要 WSL(Windows Subsystem for Linux)才能添加 WSL 服务器",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL 不可用",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode 无法验证此设备上的 WSL。",
|
||||||
|
"wsl.onboarding.installWsl": "安装 WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired": "重启 Windows 以完成 WSL 安装,然后重新打开 OpenCode。",
|
||||||
|
"wsl.onboarding.next": "下一步",
|
||||||
|
"wsl.onboarding.refresh": "刷新",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "所有已安装的发行版均已添加。",
|
||||||
|
"wsl.onboarding.noDistros": "尚未检测到发行版。",
|
||||||
|
"wsl.onboarding.install": "安装",
|
||||||
|
"wsl.onboarding.installing": "正在安装...",
|
||||||
|
"wsl.onboarding.installDistro": "安装发行版",
|
||||||
|
"wsl.onboarding.searchDistros": "搜索发行版",
|
||||||
|
"wsl.onboarding.wsl2Required": "需要 WSL 2。",
|
||||||
|
"wsl.onboarding.toolsRequired": "此发行版需要 bash 和 curl。",
|
||||||
|
"wsl.onboarding.openTerminal": "打开终端",
|
||||||
|
"wsl.onboarding.path": "路径:{{path}}",
|
||||||
|
"wsl.onboarding.notFound": "未找到",
|
||||||
|
"wsl.onboarding.version": "版本:{{version}}",
|
||||||
|
"wsl.onboarding.unknown": "未知",
|
||||||
|
"wsl.onboarding.desktopVersion": "桌面版 {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "已安装版本与桌面应用版本不一致。",
|
||||||
|
"wsl.onboarding.adding": "正在添加...",
|
||||||
|
|
||||||
"dialog.project.edit.title": "编辑项目",
|
"dialog.project.edit.title": "编辑项目",
|
||||||
"dialog.project.edit.name": "名称",
|
"dialog.project.edit.name": "名称",
|
||||||
@@ -418,6 +497,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
|
|
||||||
@@ -456,10 +536,12 @@ export const dict = {
|
|||||||
|
|
||||||
"error.page.title": "出了点问题",
|
"error.page.title": "出了点问题",
|
||||||
"error.page.description": "加载应用程序时发生错误。",
|
"error.page.description": "加载应用程序时发生错误。",
|
||||||
|
"error.page.description.localServerStartup": "启动本地服务器时发生错误。",
|
||||||
"error.page.details.label": "错误详情",
|
"error.page.details.label": "错误详情",
|
||||||
"error.page.action.restart": "重启",
|
"error.page.action.restart": "重启",
|
||||||
"error.page.action.report": "上报错误",
|
"error.page.action.report": "上报错误",
|
||||||
"error.page.action.reported": "错误已上报",
|
"error.page.action.reported": "错误已上报",
|
||||||
|
"error.page.action.exportLogs": "导出日志",
|
||||||
"error.page.action.checking": "检查中...",
|
"error.page.action.checking": "检查中...",
|
||||||
"error.page.action.checkUpdates": "检查更新",
|
"error.page.action.checkUpdates": "检查更新",
|
||||||
"error.page.action.updateTo": "更新到 {{version}}",
|
"error.page.action.updateTo": "更新到 {{version}}",
|
||||||
@@ -506,6 +588,9 @@ export const dict = {
|
|||||||
"home.title": "主页",
|
"home.title": "主页",
|
||||||
"home.projects": "项目",
|
"home.projects": "项目",
|
||||||
"home.project.add": "添加项目",
|
"home.project.add": "添加项目",
|
||||||
|
"home.recentlyClosed": "最近关闭",
|
||||||
|
"home.server.collapse": "折叠服务器项目",
|
||||||
|
"home.server.expand": "展开服务器项目",
|
||||||
"home.sessions.search.placeholder": "搜索会话",
|
"home.sessions.search.placeholder": "搜索会话",
|
||||||
"home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜索会话",
|
"home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜索会话",
|
||||||
"home.sessions.search.sessions": "会话",
|
"home.sessions.search.sessions": "会话",
|
||||||
@@ -519,7 +604,12 @@ export const dict = {
|
|||||||
"session.tab.session": "会话",
|
"session.tab.session": "会话",
|
||||||
"session.tab.review": "审查",
|
"session.tab.review": "审查",
|
||||||
"session.tab.context": "上下文",
|
"session.tab.context": "上下文",
|
||||||
|
"session.tab.unknown": "未知会话",
|
||||||
"session.panel.reviewAndFiles": "审查和文件",
|
"session.panel.reviewAndFiles": "审查和文件",
|
||||||
|
"session.error.notFound": "找不到此会话",
|
||||||
|
"session.error.notFound.description": "此标签页指向的会话已不存在于此服务器上。",
|
||||||
|
"session.error.notFound.closeTab": "关闭标签页",
|
||||||
|
"session.error.serverConnection": "无法连接到此服务器",
|
||||||
"session.review.filesChanged": "{{count}} 个文件变更",
|
"session.review.filesChanged": "{{count}} 个文件变更",
|
||||||
"session.review.change.one": "更改",
|
"session.review.change.one": "更改",
|
||||||
"session.review.change.other": "更改",
|
"session.review.change.other": "更改",
|
||||||
@@ -528,6 +618,8 @@ export const dict = {
|
|||||||
"session.review.noVcs": "未检测到 Git 版本控制系统,无法显示更改",
|
"session.review.noVcs": "未检测到 Git 版本控制系统,无法显示更改",
|
||||||
"session.review.noSnapshot": "配置中已禁用快照跟踪,因此会话更改不可用",
|
"session.review.noSnapshot": "配置中已禁用快照跟踪,因此会话更改不可用",
|
||||||
"session.review.noChanges": "无更改",
|
"session.review.noChanges": "无更改",
|
||||||
|
"session.review.noUncommittedChanges": "尚无未提交的更改",
|
||||||
|
"session.review.noBranchChanges": "尚无分支更改",
|
||||||
"session.files.selectToOpen": "选择要打开的文件",
|
"session.files.selectToOpen": "选择要打开的文件",
|
||||||
"session.files.all": "所有文件",
|
"session.files.all": "所有文件",
|
||||||
"session.files.empty": "无文件",
|
"session.files.empty": "无文件",
|
||||||
@@ -541,6 +633,10 @@ export const dict = {
|
|||||||
"session.todo.title": "待办事项",
|
"session.todo.title": "待办事项",
|
||||||
"session.todo.collapse": "折叠",
|
"session.todo.collapse": "折叠",
|
||||||
"session.todo.expand": "展开",
|
"session.todo.expand": "展开",
|
||||||
|
"session.question.minimize": "最小化问题",
|
||||||
|
"session.question.restore": "恢复问题",
|
||||||
|
"session.question.pending.one": "{{count}} 个待处理问题",
|
||||||
|
"session.question.pending.other": "{{count}} 个待处理问题",
|
||||||
"session.followupDock.summary.one": "{{count}} 条排队消息",
|
"session.followupDock.summary.one": "{{count}} 条排队消息",
|
||||||
"session.followupDock.summary.other": "{{count}} 条排队消息",
|
"session.followupDock.summary.other": "{{count}} 条排队消息",
|
||||||
"session.followupDock.sendNow": "立即发送",
|
"session.followupDock.sendNow": "立即发送",
|
||||||
@@ -553,9 +649,16 @@ export const dict = {
|
|||||||
"session.revertDock.expand": "展开已回滚消息",
|
"session.revertDock.expand": "展开已回滚消息",
|
||||||
"session.revertDock.restore": "恢复消息",
|
"session.revertDock.restore": "恢复消息",
|
||||||
"session.new.title": "构建任何东西",
|
"session.new.title": "构建任何东西",
|
||||||
|
"session.new.project.new": "新建项目",
|
||||||
|
"session.new.project.search": "搜索项目",
|
||||||
|
"session.new.project.add": "添加项目",
|
||||||
"session.new.worktree.main": "主分支",
|
"session.new.worktree.main": "主分支",
|
||||||
"session.new.worktree.mainWithBranch": "主分支({{branch}})",
|
"session.new.worktree.mainWithBranch": "主分支({{branch}})",
|
||||||
"session.new.worktree.create": "创建新的 worktree",
|
"session.new.worktree.create": "创建新的 worktree",
|
||||||
|
"session.new.workspace.runIn": "运行会话的位置",
|
||||||
|
"session.new.workspace.triggerLocal": "本地",
|
||||||
|
"session.new.workspace.local": "本地仓库",
|
||||||
|
"session.new.workspace.existing": "工作区…",
|
||||||
"session.new.lastModified": "最后修改",
|
"session.new.lastModified": "最后修改",
|
||||||
"session.header.search.placeholder": "搜索 {{project}}",
|
"session.header.search.placeholder": "搜索 {{project}}",
|
||||||
"session.header.searchFiles": "搜索文件",
|
"session.header.searchFiles": "搜索文件",
|
||||||
@@ -623,6 +726,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "最近会话",
|
"sidebar.project.recentSessions": "最近会话",
|
||||||
"sidebar.project.viewAllSessions": "查看全部会话",
|
"sidebar.project.viewAllSessions": "查看全部会话",
|
||||||
"sidebar.project.clearNotifications": "清除通知",
|
"sidebar.project.clearNotifications": "清除通知",
|
||||||
|
"sidebar.empty.title": "没有打开的项目",
|
||||||
|
"sidebar.empty.description": "打开一个项目以开始使用",
|
||||||
|
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
|
|
||||||
@@ -637,6 +742,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 OpenCode 服务器。",
|
"settings.desktop.wsl.description": "在 Windows 的 WSL 环境中运行 OpenCode 服务器。",
|
||||||
|
|
||||||
"settings.general.section.appearance": "外观",
|
"settings.general.section.appearance": "外观",
|
||||||
|
"settings.general.section.advanced": "高级",
|
||||||
"settings.general.section.notifications": "系统通知",
|
"settings.general.section.notifications": "系统通知",
|
||||||
"settings.general.section.updates": "更新",
|
"settings.general.section.updates": "更新",
|
||||||
"settings.general.section.sounds": "音效",
|
"settings.general.section.sounds": "音效",
|
||||||
@@ -644,6 +750,10 @@ export const dict = {
|
|||||||
"settings.general.section.display": "显示",
|
"settings.general.section.display": "显示",
|
||||||
"settings.general.row.language.title": "语言",
|
"settings.general.row.language.title": "语言",
|
||||||
"settings.general.row.language.description": "更改 OpenCode 的显示语言",
|
"settings.general.row.language.description": "更改 OpenCode 的显示语言",
|
||||||
|
"settings.general.row.shell.title": "终端 Shell",
|
||||||
|
"settings.general.row.shell.description": "选择终端使用的 shell。兼容的 shell 也会用于智能体工具调用。",
|
||||||
|
"settings.general.row.shell.autoDefault": "自动(默认)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "仅终端",
|
||||||
"settings.general.row.appearance.title": "外观",
|
"settings.general.row.appearance.title": "外观",
|
||||||
"settings.general.row.appearance.description": "自定义 OpenCode 在你的设备上的外观",
|
"settings.general.row.appearance.description": "自定义 OpenCode 在你的设备上的外观",
|
||||||
"settings.general.row.colorScheme.title": "配色方案",
|
"settings.general.row.colorScheme.title": "配色方案",
|
||||||
@@ -652,20 +762,38 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
|
"settings.general.row.theme.description": "自定义 OpenCode 的主题。",
|
||||||
"settings.general.row.font.title": "代码字体",
|
"settings.general.row.font.title": "代码字体",
|
||||||
"settings.general.row.font.description": "自定义代码块使用的字体",
|
"settings.general.row.font.description": "自定义代码块使用的字体",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "终端字体",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "自定义终端使用的字体",
|
||||||
"settings.general.row.uiFont.title": "界面字体",
|
"settings.general.row.uiFont.title": "界面字体",
|
||||||
"settings.general.row.uiFont.description": "自定义整个界面使用的字体",
|
"settings.general.row.uiFont.description": "自定义整个界面使用的字体",
|
||||||
"settings.general.row.followup.title": "跟进消息行为",
|
"settings.general.row.followup.title": "跟进消息行为",
|
||||||
"settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待",
|
"settings.general.row.followup.description": "选择跟进提示是立即引导还是在队列中等待",
|
||||||
"settings.general.row.followup.option.queue": "排队",
|
"settings.general.row.followup.option.queue": "排队",
|
||||||
"settings.general.row.followup.option.steer": "引导",
|
"settings.general.row.followup.option.steer": "引导",
|
||||||
|
"settings.general.row.showFileTree.title": "文件树",
|
||||||
|
"settings.general.row.showFileTree.description": "在会话中显示文件树面板",
|
||||||
|
"settings.general.row.showNavigation.title": "导航控件",
|
||||||
|
"settings.general.row.showNavigation.description": "在桌面版标题栏中显示前进和后退按钮",
|
||||||
|
"settings.general.row.showSearch.title": "命令面板",
|
||||||
|
"settings.general.row.showSearch.description": "在标题栏中显示搜索和命令面板按钮",
|
||||||
|
"settings.general.row.showTerminal.title": "终端",
|
||||||
|
"settings.general.row.showTerminal.description": "在桌面版标题栏中显示终端按钮",
|
||||||
|
"settings.general.row.showStatus.title": "服务器状态",
|
||||||
|
"settings.general.row.showStatus.description": "在标题栏中显示服务器状态按钮",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "底部导航",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description": "在移动设备上将标题栏和会话标签页置于屏幕底部",
|
||||||
|
"settings.general.row.showCustomAgents.title": "自定义智能体",
|
||||||
|
"settings.general.row.showCustomAgents.description": "在输入框中显示智能体选择器",
|
||||||
"settings.general.row.reasoningSummaries.title": "显示推理摘要",
|
"settings.general.row.reasoningSummaries.title": "显示推理摘要",
|
||||||
"settings.general.row.reasoningSummaries.description": "在时间线中显示模型推理摘要",
|
"settings.general.row.reasoningSummaries.description": "在时间线中显示模型推理摘要",
|
||||||
"settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分",
|
"settings.general.row.shellToolPartsExpanded.title": "展开 shell 工具部分",
|
||||||
"settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分",
|
"settings.general.row.shellToolPartsExpanded.description": "默认在时间线中展开 shell 工具部分",
|
||||||
"settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分",
|
"settings.general.row.editToolPartsExpanded.title": "展开编辑工具部分",
|
||||||
"settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分",
|
"settings.general.row.editToolPartsExpanded.description": "默认在时间线中展开 edit、write 和 patch 工具部分",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "新版布局和设计",
|
||||||
|
"settings.general.row.newLayoutDesigns.description": "启用重新设计的布局、主页、输入框和会话界面",
|
||||||
|
"settings.general.row.pinchZoom.title": "双指缩放",
|
||||||
|
"settings.general.row.pinchZoom.description": "允许使用触控板双指捏合和 Ctrl-scroll 手势缩放",
|
||||||
"settings.general.row.wayland.title": "使用原生 Wayland",
|
"settings.general.row.wayland.title": "使用原生 Wayland",
|
||||||
"settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。",
|
"settings.general.row.wayland.description": "在 Wayland 上禁用 X11 回退。需要重启。",
|
||||||
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
|
"settings.general.row.wayland.tooltip": "在混合刷新率显示器的 Linux 系统上,原生 Wayland 可能更稳定。",
|
||||||
@@ -678,6 +806,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "手动检查更新并在有更新时安装",
|
"settings.updates.row.check.description": "手动检查更新并在有更新时安装",
|
||||||
"settings.updates.action.checkNow": "立即检查",
|
"settings.updates.action.checkNow": "立即检查",
|
||||||
"settings.updates.action.checking": "正在检查...",
|
"settings.updates.action.checking": "正在检查...",
|
||||||
|
"settings.updates.action.downloading": "正在下载...",
|
||||||
|
"settings.updates.action.installing": "正在安装...",
|
||||||
"settings.updates.toast.latest.title": "已是最新版本",
|
"settings.updates.toast.latest.title": "已是最新版本",
|
||||||
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
|
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ export const dict = {
|
|||||||
|
|
||||||
"command.sidebar.toggle": "切換側邊欄",
|
"command.sidebar.toggle": "切換側邊欄",
|
||||||
"command.project.open": "開啟專案",
|
"command.project.open": "開啟專案",
|
||||||
|
"command.project.previous": "上一個專案",
|
||||||
|
"command.project.next": "下一個專案",
|
||||||
|
"command.project.index": "切換至專案 {{index}}",
|
||||||
"command.provider.connect": "連接提供者",
|
"command.provider.connect": "連接提供者",
|
||||||
"command.server.switch": "切換伺服器",
|
"command.server.switch": "切換伺服器",
|
||||||
"command.settings.open": "開啟設定",
|
"command.settings.open": "開啟設定",
|
||||||
@@ -49,6 +52,7 @@ export const dict = {
|
|||||||
"command.session.new": "新增工作階段",
|
"command.session.new": "新增工作階段",
|
||||||
"command.file.open": "開啟檔案",
|
"command.file.open": "開啟檔案",
|
||||||
"command.tab.close": "關閉分頁",
|
"command.tab.close": "關閉分頁",
|
||||||
|
"command.tab.reopenClosed": "重新開啟已關閉的分頁",
|
||||||
"command.context.addSelection": "將選取內容加入上下文",
|
"command.context.addSelection": "將選取內容加入上下文",
|
||||||
"command.context.addSelection.description": "加入目前檔案中選取的行",
|
"command.context.addSelection.description": "加入目前檔案中選取的行",
|
||||||
"command.input.focus": "聚焦輸入框",
|
"command.input.focus": "聚焦輸入框",
|
||||||
@@ -209,6 +213,11 @@ export const dict = {
|
|||||||
"model.input.video": "影片",
|
"model.input.video": "影片",
|
||||||
"model.input.pdf": "pdf",
|
"model.input.pdf": "pdf",
|
||||||
"model.tooltip.allows": "支援: {{inputs}}",
|
"model.tooltip.allows": "支援: {{inputs}}",
|
||||||
|
"model.tooltip.context.label": "上下文",
|
||||||
|
"model.tooltip.inputs": "輸入",
|
||||||
|
"model.tooltip.model": "模型",
|
||||||
|
"model.tooltip.provider": "供應商",
|
||||||
|
"model.tooltip.reasoning": "推理",
|
||||||
"model.tooltip.reasoning.allowed": "支援推理",
|
"model.tooltip.reasoning.allowed": "支援推理",
|
||||||
"model.tooltip.reasoning.none": "不支援推理",
|
"model.tooltip.reasoning.none": "不支援推理",
|
||||||
"model.tooltip.context": "上下文上限 {{limit}}",
|
"model.tooltip.context": "上下文上限 {{limit}}",
|
||||||
@@ -218,6 +227,7 @@ export const dict = {
|
|||||||
"common.loading": "載入中",
|
"common.loading": "載入中",
|
||||||
"common.loading.ellipsis": "...",
|
"common.loading.ellipsis": "...",
|
||||||
"common.cancel": "取消",
|
"common.cancel": "取消",
|
||||||
|
"common.clear": "清除",
|
||||||
"common.connect": "連線",
|
"common.connect": "連線",
|
||||||
"common.disconnect": "中斷連線",
|
"common.disconnect": "中斷連線",
|
||||||
"common.continue": "提交",
|
"common.continue": "提交",
|
||||||
@@ -235,6 +245,8 @@ export const dict = {
|
|||||||
"prompt.mode.shell": "Shell",
|
"prompt.mode.shell": "Shell",
|
||||||
"prompt.mode.normal": "Prompt",
|
"prompt.mode.normal": "Prompt",
|
||||||
"prompt.mode.shell.exit": "按 esc 退出",
|
"prompt.mode.shell.exit": "按 esc 退出",
|
||||||
|
"session.child.promptDisabled": "無法在子代理程式工作階段中輸入提示詞。",
|
||||||
|
"session.child.backToParent": "返回主要工作階段。",
|
||||||
|
|
||||||
"prompt.example.1": "修復程式碼庫中的一個 TODO",
|
"prompt.example.1": "修復程式碼庫中的一個 TODO",
|
||||||
"prompt.example.2": "這個專案的技術堆疊是什麼?",
|
"prompt.example.2": "這個專案的技術堆疊是什麼?",
|
||||||
@@ -340,6 +352,73 @@ export const dict = {
|
|||||||
"dialog.server.menu.delete": "刪除",
|
"dialog.server.menu.delete": "刪除",
|
||||||
"dialog.server.current": "目前伺服器",
|
"dialog.server.current": "目前伺服器",
|
||||||
"dialog.server.status.default": "預設",
|
"dialog.server.status.default": "預設",
|
||||||
|
"wsl.server.add": "新增 WSL 伺服器",
|
||||||
|
"wsl.server.addShort": "新增 WSL",
|
||||||
|
"wsl.server.label": "WSL",
|
||||||
|
"wsl.server.menu.label": "WSL 伺服器",
|
||||||
|
"wsl.server.retryStart": "重試啟動",
|
||||||
|
"wsl.server.updating": "更新中...",
|
||||||
|
"wsl.onboarding.step.distro": "選擇發行版",
|
||||||
|
"wsl.onboarding.step.opencode": "OpenCode",
|
||||||
|
"wsl.onboarding.checkingRuntime": "正在檢查 WSL...",
|
||||||
|
"wsl.onboarding.restartRequired": "Windows 需要重新啟動,才能完成 WSL 安裝。",
|
||||||
|
"wsl.onboarding.ready": "WSL 已就緒。",
|
||||||
|
"wsl.onboarding.required": "需要 WSL 才能繼續。",
|
||||||
|
"wsl.onboarding.checkingDistros": "正在檢查發行版...",
|
||||||
|
"wsl.onboarding.installingDistro": "正在安裝 {{distro}}...",
|
||||||
|
"wsl.onboarding.checkingDistro": "正在檢查 {{distro}}...",
|
||||||
|
"wsl.onboarding.listingDistros": "正在列出發行版...",
|
||||||
|
"wsl.onboarding.distroReady": "{{distro}} 已就緒。",
|
||||||
|
"wsl.onboarding.distroNotInstalled": "尚未安裝 {{distro}}。",
|
||||||
|
"wsl.onboarding.openDistroOnce": "開啟 {{distro}} 一次以完成設定。",
|
||||||
|
"wsl.onboarding.finishingDistro": "正在完成 {{distro}} 的設定。",
|
||||||
|
"wsl.onboarding.pickDistro": "選擇發行版,或在下方安裝。",
|
||||||
|
"wsl.onboarding.checkingOpencode": "正在檢查 OpenCode...",
|
||||||
|
"wsl.onboarding.checkingOpencodeIn": "正在檢查 {{distro}} 中的 OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencode": "正在更新 OpenCode...",
|
||||||
|
"wsl.onboarding.updatingOpencodeIn": "正在更新 {{distro}} 中的 OpenCode...",
|
||||||
|
"wsl.onboarding.updateOpencodeIn": "更新 {{distro}} 中的 OpenCode。",
|
||||||
|
"wsl.onboarding.updateOpencode": "更新 OpenCode",
|
||||||
|
"wsl.onboarding.opencodeReadyIn": "{{distro}} 中的 OpenCode 已就緒。",
|
||||||
|
"wsl.onboarding.opencodeReady": "OpenCode 已就緒。",
|
||||||
|
"wsl.onboarding.installOpencodeIn": "在 {{distro}} 中安裝 OpenCode。",
|
||||||
|
"wsl.onboarding.installOpencode": "安裝 OpenCode",
|
||||||
|
"wsl.onboarding.chooseDistroFirst": "請先選擇發行版。",
|
||||||
|
"wsl.onboarding.loadFailed": "無法載入 WSL 狀態。",
|
||||||
|
"wsl.onboarding.loading": "載入中...",
|
||||||
|
"wsl.onboarding.installedDistros": "已安裝的發行版",
|
||||||
|
"wsl.onboarding.checkAgain": "再次檢查",
|
||||||
|
"wsl.onboarding.distroStatus.ready": "就緒",
|
||||||
|
"wsl.onboarding.distroStatus.checking": "檢查中...",
|
||||||
|
"wsl.onboarding.distroStatus.opencodeMissing": "未安裝 OpenCode",
|
||||||
|
"wsl.onboarding.distroStatus.missingTools": "缺少 bash、curl",
|
||||||
|
"wsl.onboarding.distroStatus.unsupported": "不支援 · 請使用 WSL 2",
|
||||||
|
"wsl.onboarding.needAnotherDistro": "需要其他發行版?",
|
||||||
|
"wsl.onboarding.needAnotherDistroHint": "從 WSL 目錄安裝 Linux 發行版",
|
||||||
|
"wsl.onboarding.wslNotInstalled.title": "未安裝 WSL",
|
||||||
|
"wsl.onboarding.wslNotInstalled.description": "新增 WSL 伺服器前,必須先安裝 WSL(Windows Subsystem for Linux)",
|
||||||
|
"wsl.onboarding.wslUnavailable.title": "WSL 無法使用",
|
||||||
|
"wsl.onboarding.wslUnavailable.description": "OpenCode 無法確認此電腦上的 WSL。",
|
||||||
|
"wsl.onboarding.installWsl": "安裝 WSL",
|
||||||
|
"wsl.onboarding.windowsRestartRequired": "重新啟動 Windows 以完成 WSL 安裝,然後重新開啟 OpenCode。",
|
||||||
|
"wsl.onboarding.next": "下一步",
|
||||||
|
"wsl.onboarding.refresh": "重新整理",
|
||||||
|
"wsl.onboarding.allDistrosAdded": "所有已安裝的發行版都已新增。",
|
||||||
|
"wsl.onboarding.noDistros": "尚未偵測到發行版。",
|
||||||
|
"wsl.onboarding.install": "安裝",
|
||||||
|
"wsl.onboarding.installing": "安裝中...",
|
||||||
|
"wsl.onboarding.installDistro": "安裝發行版",
|
||||||
|
"wsl.onboarding.searchDistros": "搜尋發行版",
|
||||||
|
"wsl.onboarding.wsl2Required": "需要 WSL 2。",
|
||||||
|
"wsl.onboarding.toolsRequired": "此發行版需要 bash 和 curl。",
|
||||||
|
"wsl.onboarding.openTerminal": "開啟終端機",
|
||||||
|
"wsl.onboarding.path": "路徑: {{path}}",
|
||||||
|
"wsl.onboarding.notFound": "找不到",
|
||||||
|
"wsl.onboarding.version": "版本: {{version}}",
|
||||||
|
"wsl.onboarding.unknown": "未知",
|
||||||
|
"wsl.onboarding.desktopVersion": "桌面版 {{version}}",
|
||||||
|
"wsl.onboarding.versionMismatch": "已安裝版本與桌面應用程式版本不符。",
|
||||||
|
"wsl.onboarding.adding": "新增中...",
|
||||||
|
|
||||||
"dialog.project.edit.title": "編輯專案",
|
"dialog.project.edit.title": "編輯專案",
|
||||||
"dialog.project.edit.name": "名稱",
|
"dialog.project.edit.name": "名稱",
|
||||||
@@ -402,6 +481,7 @@ export const dict = {
|
|||||||
"language.no": "Norsk",
|
"language.no": "Norsk",
|
||||||
"language.br": "Português (Brasil)",
|
"language.br": "Português (Brasil)",
|
||||||
"language.bs": "Bosanski",
|
"language.bs": "Bosanski",
|
||||||
|
"language.uk": "Українська",
|
||||||
"language.th": "ไทย",
|
"language.th": "ไทย",
|
||||||
"language.tr": "Türkçe",
|
"language.tr": "Türkçe",
|
||||||
|
|
||||||
@@ -449,10 +529,12 @@ export const dict = {
|
|||||||
|
|
||||||
"error.page.title": "出了點問題",
|
"error.page.title": "出了點問題",
|
||||||
"error.page.description": "載入應用程式時發生錯誤。",
|
"error.page.description": "載入應用程式時發生錯誤。",
|
||||||
|
"error.page.description.localServerStartup": "啟動本機伺服器時發生錯誤。",
|
||||||
"error.page.details.label": "錯誤詳情",
|
"error.page.details.label": "錯誤詳情",
|
||||||
"error.page.action.restart": "重新啟動",
|
"error.page.action.restart": "重新啟動",
|
||||||
"error.page.action.report": "回報錯誤",
|
"error.page.action.report": "回報錯誤",
|
||||||
"error.page.action.reported": "已回報錯誤",
|
"error.page.action.reported": "已回報錯誤",
|
||||||
|
"error.page.action.exportLogs": "匯出日誌",
|
||||||
"error.page.action.checking": "檢查中...",
|
"error.page.action.checking": "檢查中...",
|
||||||
"error.page.action.checkUpdates": "檢查更新",
|
"error.page.action.checkUpdates": "檢查更新",
|
||||||
"error.page.action.updateTo": "更新到 {{version}}",
|
"error.page.action.updateTo": "更新到 {{version}}",
|
||||||
@@ -498,17 +580,39 @@ export const dict = {
|
|||||||
"home.recentProjects": "最近專案",
|
"home.recentProjects": "最近專案",
|
||||||
"home.empty.title": "沒有最近專案",
|
"home.empty.title": "沒有最近專案",
|
||||||
"home.empty.description": "透過開啟本地專案開始使用",
|
"home.empty.description": "透過開啟本地專案開始使用",
|
||||||
|
"home.title": "首頁",
|
||||||
|
"home.projects": "專案",
|
||||||
|
"home.project.add": "新增專案",
|
||||||
|
"home.recentlyClosed": "最近關閉",
|
||||||
|
"home.server.collapse": "收合伺服器專案",
|
||||||
|
"home.server.expand": "展開伺服器專案",
|
||||||
|
"home.sessions.search.placeholder": "搜尋工作階段",
|
||||||
|
"home.sessions.search.placeholder.scoped": "在 {{scope}} 中搜尋工作階段",
|
||||||
|
"home.sessions.search.sessions": "工作階段",
|
||||||
|
"home.sessions.search.noResults": "找不到符合 {{query}} 的工作階段",
|
||||||
|
"home.sessions.empty": "這裡還沒有內容",
|
||||||
|
"home.sessions.empty.description": "建立工作階段以開始使用",
|
||||||
|
"home.sessions.group.today": "今天",
|
||||||
|
"home.sessions.group.yesterday": "昨天",
|
||||||
|
"home.sessions.group.older": "更早",
|
||||||
|
|
||||||
"session.tab.session": "工作階段",
|
"session.tab.session": "工作階段",
|
||||||
"session.tab.review": "審查",
|
"session.tab.review": "審查",
|
||||||
"session.tab.context": "上下文",
|
"session.tab.context": "上下文",
|
||||||
|
"session.tab.unknown": "未知的工作階段",
|
||||||
"session.panel.reviewAndFiles": "審查與檔案",
|
"session.panel.reviewAndFiles": "審查與檔案",
|
||||||
|
"session.error.notFound": "找不到此工作階段",
|
||||||
|
"session.error.notFound.description": "此分頁指向的工作階段已不存在於此伺服器。",
|
||||||
|
"session.error.notFound.closeTab": "關閉分頁",
|
||||||
|
"session.error.serverConnection": "無法連線至此伺服器",
|
||||||
"session.review.filesChanged": "{{count}} 個檔案變更",
|
"session.review.filesChanged": "{{count}} 個檔案變更",
|
||||||
"session.review.change.one": "變更",
|
"session.review.change.one": "變更",
|
||||||
"session.review.change.other": "變更",
|
"session.review.change.other": "變更",
|
||||||
"session.review.loadingChanges": "正在載入變更...",
|
"session.review.loadingChanges": "正在載入變更...",
|
||||||
"session.review.empty": "此工作階段暫無變更",
|
"session.review.empty": "此工作階段暫無變更",
|
||||||
"session.review.noChanges": "沒有變更",
|
"session.review.noChanges": "沒有變更",
|
||||||
|
"session.review.noUncommittedChanges": "尚無未提交的變更",
|
||||||
|
"session.review.noBranchChanges": "尚無分支變更",
|
||||||
"session.review.noVcs": "未偵測到 Git 版本控制系統,無法顯示變更",
|
"session.review.noVcs": "未偵測到 Git 版本控制系統,無法顯示變更",
|
||||||
"session.review.noSnapshot": "設定中已停用快照追蹤,因此無法使用工作階段變更",
|
"session.review.noSnapshot": "設定中已停用快照追蹤,因此無法使用工作階段變更",
|
||||||
"session.files.selectToOpen": "選取要開啟的檔案",
|
"session.files.selectToOpen": "選取要開啟的檔案",
|
||||||
@@ -525,6 +629,10 @@ export const dict = {
|
|||||||
"session.todo.title": "待辦事項",
|
"session.todo.title": "待辦事項",
|
||||||
"session.todo.collapse": "折疊",
|
"session.todo.collapse": "折疊",
|
||||||
"session.todo.expand": "展開",
|
"session.todo.expand": "展開",
|
||||||
|
"session.question.minimize": "最小化問題",
|
||||||
|
"session.question.restore": "還原問題",
|
||||||
|
"session.question.pending.one": "{{count}} 個待處理問題",
|
||||||
|
"session.question.pending.other": "{{count}} 個待處理問題",
|
||||||
"session.followupDock.summary.one": "{{count}} 則佇列訊息",
|
"session.followupDock.summary.one": "{{count}} 則佇列訊息",
|
||||||
"session.followupDock.summary.other": "{{count}} 則佇列訊息",
|
"session.followupDock.summary.other": "{{count}} 則佇列訊息",
|
||||||
"session.followupDock.sendNow": "立即傳送",
|
"session.followupDock.sendNow": "立即傳送",
|
||||||
@@ -538,9 +646,16 @@ export const dict = {
|
|||||||
"session.revertDock.restore": "還原訊息",
|
"session.revertDock.restore": "還原訊息",
|
||||||
|
|
||||||
"session.new.title": "建構任何東西",
|
"session.new.title": "建構任何東西",
|
||||||
|
"session.new.project.new": "新專案",
|
||||||
|
"session.new.project.search": "搜尋專案",
|
||||||
|
"session.new.project.add": "新增專案",
|
||||||
"session.new.worktree.main": "主分支",
|
"session.new.worktree.main": "主分支",
|
||||||
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
|
"session.new.worktree.mainWithBranch": "主分支 ({{branch}})",
|
||||||
"session.new.worktree.create": "建立新的 worktree",
|
"session.new.worktree.create": "建立新的 worktree",
|
||||||
|
"session.new.workspace.runIn": "工作階段執行位置",
|
||||||
|
"session.new.workspace.triggerLocal": "本機",
|
||||||
|
"session.new.workspace.local": "本機儲存庫",
|
||||||
|
"session.new.workspace.existing": "工作區…",
|
||||||
"session.new.lastModified": "最後修改",
|
"session.new.lastModified": "最後修改",
|
||||||
|
|
||||||
"session.header.search.placeholder": "搜尋 {{project}}",
|
"session.header.search.placeholder": "搜尋 {{project}}",
|
||||||
@@ -608,6 +723,8 @@ export const dict = {
|
|||||||
"sidebar.project.recentSessions": "最近工作階段",
|
"sidebar.project.recentSessions": "最近工作階段",
|
||||||
"sidebar.project.viewAllSessions": "查看全部工作階段",
|
"sidebar.project.viewAllSessions": "查看全部工作階段",
|
||||||
"sidebar.project.clearNotifications": "清除通知",
|
"sidebar.project.clearNotifications": "清除通知",
|
||||||
|
"sidebar.empty.title": "未開啟任何專案",
|
||||||
|
"sidebar.empty.description": "開啟專案以開始使用",
|
||||||
|
|
||||||
"app.name.desktop": "OpenCode Desktop",
|
"app.name.desktop": "OpenCode Desktop",
|
||||||
"settings.section.desktop": "桌面",
|
"settings.section.desktop": "桌面",
|
||||||
@@ -619,6 +736,7 @@ export const dict = {
|
|||||||
"settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 OpenCode 伺服器。",
|
"settings.desktop.wsl.description": "在 Windows 上的 WSL 中執行 OpenCode 伺服器。",
|
||||||
|
|
||||||
"settings.general.section.appearance": "外觀",
|
"settings.general.section.appearance": "外觀",
|
||||||
|
"settings.general.section.advanced": "進階",
|
||||||
"settings.general.section.notifications": "系統通知",
|
"settings.general.section.notifications": "系統通知",
|
||||||
"settings.general.section.updates": "更新",
|
"settings.general.section.updates": "更新",
|
||||||
"settings.general.section.sounds": "音效",
|
"settings.general.section.sounds": "音效",
|
||||||
@@ -627,6 +745,10 @@ export const dict = {
|
|||||||
|
|
||||||
"settings.general.row.language.title": "語言",
|
"settings.general.row.language.title": "語言",
|
||||||
"settings.general.row.language.description": "變更 OpenCode 的顯示語言",
|
"settings.general.row.language.description": "變更 OpenCode 的顯示語言",
|
||||||
|
"settings.general.row.shell.title": "終端機 Shell",
|
||||||
|
"settings.general.row.shell.description": "選擇終端機使用的 Shell。相容的 Shell 也會用於代理程式工具呼叫。",
|
||||||
|
"settings.general.row.shell.autoDefault": "自動(預設)",
|
||||||
|
"settings.general.row.shell.terminalOnly": "僅限終端機",
|
||||||
"settings.general.row.appearance.title": "外觀",
|
"settings.general.row.appearance.title": "外觀",
|
||||||
"settings.general.row.appearance.description": "自訂 OpenCode 在你的裝置上的外觀",
|
"settings.general.row.appearance.description": "自訂 OpenCode 在你的裝置上的外觀",
|
||||||
"settings.general.row.colorScheme.title": "配色方案",
|
"settings.general.row.colorScheme.title": "配色方案",
|
||||||
@@ -635,14 +757,28 @@ export const dict = {
|
|||||||
"settings.general.row.theme.description": "自訂 OpenCode 的主題。",
|
"settings.general.row.theme.description": "自訂 OpenCode 的主題。",
|
||||||
"settings.general.row.font.title": "程式碼字型",
|
"settings.general.row.font.title": "程式碼字型",
|
||||||
"settings.general.row.font.description": "自訂程式碼區塊使用的字型",
|
"settings.general.row.font.description": "自訂程式碼區塊使用的字型",
|
||||||
"settings.general.row.terminalFont.title": "Terminal Font",
|
"settings.general.row.terminalFont.title": "終端機字型",
|
||||||
"settings.general.row.terminalFont.description": "Customise the font used in the terminal",
|
"settings.general.row.terminalFont.description": "自訂終端機使用的字型",
|
||||||
"settings.general.row.uiFont.title": "介面字型",
|
"settings.general.row.uiFont.title": "介面字型",
|
||||||
"settings.general.row.uiFont.description": "自訂整個介面使用的字型",
|
"settings.general.row.uiFont.description": "自訂整個介面使用的字型",
|
||||||
"settings.general.row.followup.title": "後續追問行為",
|
"settings.general.row.followup.title": "後續追問行為",
|
||||||
"settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待",
|
"settings.general.row.followup.description": "選擇後續追問提示是立即引導還是進入佇列等待",
|
||||||
"settings.general.row.followup.option.queue": "佇列",
|
"settings.general.row.followup.option.queue": "佇列",
|
||||||
"settings.general.row.followup.option.steer": "引導",
|
"settings.general.row.followup.option.steer": "引導",
|
||||||
|
"settings.general.row.showFileTree.title": "檔案樹",
|
||||||
|
"settings.general.row.showFileTree.description": "在工作階段中顯示檔案樹面板",
|
||||||
|
"settings.general.row.showNavigation.title": "導覽控制項",
|
||||||
|
"settings.general.row.showNavigation.description": "在桌面標題列中顯示上一頁和下一頁按鈕",
|
||||||
|
"settings.general.row.showSearch.title": "命令面板",
|
||||||
|
"settings.general.row.showSearch.description": "在標題列中顯示搜尋和命令面板按鈕",
|
||||||
|
"settings.general.row.showTerminal.title": "終端機",
|
||||||
|
"settings.general.row.showTerminal.description": "在桌面標題列中顯示終端機按鈕",
|
||||||
|
"settings.general.row.showStatus.title": "伺服器狀態",
|
||||||
|
"settings.general.row.showStatus.description": "在標題列中顯示伺服器狀態按鈕",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.title": "底部導覽",
|
||||||
|
"settings.general.row.mobileTitlebarBottom.description": "在行動裝置上將標題列和工作階段分頁置於畫面底部",
|
||||||
|
"settings.general.row.showCustomAgents.title": "自訂代理程式",
|
||||||
|
"settings.general.row.showCustomAgents.description": "在輸入區顯示代理程式選擇器",
|
||||||
"settings.general.row.reasoningSummaries.title": "顯示推理摘要",
|
"settings.general.row.reasoningSummaries.title": "顯示推理摘要",
|
||||||
"settings.general.row.reasoningSummaries.description": "在時間軸中顯示模型推理摘要",
|
"settings.general.row.reasoningSummaries.description": "在時間軸中顯示模型推理摘要",
|
||||||
|
|
||||||
@@ -650,6 +786,10 @@ export const dict = {
|
|||||||
"settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊",
|
"settings.general.row.shellToolPartsExpanded.description": "在時間軸中預設展開 shell 工具區塊",
|
||||||
"settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊",
|
"settings.general.row.editToolPartsExpanded.title": "展開 edit 工具區塊",
|
||||||
"settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊",
|
"settings.general.row.editToolPartsExpanded.description": "在時間軸中預設展開 edit、write 和 patch 工具區塊",
|
||||||
|
"settings.general.row.newLayoutDesigns.title": "新版面與設計",
|
||||||
|
"settings.general.row.newLayoutDesigns.description": "啟用重新設計的版面、首頁、輸入區和工作階段介面",
|
||||||
|
"settings.general.row.pinchZoom.title": "雙指縮放",
|
||||||
|
"settings.general.row.pinchZoom.description": "允許使用觸控板雙指開合和 Ctrl-捲動手勢縮放",
|
||||||
"settings.general.row.wayland.title": "使用原生 Wayland",
|
"settings.general.row.wayland.title": "使用原生 Wayland",
|
||||||
"settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
|
"settings.general.row.wayland.description": "在 Wayland 上停用 X11 後備模式。需要重新啟動。",
|
||||||
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
|
"settings.general.row.wayland.tooltip": "在混合更新率螢幕的 Linux 系統上,原生 Wayland 可能更穩定。",
|
||||||
@@ -663,6 +803,8 @@ export const dict = {
|
|||||||
"settings.updates.row.check.description": "手動檢查更新並在有更新時安裝",
|
"settings.updates.row.check.description": "手動檢查更新並在有更新時安裝",
|
||||||
"settings.updates.action.checkNow": "立即檢查",
|
"settings.updates.action.checkNow": "立即檢查",
|
||||||
"settings.updates.action.checking": "檢查中...",
|
"settings.updates.action.checking": "檢查中...",
|
||||||
|
"settings.updates.action.downloading": "下載中...",
|
||||||
|
"settings.updates.action.installing": "安裝中...",
|
||||||
"settings.updates.toast.latest.title": "已是最新版本",
|
"settings.updates.toast.latest.title": "已是最新版本",
|
||||||
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
|
"settings.updates.toast.latest.description": "你正在使用最新版本的 OpenCode。",
|
||||||
|
|
||||||
|
|||||||
@@ -224,7 +224,26 @@
|
|||||||
|
|
||||||
[data-slot="model-selector-scroll"]::before {
|
[data-slot="model-selector-scroll"]::before {
|
||||||
top: 0;
|
top: 0;
|
||||||
background: linear-gradient(to bottom, var(--v2-background-bg-layer-01), transparent);
|
height: 12px;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="manage-models-scroll"]::before {
|
[data-slot="manage-models-scroll"]::before {
|
||||||
@@ -239,7 +258,26 @@
|
|||||||
|
|
||||||
[data-slot="model-selector-scroll"]::after {
|
[data-slot="model-selector-scroll"]::after {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: linear-gradient(to top, var(--v2-background-bg-layer-01), transparent);
|
height: 12px;
|
||||||
|
background: linear-gradient(
|
||||||
|
0deg,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 80%, transparent) 0%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 78.3336%, transparent) 4.7%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 76.4824%, transparent) 8.9%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 74.368%, transparent) 12.8%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 71.9123%, transparent) 16.56%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 69.037%, transparent) 20.37%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 65.664%, transparent) 24.4%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 61.715%, transparent) 28.83%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 57.1117%, transparent) 33.84%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 51.776%, transparent) 39.6%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 45.6296%, transparent) 46.3%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 38.5944%, transparent) 54.1%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 30.592%, transparent) 63.2%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 21.5443%, transparent) 73.76%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 11.373%, transparent) 85.97%,
|
||||||
|
color-mix(in srgb, var(--v2-background-bg-layer-01) 0%, transparent) 100%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) {
|
@supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { usePlatform } from "@/context/platform"
|
|||||||
import { DateTime } from "luxon"
|
import { DateTime } from "luxon"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useDirectoryPicker } from "@/components/directory-picker"
|
import { useDirectoryPicker } from "@/components/directory-picker"
|
||||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
import { useSettingsDialog } from "@/components/settings-dialog"
|
||||||
import { DialogSelectServer, useServerManagementController } from "@/components/dialog-select-server"
|
import { DialogSelectServer, useServerManagementController } from "@/components/dialog-select-server"
|
||||||
import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2"
|
import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2"
|
||||||
import { ServerConnection, serverName, useServer } from "@/context/server"
|
import { ServerConnection, serverName, useServer } from "@/context/server"
|
||||||
@@ -267,7 +267,7 @@ export function NewHome() {
|
|||||||
const command = useCommand()
|
const command = useCommand()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const marked = useMarked()
|
const marked = useMarked()
|
||||||
const openSettings = useSettingsCommand()
|
const openSettings = useSettingsDialog()
|
||||||
let focusSessionSearch: (() => void) | undefined
|
let focusSessionSearch: (() => void) | undefined
|
||||||
const [state, setState] = createStore({
|
const [state, setState] = createStore({
|
||||||
search: "",
|
search: "",
|
||||||
@@ -1278,7 +1278,7 @@ function HomeSessionSearch(props: {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02">
|
<label class="relative z-20 flex h-9 w-full items-center gap-2 rounded-[6px] bg-v2-background-bg-layer-02/60 py-1 pl-3 pr-2 text-v2-icon-icon-muted transition-[background-color,box-shadow] duration-[120ms] ease-in-out hover:bg-v2-background-bg-layer-02 focus-within:bg-v2-background-bg-layer-02">
|
||||||
<IconV2 name="magnifying-glass" />
|
<IconV2 name="magnifying-glass" />
|
||||||
<input
|
<input
|
||||||
ref={input}
|
ref={input}
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
|
|||||||
import { usePlatform } from "@/context/platform"
|
import { usePlatform } from "@/context/platform"
|
||||||
import { setNavigate } from "@/utils/notification-click"
|
import { setNavigate } from "@/utils/notification-click"
|
||||||
import { setV2Toast, ToastRegion } from "@/utils/toast"
|
import { setV2Toast, ToastRegion } from "@/utils/toast"
|
||||||
|
import { useSettingsCommand } from "@/components/settings-dialog"
|
||||||
|
|
||||||
export default function NewLayout(props: ParentProps) {
|
export default function NewLayout(props: ParentProps) {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
setNavigate(navigate)
|
setNavigate(navigate)
|
||||||
|
useSettingsCommand()
|
||||||
|
|
||||||
createEffect(() => setV2Toast(true))
|
createEffect(() => setV2Toast(true))
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { createMemo, type Accessor } from "solid-js"
|
|||||||
import { useGlobal } from "@/context/global"
|
import { useGlobal } from "@/context/global"
|
||||||
import { useNotification } from "@/context/notification"
|
import { useNotification } from "@/context/notification"
|
||||||
import { usePermission } from "@/context/permission"
|
import { usePermission } from "@/context/permission"
|
||||||
|
import { sessionPermissionRequest, sessionQuestionRequest } from "@/pages/session/composer/session-request-tree"
|
||||||
import { ServerConnection } from "@/context/server"
|
import { ServerConnection } from "@/context/server"
|
||||||
import { sessionPermissionRequest } from "@/pages/session/composer/session-request-tree"
|
|
||||||
|
|
||||||
export function useSessionTabAvatarState(
|
export function useSessionTabAvatarState(
|
||||||
server: Accessor<ServerConnection.Key>,
|
server: Accessor<ServerConnection.Key>,
|
||||||
@@ -26,15 +26,20 @@ export function useSessionTabAvatarState(
|
|||||||
return !permission.autoResponds(item, directory())
|
return !permission.autoResponds(item, directory())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const unread = createMemo(() => {
|
const hasQuestions = createMemo(() => {
|
||||||
if (hasPermissions()) return true
|
const serverSync = sync()
|
||||||
if (!connection()) return false
|
if (!serverSync) return false
|
||||||
return notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0
|
const [store] = serverSync.child(directory(), { bootstrap: false })
|
||||||
|
return !!sessionQuestionRequest(store.session, serverSync.session.data.question, sessionId())
|
||||||
})
|
})
|
||||||
|
const needsAttention = createMemo(() => hasPermissions() || hasQuestions())
|
||||||
|
const unread = createMemo(
|
||||||
|
() => needsAttention() || notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0,
|
||||||
|
)
|
||||||
const loading = createMemo(() => {
|
const loading = createMemo(() => {
|
||||||
const serverSync = sync()
|
const serverSync = sync()
|
||||||
if (!serverSync) return false
|
if (!serverSync) return false
|
||||||
if (hasPermissions()) return false
|
if (needsAttention()) return false
|
||||||
return serverSync.session.data.session_working(sessionId())
|
return serverSync.session.data.session_working(sessionId())
|
||||||
})
|
})
|
||||||
return { unread, loading }
|
return { unread, loading }
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|||||||
import { NewSessionDesignView } from "@/components/session"
|
import { NewSessionDesignView } from "@/components/session"
|
||||||
import { PromptInput } from "@/components/prompt-input"
|
import { PromptInput } from "@/components/prompt-input"
|
||||||
import { StatusPopoverV2 } from "@/components/status-popover"
|
import { StatusPopoverV2 } from "@/components/status-popover"
|
||||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
|
||||||
import {
|
import {
|
||||||
PromptProjectAddButton,
|
PromptProjectAddButton,
|
||||||
PromptProjectSelector,
|
PromptProjectSelector,
|
||||||
@@ -45,7 +44,6 @@ export default function NewSessionPage() {
|
|||||||
const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>()
|
const [searchParams, setSearchParams] = useSearchParams<{ draftId?: string; prompt?: string }>()
|
||||||
|
|
||||||
useComposerCommands()
|
useComposerCommands()
|
||||||
useSettingsCommand()
|
|
||||||
|
|
||||||
let inputRef: HTMLDivElement | undefined
|
let inputRef: HTMLDivElement | undefined
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-b
|
|||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { showToast } from "@/utils/toast"
|
import { showToast } from "@/utils/toast"
|
||||||
import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
|
import { base64Encode, checksum } from "@opencode-ai/core/util/encode"
|
||||||
import { Navigate, useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
import { useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router"
|
||||||
import { NewSessionView, SessionHeader } from "@/components/session"
|
import { NewSessionView, SessionHeader } from "@/components/session"
|
||||||
import { ErrorPage } from "@/pages/error"
|
import { ErrorPage } from "@/pages/error"
|
||||||
import { CommentsProvider, useComments } from "@/context/comments"
|
import { CommentsProvider, useComments } from "@/context/comments"
|
||||||
@@ -56,7 +56,6 @@ import { useSync } from "@/context/sync"
|
|||||||
import { useTabs } from "@/context/tabs"
|
import { useTabs } from "@/context/tabs"
|
||||||
import { TerminalProvider, useTerminal } from "@/context/terminal"
|
import { TerminalProvider, useTerminal } from "@/context/terminal"
|
||||||
import { PromptInput } from "@/components/prompt-input"
|
import { PromptInput } from "@/components/prompt-input"
|
||||||
import { useSettingsCommand } from "@/components/settings-dialog"
|
|
||||||
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
||||||
import {
|
import {
|
||||||
createPromptInputController,
|
createPromptInputController,
|
||||||
@@ -153,7 +152,7 @@ export function TargetSessionRouteContent() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SessionRouteErrorBoundary(
|
export function SessionRouteErrorBoundary(
|
||||||
props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>,
|
props: ParentProps<{ sessionID?: string; serverKey?: ServerConnection.Key; padded?: boolean }>,
|
||||||
) {
|
) {
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
@@ -221,7 +220,6 @@ function SessionErrorFallback(props: { error: unknown; sessionID?: string; serve
|
|||||||
|
|
||||||
function ResolvedTargetSessionRoute() {
|
function ResolvedTargetSessionRoute() {
|
||||||
const params = useParams<{ serverKey: string; id: string }>()
|
const params = useParams<{ serverKey: string; id: string }>()
|
||||||
const settings = useSettings()
|
|
||||||
const tabs = useTabs()
|
const tabs = useTabs()
|
||||||
const sync = useServerSync()
|
const sync = useServerSync()
|
||||||
const serverKey = createMemo(() => requireServerKey(params.serverKey))
|
const serverKey = createMemo(() => requireServerKey(params.serverKey))
|
||||||
@@ -248,17 +246,12 @@ function ResolvedTargetSessionRoute() {
|
|||||||
the terminal. Same-workspace tab switches keep it open because warm
|
the terminal. Same-workspace tab switches keep it open because warm
|
||||||
targets resolve synchronously from the sync cache. */}
|
targets resolve synchronously from the sync cache. */}
|
||||||
<Show when={directory()}>
|
<Show when={directory()}>
|
||||||
<Show
|
|
||||||
when={settings.general.newLayoutDesigns()}
|
|
||||||
fallback={<Navigate href={legacySessionHref(directory()!, params.id)} />}
|
|
||||||
>
|
|
||||||
<SDKProvider directory={targetDirectory}>
|
<SDKProvider directory={targetDirectory}>
|
||||||
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
|
<DirectoryDataProvider directory={targetDirectory} server={serverKey}>
|
||||||
<TargetSessionPage />
|
<TargetSessionPage />
|
||||||
</DirectoryDataProvider>
|
</DirectoryDataProvider>
|
||||||
</SDKProvider>
|
</SDKProvider>
|
||||||
</Show>
|
</Show>
|
||||||
</Show>
|
|
||||||
</TargetServerScopedProviders>
|
</TargetServerScopedProviders>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -839,7 +832,7 @@ export default function Page() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const stopVcs = sdk().event.listen((evt) => {
|
const stopVcs = sdk().event.listen((evt) => {
|
||||||
if (evt.details.type !== "filesystem.changed") return
|
if (evt.details.type !== "file.watcher.updated") return
|
||||||
const props =
|
const props =
|
||||||
typeof evt.details.properties === "object" && evt.details.properties
|
typeof evt.details.properties === "object" && evt.details.properties
|
||||||
? (evt.details.properties as Record<string, unknown>)
|
? (evt.details.properties as Record<string, unknown>)
|
||||||
@@ -1021,7 +1014,6 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useComposerCommands()
|
useComposerCommands()
|
||||||
useSettingsCommand()
|
|
||||||
useSessionCommands({
|
useSessionCommands({
|
||||||
navigateMessageByOffset,
|
navigateMessageByOffset,
|
||||||
setActiveMessage,
|
setActiveMessage,
|
||||||
|
|||||||
@@ -160,8 +160,10 @@ function TimelineDiffSummaryRow(props: { diffs: SummaryDiff[] }) {
|
|||||||
>
|
>
|
||||||
<div data-slot="session-turn-diffs-header">
|
<div data-slot="session-turn-diffs-header">
|
||||||
<span data-slot="session-turn-diffs-label">
|
<span data-slot="session-turn-diffs-label">
|
||||||
{props.diffs.length} {language.t("ui.sessionTurn.diffs.changed")}{" "}
|
{language.t(
|
||||||
{language.t(props.diffs.length === 1 ? "ui.common.file.one" : "ui.common.file.other")}
|
props.diffs.length === 1 ? "ui.sessionTurn.diffs.changed.one" : "ui.sessionTurn.diffs.changed.other",
|
||||||
|
{ count: String(props.diffs.length) },
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<DiffChanges changes={props.diffs} />
|
<DiffChanges changes={props.diffs} />
|
||||||
<Show when={overflow() > 0}>
|
<Show when={overflow() > 0}>
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ import { createSessionTabs } from "@/pages/session/helpers"
|
|||||||
import { extractPromptFromParts } from "@/utils/prompt"
|
import { extractPromptFromParts } from "@/utils/prompt"
|
||||||
import { UserMessage } from "@opencode-ai/sdk/v2"
|
import { UserMessage } from "@opencode-ai/sdk/v2"
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
import { useTabs } from "@/context/tabs"
|
|
||||||
import { requireServerKey } from "@/utils/session-route"
|
|
||||||
import { createSessionOwnership } from "./session-ownership"
|
import { createSessionOwnership } from "./session-ownership"
|
||||||
|
|
||||||
export type SessionCommandContext = {
|
export type SessionCommandContext = {
|
||||||
@@ -48,7 +46,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const terminal = useTerminal()
|
const terminal = useTerminal()
|
||||||
const sessionTabs = useTabs()
|
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { params, sessionKey, tabs, view } = useSessionLayout()
|
const { params, sessionKey, tabs, view } = useSessionLayout()
|
||||||
@@ -269,6 +266,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
view().terminal.open()
|
view().terminal.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const closeTerminal = () => {
|
||||||
|
const id = terminal.active()
|
||||||
|
if (!id) return
|
||||||
|
const last = terminal.all().length === 1
|
||||||
|
void terminal.close(id)
|
||||||
|
if (last) view().terminal.close()
|
||||||
|
}
|
||||||
|
|
||||||
const chooseMcp = () => {
|
const chooseMcp = () => {
|
||||||
void openDialog(
|
void openDialog(
|
||||||
() => import("@/components/dialog-select-mcp"),
|
() => import("@/components/dialog-select-mcp"),
|
||||||
@@ -411,9 +416,9 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
title: language.t("command.session.new"),
|
title: language.t("command.session.new"),
|
||||||
keybind: "mod+shift+s",
|
keybind: "mod+shift+s",
|
||||||
slash: "new",
|
slash: "new",
|
||||||
onSelect: () => {
|
onSelect: (source) => {
|
||||||
if (params.serverKey) {
|
if (settings.general.newLayoutDesigns()) {
|
||||||
sessionTabs.newDraft({ server: requireServerKey(params.serverKey), directory: sdk().directory })
|
command.trigger("tab.new", source)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
navigate(`/${params.dir}/session`)
|
navigate(`/${params.dir}/session`)
|
||||||
@@ -518,6 +523,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
const terminalCmds = () => [
|
const terminalCmds = () => [
|
||||||
|
terminalCommand({
|
||||||
|
id: "terminal.close",
|
||||||
|
title: language.t("terminal.close"),
|
||||||
|
keybind: "mod+w",
|
||||||
|
hidden: true,
|
||||||
|
when: (event) => event.target instanceof Element && !!event.target.closest('[data-component="terminal"]'),
|
||||||
|
onSelect: closeTerminal,
|
||||||
|
}),
|
||||||
terminalCommand({
|
terminalCommand({
|
||||||
id: "terminal.new",
|
id: "terminal.new",
|
||||||
title: language.t("command.terminal.new"),
|
title: language.t("command.terminal.new"),
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# V2 CLI and TUI development guide
|
|
||||||
|
|
||||||
## Migration context
|
|
||||||
|
|
||||||
- The TUI is being ported from legacy APIs to the new V2 APIs. New and migrated TUI behavior should use `sdk.client.v2` and the location-scoped data in `packages/tui/src/context/data.tsx` instead of adding dependencies on legacy sync state.
|
|
||||||
- Preserve established TUI behavior unless the task intentionally changes it.
|
|
||||||
- Load the `opencode-dev` skill before interactively running, debugging, or verifying opencode's V2 CLI, TUI, or server.
|
|
||||||
@@ -31,11 +31,11 @@ function run(target) {
|
|||||||
|
|
||||||
const envPath = process.env.OPENCODE_BIN_PATH
|
const envPath = process.env.OPENCODE_BIN_PATH
|
||||||
const scriptDir = path.dirname(fs.realpathSync(__filename))
|
const scriptDir = path.dirname(fs.realpathSync(__filename))
|
||||||
const cached = path.join(scriptDir, ".opencode2")
|
const cached = path.join(scriptDir, ".lildax")
|
||||||
const platform = { darwin: "darwin", linux: "linux", win32: "windows" }[os.platform()] || os.platform()
|
const platform = { darwin: "darwin", linux: "linux", win32: "windows" }[os.platform()] || os.platform()
|
||||||
const arch = { x64: "x64", arm64: "arm64", arm: "arm" }[os.arch()] || os.arch()
|
const arch = { x64: "x64", arm64: "arm64", arm: "arm" }[os.arch()] || os.arch()
|
||||||
const base = "@opencode-ai/cli-" + platform + "-" + arch
|
const base = "@opencode-ai/cli-" + platform + "-" + arch
|
||||||
const binary = platform === "windows" ? "opencode2.exe" : "opencode2"
|
const binary = platform === "windows" ? "lildax.exe" : "lildax"
|
||||||
|
|
||||||
function supportsAvx2() {
|
function supportsAvx2() {
|
||||||
if (arch !== "x64") return false
|
if (arch !== "x64") return false
|
||||||
@@ -121,7 +121,7 @@ function findBinary(startDir) {
|
|||||||
const resolved = envPath || (fs.existsSync(cached) ? cached : findBinary(scriptDir))
|
const resolved = envPath || (fs.existsSync(cached) ? cached : findBinary(scriptDir))
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
console.error(
|
console.error(
|
||||||
"It seems that your package manager failed to install the right opencode2 CLI package. Try manually installing " +
|
"It seems that your package manager failed to install the right lildax CLI package. Try manually installing " +
|
||||||
names.map((name) => `"${name}"`).join(" or ") +
|
names.map((name) => `"${name}"`).join(" or ") +
|
||||||
" package",
|
" package",
|
||||||
)
|
)
|
||||||
@@ -1,62 +1,36 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/cli",
|
"name": "@opencode-ai/cli",
|
||||||
"version": "1.17.14",
|
"version": "1.17.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode2": "./bin/opencode2.cjs"
|
"lildax": "./bin/lildax.cjs"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"bin"
|
"bin"
|
||||||
],
|
],
|
||||||
"exports": {
|
|
||||||
"./daemon": "./src/daemon.ts",
|
|
||||||
"./mini": "./src/mini/index.ts",
|
|
||||||
"./mini/footer.command": "./src/mini/footer.command.tsx",
|
|
||||||
"./mini/footer.menu": "./src/mini/footer.menu.tsx",
|
|
||||||
"./mini/footer.permission": "./src/mini/footer.permission.tsx",
|
|
||||||
"./mini/footer.prompt": "./src/mini/footer.prompt.tsx",
|
|
||||||
"./mini/footer.question": "./src/mini/footer.question.tsx",
|
|
||||||
"./mini/footer.subagent": "./src/mini/footer.subagent.tsx",
|
|
||||||
"./mini/footer.view": "./src/mini/footer.view.tsx",
|
|
||||||
"./mini/scrollback.writer": "./src/mini/scrollback.writer.tsx",
|
|
||||||
"./mini/*": "./src/mini/*.ts",
|
|
||||||
"./server-process": "./src/server-process.ts"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "bun run script/build.ts",
|
"build": "bun run script/build.ts",
|
||||||
"dev": "bun run src/index.ts",
|
"dev": "bun run src/index.ts",
|
||||||
"test": "bun test --timeout 30000 --only-failures",
|
|
||||||
"typecheck": "tsgo --noEmit"
|
"typecheck": "tsgo --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
"@opencode-ai/client": "workspace:*",
|
|
||||||
"@opencode-ai/codemode": "workspace:*",
|
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
|
||||||
"@opencode-ai/schema": "workspace:*",
|
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@opencode-ai/server": "workspace:*",
|
"@opencode-ai/server": "workspace:*",
|
||||||
"@opencode-ai/tui": "workspace:*",
|
"@opencode-ai/tui": "workspace:*",
|
||||||
"@opentui/core": "catalog:",
|
"@opentui/core": "catalog:",
|
||||||
"@opentui/keymap": "catalog:",
|
|
||||||
"@opentui/solid": "catalog:",
|
"@opentui/solid": "catalog:",
|
||||||
"@parcel/watcher": "2.5.1",
|
"@parcel/watcher": "2.5.1",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fuzzysort": "catalog:",
|
"solid-js": "catalog:"
|
||||||
"jsonc-parser": "3.3.1",
|
|
||||||
"opentui-spinner": "catalog:",
|
|
||||||
"semver": "catalog:",
|
|
||||||
"solid-js": "catalog:",
|
|
||||||
"strip-ansi": "7.1.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@opencode-ai/script": "workspace:*",
|
"@opencode-ai/script": "workspace:*",
|
||||||
"@tsconfig/bun": "catalog:",
|
"@tsconfig/bun": "catalog:",
|
||||||
"@types/bun": "catalog:",
|
"@types/bun": "catalog:",
|
||||||
"@types/semver": "catalog:",
|
|
||||||
"@typescript/native-preview": "catalog:"
|
"@typescript/native-preview": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import pkg from "../package.json"
|
|||||||
import { modelsData } from "./generate"
|
import { modelsData } from "./generate"
|
||||||
|
|
||||||
const dir = path.resolve(import.meta.dirname, "..")
|
const dir = path.resolve(import.meta.dirname, "..")
|
||||||
const binary = "opencode2"
|
const binary = "lildax"
|
||||||
process.chdir(dir)
|
process.chdir(dir)
|
||||||
|
|
||||||
await rm("dist", { recursive: true, force: true })
|
await rm("dist", { recursive: true, force: true })
|
||||||
@@ -18,6 +18,7 @@ await rm("dist", { recursive: true, force: true })
|
|||||||
const singleFlag = process.argv.includes("--single")
|
const singleFlag = process.argv.includes("--single")
|
||||||
const baselineFlag = process.argv.includes("--baseline")
|
const baselineFlag = process.argv.includes("--baseline")
|
||||||
const skipInstall = process.argv.includes("--skip-install")
|
const skipInstall = process.argv.includes("--skip-install")
|
||||||
|
const sourcemapsFlag = process.argv.includes("--sourcemaps")
|
||||||
const plugin = createSolidTransformPlugin()
|
const plugin = createSolidTransformPlugin()
|
||||||
|
|
||||||
const allTargets: {
|
const allTargets: {
|
||||||
@@ -73,7 +74,7 @@ for (const item of targets) {
|
|||||||
external: ["node-gyp"],
|
external: ["node-gyp"],
|
||||||
format: "esm",
|
format: "esm",
|
||||||
minify: true,
|
minify: true,
|
||||||
sourcemap: "inline",
|
sourcemap: sourcemapsFlag ? "linked" : "none",
|
||||||
splitting: true,
|
splitting: true,
|
||||||
compile: {
|
compile: {
|
||||||
autoloadBunfig: false,
|
autoloadBunfig: false,
|
||||||
|
|||||||
@@ -25,15 +25,14 @@ for (const filepath of new Bun.Glob("*/package.json").scanSync({ cwd: "./dist" }
|
|||||||
}
|
}
|
||||||
console.log("binaries", binaries)
|
console.log("binaries", binaries)
|
||||||
const version = Object.values(binaries)[0]
|
const version = Object.values(binaries)[0]
|
||||||
const name = pkg.name
|
|
||||||
|
|
||||||
await $`mkdir -p ./dist/${name}/bin`
|
await $`mkdir -p ./dist/${pkg.name}/bin`
|
||||||
await $`cp ./bin/opencode2.cjs ./dist/${name}/bin/opencode2`
|
await $`cp ./bin/lildax.cjs ./dist/${pkg.name}/bin/lildax`
|
||||||
await Bun.file(`./dist/${name}/package.json`).write(
|
await Bun.file(`./dist/${pkg.name}/package.json`).write(
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
{
|
{
|
||||||
name,
|
name: pkg.name,
|
||||||
bin: { opencode2: "./bin/opencode2" },
|
bin: { lildax: "./bin/lildax" },
|
||||||
version,
|
version,
|
||||||
license: pkg.license,
|
license: pkg.license,
|
||||||
repository: { type: "git", url: "git+https://github.com/anomalyco/opencode.git" },
|
repository: { type: "git", url: "git+https://github.com/anomalyco/opencode.git" },
|
||||||
@@ -51,4 +50,4 @@ await Promise.all(
|
|||||||
publish(`./dist/${name.replace("@opencode-ai/", "")}`, name, version),
|
publish(`./dist/${name.replace("@opencode-ai/", "")}`, name, version),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
await publish(`./dist/${name}`, name, version)
|
await publish(`./dist/${pkg.name}`, pkg.name, version)
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
export * as CodeModeHost from "./code-mode"
|
|
||||||
|
|
||||||
import { NodeHttpClient } from "@effect/platform-node"
|
|
||||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|
||||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
|
||||||
import { OpenAPI, Tool } from "@opencode-ai/codemode"
|
|
||||||
import { Api } from "@opencode-ai/server/api"
|
|
||||||
import { ServerAuth } from "@opencode-ai/server/auth"
|
|
||||||
import { Effect, Layer } from "effect"
|
|
||||||
import { HttpClient, HttpClientRequest } from "effect/unstable/http"
|
|
||||||
import { OpenApi } from "effect/unstable/httpapi"
|
|
||||||
import type { Server } from "node:http"
|
|
||||||
|
|
||||||
export function replacements(server: Server, password: string): LayerNode.Replacements {
|
|
||||||
return [ToolRegistry.codeModeReplacement(makeTools(client(server), password))]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeTools(client: Layer.Layer<HttpClient.HttpClient>, password: string): ToolRegistry.CodeModeTools {
|
|
||||||
return {
|
|
||||||
opencode: bindTools(
|
|
||||||
OpenAPI.fromSpec({
|
|
||||||
spec: { ...OpenApi.fromApi(Api) },
|
|
||||||
baseUrl: "http://opencode.local",
|
|
||||||
headers: ServerAuth.headers({ username: "opencode", password }),
|
|
||||||
}).tools,
|
|
||||||
client,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function client(server: Server) {
|
|
||||||
return Layer.effect(
|
|
||||||
HttpClient.HttpClient,
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const client = yield* HttpClient.HttpClient
|
|
||||||
return HttpClient.mapRequest(client, (request) => {
|
|
||||||
const address = server.address()
|
|
||||||
if (!address || typeof address === "string") throw new Error("OpenCode server is not listening")
|
|
||||||
const local =
|
|
||||||
address.address === "0.0.0.0" ? "127.0.0.1" : address.address === "::" ? "::1" : address.address
|
|
||||||
const host = local.includes(":") && !local.startsWith("[") ? `[${local}]` : local
|
|
||||||
const url = new URL(request.url)
|
|
||||||
return HttpClientRequest.setUrl(
|
|
||||||
request,
|
|
||||||
new URL(`${url.pathname}${url.search}${url.hash}`, `http://${host}:${address.port}`),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
).pipe(Layer.provide(NodeHttpClient.layerNodeHttp))
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindTools(tools: OpenAPI.Tools, client: Layer.Layer<HttpClient.HttpClient>): ToolRegistry.CodeModeTools {
|
|
||||||
return Object.fromEntries(
|
|
||||||
Object.entries(tools).map(([name, value]) => [
|
|
||||||
name,
|
|
||||||
Tool.isDefinition<HttpClient.HttpClient>(value)
|
|
||||||
? Tool.make({
|
|
||||||
description: value.description,
|
|
||||||
input: value.input,
|
|
||||||
output: value.output,
|
|
||||||
run: (input) => value.run(input).pipe(Effect.provide(client)),
|
|
||||||
})
|
|
||||||
: bindTools(value, client),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -3,57 +3,8 @@ import { Spec } from "../framework/spec"
|
|||||||
|
|
||||||
declare const OPENCODE_CLI_NAME: string | undefined
|
declare const OPENCODE_CLI_NAME: string | undefined
|
||||||
|
|
||||||
const MiniParams = {
|
|
||||||
continue: Flag.boolean("continue").pipe(
|
|
||||||
Flag.withAlias("c"),
|
|
||||||
Flag.withDescription("Continue the last session"),
|
|
||||||
Flag.withDefault(false),
|
|
||||||
),
|
|
||||||
session: Flag.string("session").pipe(
|
|
||||||
Flag.withAlias("s"),
|
|
||||||
Flag.withDescription("Session ID to continue"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
fork: Flag.boolean("fork").pipe(
|
|
||||||
Flag.withDescription("Fork the session when continuing"),
|
|
||||||
Flag.withDefault(false),
|
|
||||||
),
|
|
||||||
replay: Flag.boolean("replay").pipe(
|
|
||||||
Flag.withDescription("Replay session history on resume and after resize"),
|
|
||||||
Flag.withDefault(true),
|
|
||||||
),
|
|
||||||
replayLimit: Flag.integer("replay-limit").pipe(
|
|
||||||
Flag.withDescription("Cap visible replay to the newest N messages"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCODE_CLI_NAME : "opencode", {
|
export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCODE_CLI_NAME : "opencode", {
|
||||||
description: "OpenCode 2.0 preview command line interface",
|
description: "OpenCode 2.0 preview command line interface",
|
||||||
params: {
|
|
||||||
directory: Argument.string("directory").pipe(
|
|
||||||
Argument.withDescription("Directory to start OpenCode in"),
|
|
||||||
Argument.optional,
|
|
||||||
),
|
|
||||||
standalone: Flag.boolean("standalone").pipe(
|
|
||||||
Flag.withDescription("Run with a private server instead of the background service"),
|
|
||||||
Flag.withDefault(false),
|
|
||||||
),
|
|
||||||
server: Flag.string("server").pipe(
|
|
||||||
Flag.withDescription("Connect to a server URL instead of the background service"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
continue: Flag.boolean("continue").pipe(
|
|
||||||
Flag.withAlias("c"),
|
|
||||||
Flag.withDescription("Continue the last session"),
|
|
||||||
Flag.withDefault(false),
|
|
||||||
),
|
|
||||||
session: Flag.string("session").pipe(
|
|
||||||
Flag.withAlias("s"),
|
|
||||||
Flag.withDescription("Session ID to continue"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
commands: [
|
commands: [
|
||||||
Spec.make("api", {
|
Spec.make("api", {
|
||||||
description: "Make a request to the running server",
|
description: "Make a request to the running server",
|
||||||
@@ -75,124 +26,7 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
|
|||||||
description: "Debugging and troubleshooting tools",
|
description: "Debugging and troubleshooting tools",
|
||||||
commands: [Spec.make("agents", { description: "List all agents" })],
|
commands: [Spec.make("agents", { description: "List all agents" })],
|
||||||
}),
|
}),
|
||||||
Spec.make("mcp", {
|
|
||||||
description: "Manage MCP (Model Context Protocol) servers",
|
|
||||||
commands: [
|
|
||||||
Spec.make("list", { description: "List configured MCP servers and their status" }),
|
|
||||||
Spec.make("add", {
|
|
||||||
description: "Add an MCP server to your configuration",
|
|
||||||
params: {
|
|
||||||
name: Argument.string("name").pipe(Argument.withDescription("Name of the MCP server")),
|
|
||||||
command: Argument.string("command").pipe(
|
|
||||||
Argument.withDescription("Command and arguments for a local server, passed after --"),
|
|
||||||
Argument.variadic({ min: 0 }),
|
|
||||||
),
|
|
||||||
url: Flag.string("url").pipe(Flag.withDescription("URL for a remote MCP server"), Flag.optional),
|
|
||||||
header: Flag.keyValuePair("header").pipe(
|
|
||||||
Flag.withDescription("HTTP header for a remote server, as name=value"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
env: Flag.keyValuePair("env").pipe(
|
|
||||||
Flag.withDescription("Environment variable for a local server, as name=value"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
global: Flag.boolean("global").pipe(
|
|
||||||
Flag.withDescription("Write to the global config instead of the project config"),
|
|
||||||
Flag.withDefault(false),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
Spec.make("auth", {
|
|
||||||
description: "Authenticate with an OAuth-capable remote MCP server",
|
|
||||||
params: { name: Argument.string("name").pipe(Argument.withDescription("Name of the MCP server")) },
|
|
||||||
}),
|
|
||||||
Spec.make("logout", {
|
|
||||||
description: "Remove stored OAuth credentials for an MCP server",
|
|
||||||
params: { name: Argument.string("name").pipe(Argument.withDescription("Name of the MCP server")) },
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
Spec.make("migrate", { description: "Migrate v1 data to v2" }),
|
Spec.make("migrate", { description: "Migrate v1 data to v2" }),
|
||||||
Spec.make("mini", {
|
|
||||||
description: "Start the minimal interactive interface",
|
|
||||||
params: {
|
|
||||||
...MiniParams,
|
|
||||||
project: Argument.string("project").pipe(
|
|
||||||
Argument.withDescription("Path to start OpenCode in"),
|
|
||||||
Argument.optional,
|
|
||||||
),
|
|
||||||
model: Flag.string("model").pipe(
|
|
||||||
Flag.withAlias("m"),
|
|
||||||
Flag.withDescription("Model to use in the format provider/model"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
agent: Flag.string("agent").pipe(Flag.withDescription("Agent to use"), Flag.optional),
|
|
||||||
prompt: Flag.string("prompt").pipe(Flag.withDescription("Prompt to use"), Flag.optional),
|
|
||||||
server: Flag.string("server").pipe(
|
|
||||||
Flag.withDescription("Connect to a server URL instead of the background service"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
demo: Flag.boolean("demo").pipe(Flag.withDefault(false), Flag.withHidden),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
Spec.make("run", {
|
|
||||||
description: "Run OpenCode with a message",
|
|
||||||
params: {
|
|
||||||
message: Argument.string("message").pipe(
|
|
||||||
Argument.withDescription("Message to send"),
|
|
||||||
Argument.variadic({ min: 0 }),
|
|
||||||
),
|
|
||||||
continue: Flag.boolean("continue").pipe(
|
|
||||||
Flag.withAlias("c"),
|
|
||||||
Flag.withDescription("Continue the last session"),
|
|
||||||
Flag.withDefault(false),
|
|
||||||
),
|
|
||||||
session: Flag.string("session").pipe(
|
|
||||||
Flag.withAlias("s"),
|
|
||||||
Flag.withDescription("Session ID to continue"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
fork: Flag.boolean("fork").pipe(
|
|
||||||
Flag.withDescription("Fork the session before continuing"),
|
|
||||||
Flag.withDefault(false),
|
|
||||||
),
|
|
||||||
model: Flag.string("model").pipe(
|
|
||||||
Flag.withAlias("m"),
|
|
||||||
Flag.withDescription("Model to use in the format provider/model"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
agent: Flag.string("agent").pipe(Flag.withDescription("Agent to use"), Flag.optional),
|
|
||||||
format: Flag.choice("format", ["default", "json"]).pipe(
|
|
||||||
Flag.withDescription("Output format"),
|
|
||||||
Flag.withDefault("default"),
|
|
||||||
),
|
|
||||||
file: Flag.string("file").pipe(
|
|
||||||
Flag.withAlias("f"),
|
|
||||||
Flag.withDescription("File to attach to the message"),
|
|
||||||
Flag.atMost(100),
|
|
||||||
),
|
|
||||||
title: Flag.string("title").pipe(Flag.withDescription("Session title"), Flag.optional),
|
|
||||||
server: Flag.string("server").pipe(
|
|
||||||
Flag.withDescription("Connect to a server URL instead of the background service"),
|
|
||||||
Flag.optional,
|
|
||||||
),
|
|
||||||
dir: Flag.string("dir").pipe(Flag.withDescription("Directory to run in"), Flag.optional),
|
|
||||||
variant: Flag.string("variant").pipe(Flag.withDescription("Model variant"), Flag.optional),
|
|
||||||
thinking: Flag.boolean("thinking").pipe(
|
|
||||||
Flag.withDescription("Show thinking blocks"),
|
|
||||||
Flag.withDefault(false),
|
|
||||||
),
|
|
||||||
auto: Flag.boolean("auto").pipe(
|
|
||||||
Flag.withDescription("Auto-approve permissions that are not explicitly denied"),
|
|
||||||
Flag.withDefault(false),
|
|
||||||
),
|
|
||||||
yolo: Flag.boolean("yolo").pipe(Flag.withDefault(false), Flag.withHidden),
|
|
||||||
dangerouslySkipPermissions: Flag.boolean("dangerously-skip-permissions").pipe(
|
|
||||||
Flag.withDefault(false),
|
|
||||||
Flag.withHidden,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
Spec.make("service", {
|
Spec.make("service", {
|
||||||
description: "Manage the background server",
|
description: "Manage the background server",
|
||||||
commands: [
|
commands: [
|
||||||
@@ -200,27 +34,18 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO
|
|||||||
Spec.make("restart", { description: "Restart the background server" }),
|
Spec.make("restart", { description: "Restart the background server" }),
|
||||||
Spec.make("status", { description: "Show background server status" }),
|
Spec.make("status", { description: "Show background server status" }),
|
||||||
Spec.make("stop", { description: "Stop the background server" }),
|
Spec.make("stop", { description: "Stop the background server" }),
|
||||||
Spec.make("get", {
|
Spec.make("password", {
|
||||||
description: "Get service configuration",
|
description: "Get or set the server password",
|
||||||
params: { key: Argument.string("key").pipe(Argument.optional) },
|
params: { value: Argument.string("value").pipe(Argument.optional) },
|
||||||
}),
|
|
||||||
Spec.make("set", {
|
|
||||||
description: "Set service configuration",
|
|
||||||
params: { key: Argument.string("key"), value: Argument.string("value") },
|
|
||||||
}),
|
|
||||||
Spec.make("unset", {
|
|
||||||
description: "Unset service configuration",
|
|
||||||
params: { key: Argument.string("key") },
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
Spec.make("serve", {
|
Spec.make("serve", {
|
||||||
description: "Start the v2 API server",
|
description: "Start the v2 API server",
|
||||||
params: {
|
params: {
|
||||||
hostname: Flag.string("hostname").pipe(Flag.optional),
|
hostname: Flag.string("hostname").pipe(Flag.withDefault("127.0.0.1")),
|
||||||
port: Flag.integer("port").pipe(Flag.optional),
|
port: Flag.integer("port").pipe(Flag.optional),
|
||||||
service: Flag.boolean("service").pipe(Flag.withDefault(false)),
|
register: Flag.boolean("register").pipe(Flag.withDefault(false)),
|
||||||
stdio: Flag.boolean("stdio").pipe(Flag.withDefault(false)),
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { EOL } from "node:os"
|
|||||||
import { Effect, Option } from "effect"
|
import { Effect, Option } from "effect"
|
||||||
import { Commands } from "../commands"
|
import { Commands } from "../commands"
|
||||||
import { Runtime } from "../../framework/runtime"
|
import { Runtime } from "../../framework/runtime"
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
import { Daemon } from "../../services/daemon"
|
||||||
import { ServiceConfig } from "../../services/service-config"
|
|
||||||
|
|
||||||
const methods = new Set(["delete", "get", "head", "options", "patch", "post", "put"])
|
const methods = new Set(["delete", "get", "head", "options", "patch", "post", "put"])
|
||||||
|
|
||||||
@@ -18,9 +17,8 @@ type OpenApi = {
|
|||||||
export default Runtime.handler(
|
export default Runtime.handler(
|
||||||
Commands.commands.api,
|
Commands.commands.api,
|
||||||
Effect.fn("cli.api")(function* (input) {
|
Effect.fn("cli.api")(function* (input) {
|
||||||
const options = yield* ServiceConfig.options()
|
const daemon = yield* Daemon.Service
|
||||||
const found = yield* Service.discover(options)
|
const transport = yield* daemon.transport()
|
||||||
const transport = found ?? (yield* Service.start(options))
|
|
||||||
const params = Option.getOrElse(input.param, () => ({}))
|
const params = Option.getOrElse(input.param, () => ({}))
|
||||||
const request = yield* resolveRequest(transport, input.request, params)
|
const request = yield* resolveRequest(transport, input.request, params)
|
||||||
const headers = new Headers(transport.headers)
|
const headers = new Headers(transport.headers)
|
||||||
@@ -60,7 +58,7 @@ export function rawRequest(input: readonly string[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveRequest(
|
function resolveRequest(
|
||||||
transport: Service.Transport,
|
transport: { url: string; headers: RequestInit["headers"] },
|
||||||
input: readonly string[],
|
input: readonly string[],
|
||||||
params: Record<string, string>,
|
params: Record<string, string>,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { Effect } from "effect"
|
import * as Effect from "effect/Effect"
|
||||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
|
||||||
import { Commands } from "../../commands"
|
import { Commands } from "../../commands"
|
||||||
import { Runtime } from "../../../framework/runtime"
|
import { Runtime } from "../../../framework/runtime"
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
import { Daemon } from "../../../services/daemon"
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
|
||||||
|
|
||||||
export default Runtime.handler(
|
export default Runtime.handler(
|
||||||
Commands.commands.debug.commands.agents,
|
Commands.commands.debug.commands.agents,
|
||||||
Effect.fn("cli.debug.agents")(function* () {
|
Effect.fn("cli.debug.agents")(function* () {
|
||||||
const options = yield* ServiceConfig.options()
|
const daemon = yield* Daemon.Service
|
||||||
const found = yield* Service.discover(options)
|
const client = yield* daemon.client()
|
||||||
const transport = found ?? (yield* Service.start(options))
|
|
||||||
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
|
|
||||||
const response = yield* Effect.promise(() => client.v2.agent.list({ location: { directory: process.cwd() } }))
|
const response = yield* Effect.promise(() => client.v2.agent.list({ location: { directory: process.cwd() } }))
|
||||||
process.stdout.write(
|
process.stdout.write(
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
|
|||||||
@@ -1,89 +1,13 @@
|
|||||||
import { NodeFileSystem } from "@effect/platform-node"
|
|
||||||
import { Commands } from "../commands"
|
import { Commands } from "../commands"
|
||||||
import { Runtime } from "../../framework/runtime"
|
import { Runtime } from "../../framework/runtime"
|
||||||
import { Effect, Option, Redacted } from "effect"
|
import { Effect } from "effect"
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
import { Daemon } from "../../services/daemon"
|
||||||
import { Env } from "../../env"
|
|
||||||
import { ServiceConfig } from "../../services/service-config"
|
|
||||||
import { Standalone } from "../../services/standalone"
|
|
||||||
import { Updater } from "../../services/updater"
|
|
||||||
|
|
||||||
export default Runtime.handler(Commands, (input) =>
|
export default Runtime.handler(Commands, () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const directory = Option.getOrUndefined(input.directory)
|
const daemon = yield* Daemon.Service
|
||||||
if (directory !== undefined) process.chdir(directory)
|
const transport = yield* daemon.transport()
|
||||||
const updater = yield* Updater.Service
|
|
||||||
yield* updater.check().pipe(Effect.forkScoped)
|
|
||||||
const server = Option.getOrUndefined(input.server)
|
|
||||||
if (server !== undefined && input.standalone)
|
|
||||||
return yield* Effect.fail(new Error("--server and --standalone cannot be combined"))
|
|
||||||
const transport = yield* Effect.gen(function* () {
|
|
||||||
if (server !== undefined) {
|
|
||||||
const password = yield* Env.password
|
|
||||||
const explicit = {
|
|
||||||
url: server,
|
|
||||||
headers: password
|
|
||||||
? { authorization: "Basic " + btoa("opencode:" + Redacted.value(password)) }
|
|
||||||
: undefined,
|
|
||||||
} satisfies Service.Transport
|
|
||||||
// Fail loudly before entering the TUI: an explicit server that is
|
|
||||||
// unreachable or rejects auth should not present as reconnect churn.
|
|
||||||
const response = yield* Effect.tryPromise(() =>
|
|
||||||
fetch(new URL("/api/health", server), { headers: explicit.headers, signal: AbortSignal.timeout(5_000) }),
|
|
||||||
).pipe(Effect.mapError((cause) => new Error(`Could not reach server at ${server}`, { cause })))
|
|
||||||
if (response.status === 401)
|
|
||||||
return yield* Effect.fail(
|
|
||||||
new Error(
|
|
||||||
password
|
|
||||||
? `Server at ${server} rejected the password`
|
|
||||||
: `Server at ${server} requires a password; set OPENCODE_PASSWORD`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (!response.ok)
|
|
||||||
return yield* Effect.fail(new Error(`Server at ${server} responded with status ${response.status}`))
|
|
||||||
return explicit
|
|
||||||
}
|
|
||||||
if (input.standalone) return yield* Standalone.transport()
|
|
||||||
const options = yield* ServiceConfig.options()
|
|
||||||
const found = yield* Service.discover(options)
|
|
||||||
return found ?? (yield* Service.start(options))
|
|
||||||
})
|
|
||||||
const { runTui } = yield* Effect.promise(() => import("../../tui"))
|
const { runTui } = yield* Effect.promise(() => import("../../tui"))
|
||||||
// The TUI re-runs discover whenever its event stream drops. For an explicit
|
yield* runTui(transport)
|
||||||
// --server or a standalone child the transport is fixed, so reconnects
|
|
||||||
// retry the same address; for the managed service discovery re-reads the
|
|
||||||
// registration and may start a replacement.
|
|
||||||
const serviceOptions = server === undefined && !input.standalone ? yield* ServiceConfig.options() : undefined
|
|
||||||
// Only startup enforces the CLI version. A reconnect must accept a server
|
|
||||||
// replaced by another client or the two clients will restart it forever.
|
|
||||||
const reconnectOptions = serviceOptions ? { ...serviceOptions, version: undefined } : undefined
|
|
||||||
const discover = reconnectOptions
|
|
||||||
? () =>
|
|
||||||
Effect.runPromise(
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const found = yield* Service.discover(reconnectOptions)
|
|
||||||
return found ?? (yield* Service.start(reconnectOptions))
|
|
||||||
}).pipe(Effect.provide(NodeFileSystem.layer)),
|
|
||||||
)
|
|
||||||
: () => Promise.resolve(transport)
|
|
||||||
// Restart the managed service in place; start() resolves once the
|
|
||||||
// replacement is healthy and the reconnect loop reattaches on its own.
|
|
||||||
// Only meaningful in service mode: --server is not ours to restart and a
|
|
||||||
// standalone child cannot be respawned.
|
|
||||||
const reload = serviceOptions
|
|
||||||
? () =>
|
|
||||||
Effect.runPromise(
|
|
||||||
Effect.gen(function* () {
|
|
||||||
yield* Service.stop(serviceOptions)
|
|
||||||
yield* Service.start(serviceOptions)
|
|
||||||
}).pipe(Effect.provide(NodeFileSystem.layer)),
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
yield* runTui(
|
|
||||||
transport,
|
|
||||||
{ continue: input.continue, sessionID: Option.getOrUndefined(input.session) },
|
|
||||||
discover,
|
|
||||||
reload,
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
import { EOL } from "node:os"
|
|
||||||
import path from "node:path"
|
|
||||||
import { Effect, Option } from "effect"
|
|
||||||
import { applyEdits, modify } from "jsonc-parser"
|
|
||||||
import { Global } from "@opencode-ai/core/global"
|
|
||||||
import { Commands } from "../../commands"
|
|
||||||
import { Runtime } from "../../../framework/runtime"
|
|
||||||
|
|
||||||
export default Runtime.handler(
|
|
||||||
Commands.commands.mcp.commands.add,
|
|
||||||
Effect.fn("cli.mcp.add")(function* (input) {
|
|
||||||
const url = Option.getOrUndefined(input.url)
|
|
||||||
const headers = Option.getOrUndefined(input.header)
|
|
||||||
const environment = Option.getOrUndefined(input.env)
|
|
||||||
// The CLI framework strands `--` operands on the root command, so read the local server command
|
|
||||||
// straight from argv after `--`. This also lets the command carry its own flags (e.g. `npx -y`).
|
|
||||||
const dash = process.argv.indexOf("--")
|
|
||||||
const command = dash === -1 ? [...input.command] : process.argv.slice(dash + 1)
|
|
||||||
|
|
||||||
const hasCommand = command.length > 0
|
|
||||||
if (url && hasCommand)
|
|
||||||
return yield* Effect.fail(new Error("Provide either --url <url> or a command after --, not both"))
|
|
||||||
if (!url && !hasCommand) return yield* Effect.fail(new Error("Provide either --url <url> or a command after --"))
|
|
||||||
if (url && !URL.canParse(url)) return yield* Effect.fail(new Error(`Invalid URL: ${url}`))
|
|
||||||
if (url && environment) return yield* Effect.fail(new Error("--env is only valid for local MCP servers"))
|
|
||||||
if (hasCommand && headers) return yield* Effect.fail(new Error("--header is only valid for remote MCP servers"))
|
|
||||||
|
|
||||||
const server = url
|
|
||||||
? { type: "remote" as const, url, ...(headers ? { headers } : {}) }
|
|
||||||
: { type: "local" as const, command, ...(environment ? { environment } : {}) }
|
|
||||||
|
|
||||||
const configPath = yield* Effect.promise(() => resolveConfigPath(input.global ? Global.Path.config : process.cwd()))
|
|
||||||
yield* Effect.promise(() => write(configPath, input.name, server))
|
|
||||||
process.stdout.write(`MCP server "${input.name}" added to ${configPath}` + EOL)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
async function resolveConfigPath(directory: string) {
|
|
||||||
const candidates = [
|
|
||||||
path.join(directory, "opencode.json"),
|
|
||||||
path.join(directory, "opencode.jsonc"),
|
|
||||||
path.join(directory, ".opencode", "opencode.json"),
|
|
||||||
path.join(directory, ".opencode", "opencode.jsonc"),
|
|
||||||
]
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
if (await Bun.file(candidate).exists()) return candidate
|
|
||||||
}
|
|
||||||
return candidates[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function write(configPath: string, name: string, server: unknown) {
|
|
||||||
const file = Bun.file(configPath)
|
|
||||||
const text = (await file.exists()) ? await file.text() : "{}"
|
|
||||||
const edits = modify(text, ["mcp", "servers", name], server, {
|
|
||||||
formattingOptions: { tabSize: 2, insertSpaces: true },
|
|
||||||
})
|
|
||||||
await Bun.write(configPath, applyEdits(text, edits))
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { EOL } from "node:os"
|
|
||||||
import { Effect } from "effect"
|
|
||||||
import {
|
|
||||||
createOpencodeClient,
|
|
||||||
type IntegrationAttemptStatus,
|
|
||||||
type IntegrationOAuthMethod,
|
|
||||||
type OpencodeClient,
|
|
||||||
} from "@opencode-ai/sdk/v2/client"
|
|
||||||
import { Commands } from "../../commands"
|
|
||||||
import { Runtime } from "../../../framework/runtime"
|
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
|
||||||
import { resolveIntegration } from "./resolve"
|
|
||||||
|
|
||||||
const location = { directory: process.cwd() }
|
|
||||||
|
|
||||||
export default Runtime.handler(
|
|
||||||
Commands.commands.mcp.commands.auth,
|
|
||||||
Effect.fn("cli.mcp.auth")(function* (input) {
|
|
||||||
const options = yield* ServiceConfig.options()
|
|
||||||
const found = yield* Service.discover(options)
|
|
||||||
const transport = found ?? (yield* Service.start(options))
|
|
||||||
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
|
|
||||||
|
|
||||||
const integration = yield* resolveIntegration(client, input.name, location)
|
|
||||||
if (!integration)
|
|
||||||
return yield* Effect.fail(new Error(`MCP server "${input.name}" is not an OAuth-capable remote server`))
|
|
||||||
const method = integration.methods.find(
|
|
||||||
(candidate): candidate is IntegrationOAuthMethod => candidate.type === "oauth",
|
|
||||||
)
|
|
||||||
if (!method)
|
|
||||||
return yield* Effect.fail(new Error(`MCP server "${input.name}" is not an OAuth-capable remote server`))
|
|
||||||
|
|
||||||
const started = yield* Effect.promise(() =>
|
|
||||||
client.v2.integration.connect.oauth({ integrationID: integration.id, methodID: method.id, inputs: {}, location }),
|
|
||||||
)
|
|
||||||
const attempt = started.data?.data
|
|
||||||
if (!attempt) return yield* Effect.fail(new Error(started.error?.message ?? "Failed to start OAuth attempt"))
|
|
||||||
if (attempt.mode === "code")
|
|
||||||
return yield* Effect.fail(new Error("This server requires manual code entry, which the CLI does not support"))
|
|
||||||
|
|
||||||
process.stdout.write(attempt.instructions + EOL + attempt.url + EOL)
|
|
||||||
|
|
||||||
const result = yield* poll(client, attempt.attemptID)
|
|
||||||
if (result.status === "complete") {
|
|
||||||
process.stdout.write(`Authenticated with ${input.name}` + EOL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const reason = result.status === "failed" ? `: ${result.message}` : ""
|
|
||||||
return yield* Effect.fail(new Error(`Authentication ${result.status}${reason}`))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const poll = (
|
|
||||||
client: OpencodeClient,
|
|
||||||
attemptID: string,
|
|
||||||
): Effect.Effect<Exclude<IntegrationAttemptStatus, { status: "pending" }>> =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const response = yield* Effect.promise(() => client.v2.integration.attempt.status({ attemptID, location }))
|
|
||||||
const status = response.data?.data
|
|
||||||
if (!status || status.status === "pending") {
|
|
||||||
yield* Effect.sleep("1 second")
|
|
||||||
return yield* poll(client, attemptID)
|
|
||||||
}
|
|
||||||
return status
|
|
||||||
})
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { EOL } from "node:os"
|
|
||||||
import { Effect } from "effect"
|
|
||||||
import { createOpencodeClient, type McpServer } from "@opencode-ai/sdk/v2/client"
|
|
||||||
import { Commands } from "../../commands"
|
|
||||||
import { Runtime } from "../../../framework/runtime"
|
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
|
||||||
|
|
||||||
export default Runtime.handler(
|
|
||||||
Commands.commands.mcp.commands.list,
|
|
||||||
Effect.fn("cli.mcp.list")(function* () {
|
|
||||||
const options = yield* ServiceConfig.options()
|
|
||||||
const found = yield* Service.discover(options)
|
|
||||||
const transport = found ?? (yield* Service.start(options))
|
|
||||||
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
|
|
||||||
const response = yield* Effect.promise(() => client.v2.mcp.list({ location: { directory: process.cwd() } }))
|
|
||||||
const servers = (response.data?.data ?? []).toSorted((a, b) => a.name.localeCompare(b.name))
|
|
||||||
if (servers.length === 0) {
|
|
||||||
process.stdout.write("No MCP servers configured" + EOL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const width = Math.max(...servers.map((server) => server.name.length))
|
|
||||||
const lines = servers.map(
|
|
||||||
(server) => `${icon(server.status)} ${server.name.padEnd(width)} ${describe(server.status)}`,
|
|
||||||
)
|
|
||||||
process.stdout.write(lines.join(EOL) + EOL)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
function icon(status: McpServer["status"]) {
|
|
||||||
switch (status.status) {
|
|
||||||
case "connected":
|
|
||||||
return "✓"
|
|
||||||
case "needs_auth":
|
|
||||||
return "⚠"
|
|
||||||
case "failed":
|
|
||||||
case "needs_client_registration":
|
|
||||||
return "✗"
|
|
||||||
default:
|
|
||||||
return "○"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function describe(status: McpServer["status"]) {
|
|
||||||
switch (status.status) {
|
|
||||||
case "needs_auth":
|
|
||||||
return "needs authentication"
|
|
||||||
case "needs_client_registration":
|
|
||||||
return `needs client registration: ${status.error}`
|
|
||||||
case "failed":
|
|
||||||
return `failed: ${status.error}`
|
|
||||||
default:
|
|
||||||
return status.status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { EOL } from "node:os"
|
|
||||||
import { Effect } from "effect"
|
|
||||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
|
||||||
import { Commands } from "../../commands"
|
|
||||||
import { Runtime } from "../../../framework/runtime"
|
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
|
||||||
import { resolveIntegration } from "./resolve"
|
|
||||||
|
|
||||||
const location = { directory: process.cwd() }
|
|
||||||
|
|
||||||
export default Runtime.handler(
|
|
||||||
Commands.commands.mcp.commands.logout,
|
|
||||||
Effect.fn("cli.mcp.logout")(function* (input) {
|
|
||||||
const options = yield* ServiceConfig.options()
|
|
||||||
const found = yield* Service.discover(options)
|
|
||||||
const transport = found ?? (yield* Service.start(options))
|
|
||||||
const client = createOpencodeClient({ baseUrl: transport.url, headers: transport.headers })
|
|
||||||
|
|
||||||
const integration = yield* resolveIntegration(client, input.name, location)
|
|
||||||
if (!integration) {
|
|
||||||
process.stdout.write(`No stored credentials for ${input.name}` + EOL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const credentials = integration.connections.filter((connection) => connection.type === "credential")
|
|
||||||
if (credentials.length === 0) {
|
|
||||||
process.stdout.write(`No stored credentials for ${input.name}` + EOL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
yield* Effect.forEach(
|
|
||||||
credentials,
|
|
||||||
(connection) => Effect.promise(() => client.v2.credential.remove({ credentialID: connection.id, location })),
|
|
||||||
{ discard: true },
|
|
||||||
)
|
|
||||||
process.stdout.write(`Removed OAuth credentials for ${input.name}` + EOL)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Effect } from "effect"
|
|
||||||
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client"
|
|
||||||
|
|
||||||
// Resolve through the MCP-owned integrationID rather than matching integration names: the shared
|
|
||||||
// integration registry also holds provider/plugin integrations, whose names could collide with a server.
|
|
||||||
// Fails when the server is unknown; returns undefined when the server has no integration (e.g. a local
|
|
||||||
// or anonymous server), leaving that case for the caller to interpret.
|
|
||||||
export const resolveIntegration = (client: OpencodeClient, name: string, location: { directory: string }) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const servers = yield* Effect.promise(() => client.v2.mcp.list({ location }))
|
|
||||||
const server = (servers.data?.data ?? []).find((entry) => entry.name === name)
|
|
||||||
if (!server) return yield* Effect.fail(new Error(`MCP server not found: ${name}`))
|
|
||||||
const integrationID = server.integrationID
|
|
||||||
if (!integrationID) return undefined
|
|
||||||
const found = yield* Effect.promise(() => client.v2.integration.get({ integrationID, location }))
|
|
||||||
return found.data?.data
|
|
||||||
})
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Effect } from "effect"
|
import * as Effect from "effect/Effect"
|
||||||
import { Commands } from "../commands"
|
import { Commands } from "../commands"
|
||||||
import { Runtime } from "../../framework/runtime"
|
import { Runtime } from "../../framework/runtime"
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { Effect, Option, Redacted } from "effect"
|
|
||||||
import path from "node:path"
|
|
||||||
import { Commands } from "../commands"
|
|
||||||
import { Env } from "../../env"
|
|
||||||
import { Runtime } from "../../framework/runtime"
|
|
||||||
|
|
||||||
export default Runtime.handler(Commands.commands.mini, (input) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const { runMini } = yield* Effect.promise(() => import("../../mini"))
|
|
||||||
const project = Option.getOrUndefined(input.project)
|
|
||||||
const server = Option.getOrUndefined(input.server)
|
|
||||||
const password = yield* Env.password
|
|
||||||
yield* Effect.promise(() =>
|
|
||||||
runMini({
|
|
||||||
attach: server,
|
|
||||||
password: password ? Redacted.value(password) : undefined,
|
|
||||||
directory:
|
|
||||||
server !== undefined
|
|
||||||
? project
|
|
||||||
: project === undefined
|
|
||||||
? process.cwd()
|
|
||||||
: path.resolve(process.env.PWD ?? process.cwd(), project),
|
|
||||||
continue: input.continue,
|
|
||||||
session: Option.getOrUndefined(input.session),
|
|
||||||
fork: input.fork,
|
|
||||||
model: Option.getOrUndefined(input.model),
|
|
||||||
agent: Option.getOrUndefined(input.agent),
|
|
||||||
prompt: Option.getOrUndefined(input.prompt),
|
|
||||||
replay: input.replay,
|
|
||||||
replayLimit: Option.getOrUndefined(input.replayLimit),
|
|
||||||
demo: input.demo,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Effect, Option, Redacted } from "effect"
|
|
||||||
import { Commands } from "../commands"
|
|
||||||
import { Env } from "../../env"
|
|
||||||
import { Runtime } from "../../framework/runtime"
|
|
||||||
|
|
||||||
export default Runtime.handler(Commands.commands.run, (input) =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const { runNonInteractive } = yield* Effect.promise(() => import("../../mini"))
|
|
||||||
const password = yield* Env.password
|
|
||||||
const separator = process.argv.indexOf("--", 2)
|
|
||||||
yield* Effect.promise(() =>
|
|
||||||
runNonInteractive({
|
|
||||||
message: [...input.message, ...(separator === -1 ? [] : process.argv.slice(separator + 1))],
|
|
||||||
continue: input.continue,
|
|
||||||
session: Option.getOrUndefined(input.session),
|
|
||||||
fork: input.fork,
|
|
||||||
model: Option.getOrUndefined(input.model),
|
|
||||||
agent: Option.getOrUndefined(input.agent),
|
|
||||||
format: input.format,
|
|
||||||
file: [...input.file],
|
|
||||||
title: Option.getOrUndefined(input.title),
|
|
||||||
server: Option.getOrUndefined(input.server),
|
|
||||||
password: password ? Redacted.value(password) : undefined,
|
|
||||||
directory: Option.getOrUndefined(input.dir),
|
|
||||||
variant: Option.getOrUndefined(input.variant),
|
|
||||||
thinking: input.thinking,
|
|
||||||
dangerouslySkipPermissions: input.auto || input.yolo || input.dangerouslySkipPermissions,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
@@ -1,16 +1,46 @@
|
|||||||
import { Effect, Option } from "effect"
|
import { NodeHttpServer } from "@effect/platform-node"
|
||||||
|
import { Credential } from "@opencode-ai/core/credential"
|
||||||
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||||
|
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||||
|
import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
||||||
|
import { Context, Layer, Option } from "effect"
|
||||||
|
import * as Effect from "effect/Effect"
|
||||||
|
import { HttpRouter, HttpServer } from "effect/unstable/http"
|
||||||
|
import { createServer } from "node:http"
|
||||||
|
import { createRoutes } from "@opencode-ai/server/routes"
|
||||||
import { Commands } from "../commands"
|
import { Commands } from "../commands"
|
||||||
import { Runtime } from "../../framework/runtime"
|
import { Runtime } from "../../framework/runtime"
|
||||||
import { ServerProcess } from "../../server-process"
|
import { Daemon } from "../../services/daemon"
|
||||||
|
|
||||||
export default Runtime.handler(
|
export default Runtime.handler(
|
||||||
Commands.commands.serve,
|
Commands.commands.serve,
|
||||||
Effect.fn("cli.serve")(function* (input) {
|
Effect.fn("cli.serve")(function* (input) {
|
||||||
if (input.service && input.stdio) return yield* Effect.fail(new Error("--service and --stdio cannot be combined"))
|
return yield* Effect.scoped(
|
||||||
return yield* ServerProcess.run({
|
Effect.gen(function* () {
|
||||||
mode: input.service ? "service" : input.stdio ? "stdio" : "default",
|
const daemon = yield* Daemon.Service
|
||||||
hostname: Option.getOrUndefined(input.hostname),
|
const address = yield* listen(input.hostname, input.port, yield* daemon.password())
|
||||||
port: Option.getOrUndefined(input.port),
|
if (input.register) yield* daemon.register(address)
|
||||||
})
|
console.log(`server listening on ${HttpServer.formatAddress(address)}`)
|
||||||
|
return yield* Effect.never
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
function listen(hostname: string, port: Option.Option<number>, password: string) {
|
||||||
|
if (Option.isSome(port)) return bind(hostname, port.value, password)
|
||||||
|
const next = (port: number): ReturnType<typeof bind> =>
|
||||||
|
bind(hostname, port, password).pipe(
|
||||||
|
Effect.catch((error) => (port === 65_535 ? Effect.fail(error) : next(port + 1))),
|
||||||
|
)
|
||||||
|
return next(4096)
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind(hostname: string, port: number, password: string) {
|
||||||
|
return Layer.build(
|
||||||
|
HttpRouter.serve(createRoutes(password), { disableListenLog: true, disableLogger: true }).pipe(
|
||||||
|
Layer.provideMerge(NodeHttpServer.layer(() => createServer(), { port, host: hostname })),
|
||||||
|
Layer.provide(AppNodeBuilder.build(LayerNode.group([Credential.node, PermissionSaved.node]))),
|
||||||
|
),
|
||||||
|
).pipe(Effect.map((context) => Context.get(context, HttpServer.HttpServer).address))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { EOL } from "os"
|
|
||||||
import { Effect, Option } from "effect"
|
|
||||||
import { Commands } from "../../commands"
|
|
||||||
import { Runtime } from "../../../framework/runtime"
|
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
|
||||||
|
|
||||||
export default Runtime.handler(
|
|
||||||
Commands.commands.service.commands.get,
|
|
||||||
Effect.fn("cli.service.get")(function* (input) {
|
|
||||||
process.stdout.write((yield* ServiceConfig.get(Option.getOrUndefined(input.key))) + EOL)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { EOL } from "os"
|
||||||
|
import { Option } from "effect"
|
||||||
|
import * as Effect from "effect/Effect"
|
||||||
|
import { Commands } from "../../commands"
|
||||||
|
import { Runtime } from "../../../framework/runtime"
|
||||||
|
import { Daemon } from "../../../services/daemon"
|
||||||
|
|
||||||
|
export default Runtime.handler(
|
||||||
|
Commands.commands.service.commands.password,
|
||||||
|
Effect.fn("cli.service.password")(function* (input) {
|
||||||
|
const daemon = yield* Daemon.Service
|
||||||
|
const value = Option.getOrUndefined(input.value)
|
||||||
|
if (value !== undefined) yield* daemon.stop()
|
||||||
|
process.stdout.write((yield* daemon.password(value)) + EOL)
|
||||||
|
}),
|
||||||
|
)
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { Effect } from "effect"
|
import * as Effect from "effect/Effect"
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
|
||||||
import { Commands } from "../../commands"
|
import { Commands } from "../../commands"
|
||||||
import { Runtime } from "../../../framework/runtime"
|
import { Runtime } from "../../../framework/runtime"
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
import { Daemon } from "../../../services/daemon"
|
||||||
|
|
||||||
export default Runtime.handler(
|
export default Runtime.handler(
|
||||||
Commands.commands.service.commands.restart,
|
Commands.commands.service.commands.restart,
|
||||||
Effect.fn("cli.service.restart")(function* () {
|
Effect.fn("cli.service.restart")(function* () {
|
||||||
const options = yield* ServiceConfig.options()
|
const daemon = yield* Daemon.Service
|
||||||
yield* Service.stop(options)
|
yield* daemon.stop()
|
||||||
const transport = yield* Service.start(options)
|
process.stdout.write((yield* daemon.start()) + EOL)
|
||||||
process.stdout.write(transport.url + EOL)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { Effect } from "effect"
|
|
||||||
import { Commands } from "../../commands"
|
|
||||||
import { Runtime } from "../../../framework/runtime"
|
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
|
||||||
|
|
||||||
export default Runtime.handler(
|
|
||||||
Commands.commands.service.commands.set,
|
|
||||||
Effect.fn("cli.service.set")(function* (input) {
|
|
||||||
yield* ServiceConfig.set(input.key, input.value)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { Effect } from "effect"
|
import * as Effect from "effect/Effect"
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
|
||||||
import { Commands } from "../../commands"
|
import { Commands } from "../../commands"
|
||||||
import { Runtime } from "../../../framework/runtime"
|
import { Runtime } from "../../../framework/runtime"
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
import { Daemon } from "../../../services/daemon"
|
||||||
|
|
||||||
export default Runtime.handler(
|
export default Runtime.handler(
|
||||||
Commands.commands.service.commands.start,
|
Commands.commands.service.commands.start,
|
||||||
Effect.fn("cli.service.start")(function* () {
|
Effect.fn("cli.service.start")(function* () {
|
||||||
const transport = yield* Service.start(yield* ServiceConfig.options())
|
process.stdout.write((yield* (yield* Daemon.Service).start()) + EOL)
|
||||||
process.stdout.write(transport.url + EOL)
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { EOL } from "os"
|
import { EOL } from "os"
|
||||||
import { Effect } from "effect"
|
import * as Effect from "effect/Effect"
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
|
||||||
import { Commands } from "../../commands"
|
import { Commands } from "../../commands"
|
||||||
import { Runtime } from "../../../framework/runtime"
|
import { Runtime } from "../../../framework/runtime"
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
import { Daemon } from "../../../services/daemon"
|
||||||
|
|
||||||
export default Runtime.handler(
|
export default Runtime.handler(
|
||||||
Commands.commands.service.commands.status,
|
Commands.commands.service.commands.status,
|
||||||
Effect.fn("cli.service.status")(function* () {
|
Effect.fn("cli.service.status")(function* () {
|
||||||
const found = yield* Service.discover(yield* ServiceConfig.options())
|
const url = yield* (yield* Daemon.Service).status()
|
||||||
process.stdout.write((found ? found.url : "stopped") + EOL)
|
process.stdout.write((url ? `running ${url}` : "stopped") + EOL)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Effect } from "effect"
|
import * as Effect from "effect/Effect"
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
|
||||||
import { Commands } from "../../commands"
|
import { Commands } from "../../commands"
|
||||||
import { Runtime } from "../../../framework/runtime"
|
import { Runtime } from "../../../framework/runtime"
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
import { Daemon } from "../../../services/daemon"
|
||||||
|
|
||||||
export default Runtime.handler(
|
export default Runtime.handler(
|
||||||
Commands.commands.service.commands.stop,
|
Commands.commands.service.commands.stop,
|
||||||
Effect.fn("cli.service.stop")(function* () {
|
Effect.fn("cli.service.stop")(function* () {
|
||||||
yield* Service.stop(yield* ServiceConfig.options())
|
yield* (yield* Daemon.Service).stop()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { Effect } from "effect"
|
|
||||||
import { Commands } from "../../commands"
|
|
||||||
import { Runtime } from "../../../framework/runtime"
|
|
||||||
import { ServiceConfig } from "../../../services/service-config"
|
|
||||||
|
|
||||||
export default Runtime.handler(
|
|
||||||
Commands.commands.service.commands.unset,
|
|
||||||
Effect.fn("cli.service.unset")(function* (input) {
|
|
||||||
yield* ServiceConfig.unset(input.key)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import { Service } from "@opencode-ai/client/effect"
|
|
||||||
import { ClientError, isUnauthorizedError, OpenCode } from "@opencode-ai/client/promise"
|
|
||||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
|
||||||
import { ServerAuth } from "@opencode-ai/server/auth"
|
|
||||||
import { Effect } from "effect"
|
|
||||||
import { ServiceConfig } from "./services/service-config"
|
|
||||||
|
|
||||||
export type SharedOptions = {
|
|
||||||
readonly mode: "shared"
|
|
||||||
readonly command?: ReadonlyArray<string>
|
|
||||||
}
|
|
||||||
export type AttachOptions = {
|
|
||||||
readonly mode: "attach"
|
|
||||||
readonly url: string
|
|
||||||
readonly username?: string
|
|
||||||
readonly password?: string
|
|
||||||
}
|
|
||||||
export type Options = SharedOptions | AttachOptions
|
|
||||||
|
|
||||||
const attach = Effect.fn("cli.daemon.attach")(function* (options: AttachOptions) {
|
|
||||||
const transport = {
|
|
||||||
url: options.url,
|
|
||||||
headers:
|
|
||||||
options.password === undefined
|
|
||||||
? undefined
|
|
||||||
: ServerAuth.headers({ password: options.password, username: options.username }),
|
|
||||||
} satisfies Service.Transport
|
|
||||||
const client = OpenCode.make({ baseUrl: transport.url, headers: transport.headers })
|
|
||||||
const health = yield* Effect.tryPromise({
|
|
||||||
try: () => client.health.get({ signal: AbortSignal.timeout(5_000) }),
|
|
||||||
catch: (cause) => attachError(options, cause),
|
|
||||||
})
|
|
||||||
if (health.version !== InstallationVersion)
|
|
||||||
process.stderr.write(
|
|
||||||
`Warning: Server at ${options.url} has version ${health.version}; this client is ${InstallationVersion}. Continuing anyway.\n`,
|
|
||||||
)
|
|
||||||
return transport
|
|
||||||
})
|
|
||||||
|
|
||||||
const shared = Effect.fn("cli.daemon.shared")(function* (options: SharedOptions) {
|
|
||||||
const config = yield* ServiceConfig.options()
|
|
||||||
const service = options.command === undefined ? config : { ...config, command: options.command }
|
|
||||||
const found = yield* Service.discover(service)
|
|
||||||
if (found) return found
|
|
||||||
return yield* Service.start(service)
|
|
||||||
})
|
|
||||||
|
|
||||||
export function transport(options: AttachOptions): ReturnType<typeof attach>
|
|
||||||
export function transport(options: SharedOptions): ReturnType<typeof shared>
|
|
||||||
export function transport(options: Options): ReturnType<typeof attach> | ReturnType<typeof shared>
|
|
||||||
export function transport(options: Options) {
|
|
||||||
if (options.mode === "attach") return attach(options)
|
|
||||||
return shared(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
function attachError(options: AttachOptions, cause: unknown) {
|
|
||||||
if (isUnauthorizedError(cause)) {
|
|
||||||
return new Error(
|
|
||||||
options.password === undefined
|
|
||||||
? `Server at ${options.url} requires authentication; provide a password`
|
|
||||||
: `Server at ${options.url} rejected the supplied credentials`,
|
|
||||||
{ cause },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (cause instanceof ClientError && cause.reason === "Transport")
|
|
||||||
return new Error(`Could not reach server at ${options.url}`, { cause })
|
|
||||||
return new Error(`Server at ${options.url} did not provide a compatible V2 health response`, { cause })
|
|
||||||
}
|
|
||||||
|
|
||||||
export * as Daemon from "./daemon"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Config } from "effect"
|
|
||||||
|
|
||||||
// Every environment variable the CLI reads, in one place. Consumers yield
|
|
||||||
// these instead of touching process.env so the full surface stays visible,
|
|
||||||
// typed, and redacted where secret.
|
|
||||||
|
|
||||||
// The opencode server password: sent by clients connecting to an explicit
|
|
||||||
// --server, and adopted by a manually run or standalone server. The legacy
|
|
||||||
// name is still honored.
|
|
||||||
export const password = Config.redacted("OPENCODE_PASSWORD").pipe(
|
|
||||||
Config.orElse(() => Config.redacted("OPENCODE_SERVER_PASSWORD")),
|
|
||||||
Config.withDefault(undefined),
|
|
||||||
)
|
|
||||||
|
|
||||||
export * as Env from "./env"
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Effect, FileSystem, Scope } from "effect"
|
import * as Effect from "effect/Effect"
|
||||||
import { Command } from "effect/unstable/cli"
|
import * as Command from "effect/unstable/cli/Command"
|
||||||
import { Spec } from "./spec"
|
import { Spec } from "./spec"
|
||||||
import { Global } from "@opencode-ai/core/global"
|
import { Daemon } from "../services/daemon"
|
||||||
import { Updater } from "../services/updater"
|
|
||||||
|
|
||||||
export type Input<Value> =
|
export type Input<Value> =
|
||||||
Value extends Spec.Node<infer _Name, infer Command, infer _Commands>
|
Value extends Spec.Node<infer _Name, infer Command, infer _Commands>
|
||||||
@@ -11,21 +10,11 @@ export type Input<Value> =
|
|||||||
? Input
|
? Input
|
||||||
: never
|
: never
|
||||||
|
|
||||||
type RuntimeHandler = (
|
type RuntimeHandler = (input: unknown) => Effect.Effect<void, unknown, Daemon.Service>
|
||||||
input: unknown,
|
|
||||||
) => Effect.Effect<void, unknown, FileSystem.FileSystem | Global.Service | Updater.Service | Scope.Scope>
|
|
||||||
type Loader<Node extends Spec.Any> = () => Promise<{
|
type Loader<Node extends Spec.Any> = () => Promise<{
|
||||||
default: (
|
default: (input: Input<Node>) => Effect.Effect<void, any, Daemon.Service>
|
||||||
input: Input<Node>,
|
|
||||||
) => Effect.Effect<void, any, FileSystem.FileSystem | Global.Service | Updater.Service | Scope.Scope>
|
|
||||||
}>
|
}>
|
||||||
type ProvidedCommand = Command.Command<
|
type ProvidedCommand = Command.Command<string, unknown, unknown, unknown, Daemon.Service>
|
||||||
string,
|
|
||||||
unknown,
|
|
||||||
unknown,
|
|
||||||
unknown,
|
|
||||||
FileSystem.FileSystem | Global.Service | Updater.Service | Scope.Scope
|
|
||||||
>
|
|
||||||
|
|
||||||
export type Handlers<Node extends Spec.Any> = keyof Node["commands"] extends never
|
export type Handlers<Node extends Spec.Any> = keyof Node["commands"] extends never
|
||||||
? Loader<Node>
|
? Loader<Node>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Command } from "effect/unstable/cli"
|
import * as Command from "effect/unstable/cli/Command"
|
||||||
|
|
||||||
type Options<Config extends Command.Command.Config, Commands extends ReadonlyArray<Any>> = {
|
type Options<Config extends Command.Command.Config, Commands extends ReadonlyArray<Any>> = {
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
|
|||||||
@@ -1,22 +1,11 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { NodeFileSystem, NodeRuntime, NodeServices } from "@effect/platform-node"
|
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
|
||||||
import { Effect, Layer, Logger, References } from "effect"
|
import * as NodeServices from "@effect/platform-node/NodeServices"
|
||||||
|
import * as Effect from "effect/Effect"
|
||||||
import { Commands } from "./commands/commands"
|
import { Commands } from "./commands/commands"
|
||||||
import { Runtime } from "./framework/runtime"
|
import { Runtime } from "./framework/runtime"
|
||||||
import { Logging } from "@opencode-ai/core/observability/logging"
|
import { Daemon } from "./services/daemon"
|
||||||
import { Updater } from "./services/updater"
|
|
||||||
import { InstallationChannel, InstallationVersion, InstallationLocal } from "@opencode-ai/core/installation/version"
|
|
||||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
||||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|
||||||
import { Global } from "@opencode-ai/core/global"
|
|
||||||
import { AppProcess } from "@opencode-ai/core/process"
|
|
||||||
|
|
||||||
const LoggingLayer = Logger.layer(Logging.loggers(), { mergeWithExisting: false }).pipe(
|
|
||||||
Layer.provide(NodeFileSystem.layer),
|
|
||||||
Layer.orDie,
|
|
||||||
Layer.merge(Layer.succeed(References.MinimumLogLevel, Logging.minimumLogLevel())),
|
|
||||||
)
|
|
||||||
|
|
||||||
const Handlers = Runtime.handlers(Commands, {
|
const Handlers = Runtime.handlers(Commands, {
|
||||||
$: () => import("./commands/handlers/default"),
|
$: () => import("./commands/handlers/default"),
|
||||||
@@ -24,40 +13,20 @@ const Handlers = Runtime.handlers(Commands, {
|
|||||||
debug: {
|
debug: {
|
||||||
agents: () => import("./commands/handlers/debug/agents"),
|
agents: () => import("./commands/handlers/debug/agents"),
|
||||||
},
|
},
|
||||||
mcp: {
|
|
||||||
list: () => import("./commands/handlers/mcp/list"),
|
|
||||||
add: () => import("./commands/handlers/mcp/add"),
|
|
||||||
auth: () => import("./commands/handlers/mcp/auth"),
|
|
||||||
logout: () => import("./commands/handlers/mcp/logout"),
|
|
||||||
},
|
|
||||||
migrate: () => import("./commands/handlers/migrate"),
|
migrate: () => import("./commands/handlers/migrate"),
|
||||||
mini: () => import("./commands/handlers/mini"),
|
|
||||||
run: () => import("./commands/handlers/run"),
|
|
||||||
service: {
|
service: {
|
||||||
start: () => import("./commands/handlers/service/start"),
|
start: () => import("./commands/handlers/service/start"),
|
||||||
restart: () => import("./commands/handlers/service/restart"),
|
restart: () => import("./commands/handlers/service/restart"),
|
||||||
status: () => import("./commands/handlers/service/status"),
|
status: () => import("./commands/handlers/service/status"),
|
||||||
stop: () => import("./commands/handlers/service/stop"),
|
stop: () => import("./commands/handlers/service/stop"),
|
||||||
get: () => import("./commands/handlers/service/get"),
|
password: () => import("./commands/handlers/service/password"),
|
||||||
set: () => import("./commands/handlers/service/set"),
|
|
||||||
unset: () => import("./commands/handlers/service/unset"),
|
|
||||||
},
|
},
|
||||||
serve: () => import("./commands/handlers/serve"),
|
serve: () => import("./commands/handlers/serve"),
|
||||||
})
|
})
|
||||||
|
|
||||||
Effect.logInfo("cli starting", {
|
Runtime.run(Commands, Handlers, { version: "local" }).pipe(
|
||||||
version: InstallationVersion,
|
Effect.provide(Daemon.layer),
|
||||||
channel: InstallationChannel,
|
|
||||||
local: InstallationLocal,
|
|
||||||
args: process.argv.slice(2),
|
|
||||||
}).pipe(
|
|
||||||
Effect.flatMap(() => Runtime.run(Commands, Handlers, { version: InstallationVersion })),
|
|
||||||
Effect.annotateLogs({ role: "cli" }),
|
|
||||||
Effect.provide(Updater.layer),
|
|
||||||
Effect.provide(AppNodeBuilder.build(LayerNode.group([Global.node, AppProcess.node]))),
|
|
||||||
Effect.provide(LoggingLayer),
|
|
||||||
Effect.provide(NodeServices.layer),
|
Effect.provide(NodeServices.layer),
|
||||||
Effect.scoped,
|
Effect.scoped,
|
||||||
Effect.tap(() => Effect.sync(() => process.exit(process.exitCode ?? 0))),
|
|
||||||
NodeRuntime.runMain,
|
NodeRuntime.runMain,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
import type {
|
|
||||||
AgentListOutput,
|
|
||||||
CommandListOutput,
|
|
||||||
ModelListOutput,
|
|
||||||
OpenCodeClient,
|
|
||||||
ProviderListOutput,
|
|
||||||
SkillListOutput,
|
|
||||||
} from "@opencode-ai/client/promise"
|
|
||||||
import type { RunAgent, RunCommand, RunProvider, RunReference } from "./types"
|
|
||||||
|
|
||||||
type CurrentAgent = AgentListOutput["data"][number]
|
|
||||||
type CurrentCommand = CommandListOutput["data"][number]
|
|
||||||
type CurrentSkill = SkillListOutput["data"][number]
|
|
||||||
type CurrentProvider = ProviderListOutput["data"][number]
|
|
||||||
type CurrentModel = ModelListOutput["data"][number]
|
|
||||||
|
|
||||||
function location(directory: string, workspace?: string) {
|
|
||||||
return {
|
|
||||||
location: {
|
|
||||||
directory,
|
|
||||||
workspace,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultCost(model: CurrentModel) {
|
|
||||||
const picked = model.cost.find((cost) => cost.tier === undefined) ?? model.cost[0]
|
|
||||||
if (!picked) {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...picked,
|
|
||||||
input: model.cost.every((cost) => cost.input === 0) ? 0 : picked.input,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runAgent(input: CurrentAgent): RunAgent {
|
|
||||||
return {
|
|
||||||
name: input.id,
|
|
||||||
description: input.description,
|
|
||||||
mode: input.mode,
|
|
||||||
hidden: input.hidden,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runCommand(input: CurrentCommand): RunCommand {
|
|
||||||
return {
|
|
||||||
name: input.name,
|
|
||||||
description: input.description,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runSkill(input: CurrentSkill): RunCommand {
|
|
||||||
return {
|
|
||||||
name: input.name,
|
|
||||||
description: input.description,
|
|
||||||
source: "skill",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runProviders(providers: CurrentProvider[], models: CurrentModel[]): RunProvider[] {
|
|
||||||
const grouped = new Map<string, RunProvider>()
|
|
||||||
|
|
||||||
for (const provider of providers) {
|
|
||||||
grouped.set(provider.id, {
|
|
||||||
id: provider.id,
|
|
||||||
name: provider.name,
|
|
||||||
models: {},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const model of models) {
|
|
||||||
const provider = grouped.get(model.providerID) ?? {
|
|
||||||
id: model.providerID,
|
|
||||||
name: model.providerID,
|
|
||||||
models: {},
|
|
||||||
}
|
|
||||||
provider.models[model.id] = {
|
|
||||||
id: model.id,
|
|
||||||
providerID: model.providerID,
|
|
||||||
name: model.name,
|
|
||||||
capabilities: model.capabilities,
|
|
||||||
cost: defaultCost(model),
|
|
||||||
limit: model.limit,
|
|
||||||
status: model.status,
|
|
||||||
variants: Object.fromEntries((model.variants ?? []).map((variant) => [variant.id, {}])),
|
|
||||||
}
|
|
||||||
grouped.set(provider.id, provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...grouped.values()]
|
|
||||||
}
|
|
||||||
|
|
||||||
// A location boots its plugins in a deferred background batch after the layer
|
|
||||||
// is built, so first-turn model resolution can observe empty catalog state.
|
|
||||||
// For explicit --model flows, wait for that exact ref to appear before prompt
|
|
||||||
// admission. On timeout, return and let the real execution error surface.
|
|
||||||
export async function waitForCatalogReady(input: {
|
|
||||||
sdk: OpenCodeClient
|
|
||||||
directory: string
|
|
||||||
workspace?: string
|
|
||||||
model: { providerID: string; modelID: string }
|
|
||||||
timeoutMs?: number
|
|
||||||
}) {
|
|
||||||
const deadline = Date.now() + (input.timeoutMs ?? 5_000)
|
|
||||||
while (Date.now() < deadline) {
|
|
||||||
const models = await input.sdk.model
|
|
||||||
.list(location(input.directory, input.workspace))
|
|
||||||
.then((result) => result.data)
|
|
||||||
.catch(() => undefined)
|
|
||||||
if (models?.some((model) => model.providerID === input.model.providerID && model.id === input.model.modelID)) return
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function waitForDefaultModel(input: {
|
|
||||||
sdk: OpenCodeClient
|
|
||||||
directory: string
|
|
||||||
timeoutMs?: number
|
|
||||||
active?: () => boolean
|
|
||||||
}): Promise<{ providerID: string; modelID: string } | undefined> {
|
|
||||||
const deadline = Date.now() + (input.timeoutMs ?? 5_000)
|
|
||||||
while (Date.now() < deadline && (input.active?.() ?? true)) {
|
|
||||||
const model = await input.sdk.model
|
|
||||||
.default(location(input.directory))
|
|
||||||
.then((result) => result.data)
|
|
||||||
.catch(() => undefined)
|
|
||||||
if (model) return { providerID: model.providerID, modelID: model.id }
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadRunAgents(sdk: OpenCodeClient, directory: string): Promise<RunAgent[]> {
|
|
||||||
const result = await sdk.agent.list(location(directory))
|
|
||||||
return result.data.map(runAgent)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadRunCommands(sdk: OpenCodeClient, directory: string): Promise<RunCommand[]> {
|
|
||||||
const [commands, skills] = await Promise.all([
|
|
||||||
sdk.command.list(location(directory)),
|
|
||||||
sdk.skill.list(location(directory)),
|
|
||||||
])
|
|
||||||
return [...commands.data.map(runCommand), ...skills.data.filter((skill) => skill.slash !== false).map(runSkill)]
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadRunReferences(sdk: OpenCodeClient, directory: string): Promise<RunReference[]> {
|
|
||||||
const result = await sdk.reference.list(location(directory))
|
|
||||||
return result.data.filter((reference) => !reference.hidden)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadRunProviders(sdk: OpenCodeClient, directory: string): Promise<RunProvider[]> {
|
|
||||||
const [providers, models] = await Promise.all([
|
|
||||||
sdk.provider.list(location(directory)),
|
|
||||||
sdk.model.list(location(directory)),
|
|
||||||
])
|
|
||||||
return runProviders([...providers.data], [...models.data])
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export { runMini, mergeInput as mergeInteractiveInput, type MiniCommandInput } from "./mini"
|
|
||||||
export {
|
|
||||||
runNonInteractive,
|
|
||||||
mergeInput as mergeNonInteractiveInput,
|
|
||||||
pickRunModel,
|
|
||||||
type RunCommandInput,
|
|
||||||
} from "./run"
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
import { NodeFileSystem } from "@effect/platform-node"
|
|
||||||
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
|
|
||||||
import { truthy } from "@opencode-ai/core/flag/flag"
|
|
||||||
import { Global } from "@opencode-ai/core/global"
|
|
||||||
import { Effect } from "effect"
|
|
||||||
import path from "node:path"
|
|
||||||
import { Daemon } from "../daemon"
|
|
||||||
import { waitForCatalogReady } from "./catalog.shared"
|
|
||||||
import { INTERACTIVE_INPUT_ERROR, resolveInteractiveStdin } from "./runtime.stdin"
|
|
||||||
import type { RunInput, RunTuiConfig } from "./types"
|
|
||||||
|
|
||||||
export type MiniCommandInput = {
|
|
||||||
directory?: string
|
|
||||||
attach?: string
|
|
||||||
password?: string
|
|
||||||
username?: string
|
|
||||||
continue?: boolean
|
|
||||||
session?: string
|
|
||||||
fork?: boolean
|
|
||||||
model?: string
|
|
||||||
agent?: string
|
|
||||||
prompt?: string
|
|
||||||
replay?: boolean
|
|
||||||
replayLimit?: number
|
|
||||||
demo?: boolean
|
|
||||||
serverCommand?: ReadonlyArray<string>
|
|
||||||
tuiConfig?: RunTuiConfig | Promise<RunTuiConfig>
|
|
||||||
}
|
|
||||||
|
|
||||||
type Session = Awaited<ReturnType<OpenCodeClient["session"]["get"]>>
|
|
||||||
type Transport = { readonly url: string; readonly headers?: HeadersInit }
|
|
||||||
|
|
||||||
export async function runMini(input: MiniCommandInput) {
|
|
||||||
validate(input)
|
|
||||||
const initialInput = mergeInput(process.stdin.isTTY ? undefined : await Bun.stdin.text(), input.prompt)
|
|
||||||
const runtimeTask = import("./runtime")
|
|
||||||
const directory = input.attach ? input.directory : localDirectory(input.directory)
|
|
||||||
const transportTask = startTransport(input)
|
|
||||||
void transportTask.catch(() => {})
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (input.attach) await transportTask
|
|
||||||
const sdk = OpenCode.make({
|
|
||||||
baseUrl: "http://opencode.pending",
|
|
||||||
fetch: deferredFetch(transportTask),
|
|
||||||
})
|
|
||||||
const attachedSession =
|
|
||||||
input.attach && input.session && !input.directory
|
|
||||||
? await sdk.session.get({ sessionID: input.session }).catch(() => fail("Session not found"))
|
|
||||||
: undefined
|
|
||||||
const resolvedDirectory =
|
|
||||||
directory ?? attachedSession?.location.directory ?? (await remoteDirectory(await transportTask, sdk))
|
|
||||||
const model = parseModel(input.model)
|
|
||||||
let agentTask: Promise<string | undefined> | undefined
|
|
||||||
const resolveAgent = () => {
|
|
||||||
agentTask ??= validateAgent(sdk, resolvedDirectory, input.agent, input.attach)
|
|
||||||
return agentTask
|
|
||||||
}
|
|
||||||
const resolveSession = async () => {
|
|
||||||
const [agent, selected] = await Promise.all([
|
|
||||||
resolveAgent(),
|
|
||||||
selectSession(sdk, resolvedDirectory, input, attachedSession),
|
|
||||||
])
|
|
||||||
const readyModel =
|
|
||||||
model ?? (selected?.model ? { providerID: selected.model.providerID, modelID: selected.model.id } : undefined)
|
|
||||||
if (readyModel) await waitForCatalogReady({ sdk, directory: resolvedDirectory, model: readyModel })
|
|
||||||
const session = selected ?? (await createSession(sdk, resolvedDirectory, agent, model))
|
|
||||||
return { id: session.id, title: session.title, resume: selected !== undefined }
|
|
||||||
}
|
|
||||||
const create = (
|
|
||||||
_sdk: OpenCodeClient,
|
|
||||||
next: { agent: string | undefined; model: RunInput["model"]; variant: string | undefined },
|
|
||||||
) => createSession(sdk, resolvedDirectory, next.agent, next.model, next.variant)
|
|
||||||
const runtime = await runtimeTask
|
|
||||||
await runtime.runInteractiveDeferredMode({
|
|
||||||
sdk,
|
|
||||||
directory: resolvedDirectory,
|
|
||||||
resolveAgent,
|
|
||||||
session: resolveSession,
|
|
||||||
createSession: create,
|
|
||||||
agent: input.agent,
|
|
||||||
model,
|
|
||||||
variant: undefined,
|
|
||||||
files: [],
|
|
||||||
initialInput,
|
|
||||||
thinking: true,
|
|
||||||
backgroundSubagents:
|
|
||||||
truthy("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS") ||
|
|
||||||
(process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS === undefined && truthy("OPENCODE_EXPERIMENTAL")),
|
|
||||||
replay: input.replay ?? true,
|
|
||||||
replayLimit: input.replayLimit,
|
|
||||||
demo: input.demo,
|
|
||||||
tuiConfig: input.tuiConfig,
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error && error.message === INTERACTIVE_INPUT_ERROR) fail(error.message)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @internal Exported for testing. */
|
|
||||||
export function mergeInput(piped: string | undefined, prompt: string | undefined) {
|
|
||||||
if (!prompt) return piped || undefined
|
|
||||||
if (!piped) return prompt
|
|
||||||
return piped + "\n" + prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate(input: MiniCommandInput) {
|
|
||||||
if (!process.stdout.isTTY) fail("opencode mini requires a TTY stdout")
|
|
||||||
if (input.replayLimit !== undefined && (!Number.isInteger(input.replayLimit) || input.replayLimit <= 0)) {
|
|
||||||
fail("--replay-limit must be a positive integer")
|
|
||||||
}
|
|
||||||
if (input.fork && !input.continue && !input.session) fail("--fork requires --continue or --session")
|
|
||||||
resolveInteractiveStdin().cleanup?.()
|
|
||||||
}
|
|
||||||
|
|
||||||
function localDirectory(directory?: string): string {
|
|
||||||
const root = process.env.PWD ?? process.cwd()
|
|
||||||
try {
|
|
||||||
process.chdir(directory ? (path.isAbsolute(directory) ? directory : path.join(root, directory)) : root)
|
|
||||||
return process.cwd()
|
|
||||||
} catch {
|
|
||||||
fail(`Failed to change directory to ${directory}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startTransport(input: MiniCommandInput): Promise<Transport> {
|
|
||||||
if (input.attach) {
|
|
||||||
return Effect.runPromise(
|
|
||||||
Daemon.transport({
|
|
||||||
mode: "attach",
|
|
||||||
url: input.attach,
|
|
||||||
password: input.password ?? process.env.OPENCODE_SERVER_PASSWORD,
|
|
||||||
username: input.username ?? process.env.OPENCODE_SERVER_USERNAME,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return Effect.runPromise(
|
|
||||||
Daemon.transport({ mode: "shared", command: input.serverCommand }).pipe(
|
|
||||||
Effect.provide(NodeFileSystem.layer),
|
|
||||||
Effect.provide(Global.layerWith({})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function deferredFetch(transportTask: Promise<{ url: string; headers?: HeadersInit }>): typeof globalThis.fetch {
|
|
||||||
const fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
||||||
const transport = await transportTask
|
|
||||||
const request = new Request(input, init)
|
|
||||||
const source = new URL(request.url)
|
|
||||||
const headers = new Headers(request.headers)
|
|
||||||
for (const [key, value] of new Headers(transport.headers)) headers.set(key, value)
|
|
||||||
return globalThis.fetch(new Request(new URL(source.pathname + source.search, transport.url), request), { headers })
|
|
||||||
}
|
|
||||||
return fetch as typeof globalThis.fetch
|
|
||||||
}
|
|
||||||
|
|
||||||
async function remoteDirectory(
|
|
||||||
transport: { url: string; headers?: HeadersInit },
|
|
||||||
sdk: OpenCodeClient,
|
|
||||||
): Promise<string> {
|
|
||||||
const location = await sdk.location.get()
|
|
||||||
if (!location.directory) throw new Error(`Failed to resolve remote directory from ${transport.url}`)
|
|
||||||
return location.directory
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseModel(value?: string): RunInput["model"] {
|
|
||||||
if (!value) return
|
|
||||||
const [providerID, ...rest] = value.split("/")
|
|
||||||
const modelID = rest.join("/")
|
|
||||||
if (!providerID || !modelID) fail("--model must use the format provider/model")
|
|
||||||
return { providerID, modelID }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateAgent(sdk: OpenCodeClient, directory: string, name?: string, attach?: string) {
|
|
||||||
if (!name) return
|
|
||||||
const deadline = Date.now() + 5_000
|
|
||||||
let agents: Awaited<ReturnType<OpenCodeClient["agent"]["list"]>> | undefined
|
|
||||||
while (Date.now() < deadline) {
|
|
||||||
agents = await sdk.agent.list({ location: { directory } }).catch(() => undefined)
|
|
||||||
const agent = agents?.data.find((item) => item.id === name)
|
|
||||||
if (agent?.mode === "subagent") {
|
|
||||||
warning(`agent "${name}" is a subagent, not a primary agent. Falling back to default agent`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (agent) return name
|
|
||||||
await Bun.sleep(25)
|
|
||||||
}
|
|
||||||
if (!agents) {
|
|
||||||
warning(`failed to list agents${attach ? ` from ${attach}` : ""}. Falling back to default agent`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
warning(`agent "${name}" not found. Falling back to default agent`)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function selectSession(sdk: OpenCodeClient, directory: string, input: MiniCommandInput, preselected?: Session) {
|
|
||||||
const selected =
|
|
||||||
preselected ??
|
|
||||||
(input.session
|
|
||||||
? await sdk.session.get({ sessionID: input.session }).catch(() => undefined)
|
|
||||||
: input.continue
|
|
||||||
? await sdk.session
|
|
||||||
.list({ directory, parentID: null, limit: 1, order: "desc" })
|
|
||||||
.then((result) => result.data[0])
|
|
||||||
: undefined)
|
|
||||||
if (input.session && !selected) fail("Session not found")
|
|
||||||
if (!selected) return
|
|
||||||
if (!input.fork) return selected
|
|
||||||
return sdk.session.fork({ sessionID: selected.id })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createSession(
|
|
||||||
sdk: OpenCodeClient,
|
|
||||||
directory: string,
|
|
||||||
agent: string | undefined,
|
|
||||||
model: RunInput["model"],
|
|
||||||
variant?: string,
|
|
||||||
): Promise<Session> {
|
|
||||||
if (model) await waitForCatalogReady({ sdk, directory, model })
|
|
||||||
return sdk.session.create({
|
|
||||||
agent,
|
|
||||||
model: model ? { providerID: model.providerID, id: model.modelID, variant } : undefined,
|
|
||||||
location: { directory },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function warning(message: string) {
|
|
||||||
process.stderr.write(`\x1b[93m\x1b[1m!\x1b[0m ${message}\n`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function fail(message: string): never {
|
|
||||||
process.stderr.write(`\x1b[91m\x1b[1mError: \x1b[0m${message}\n`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
@@ -1,491 +0,0 @@
|
|||||||
import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise"
|
|
||||||
import type { ReasoningPart, StepFinishPart, StepStartPart, TextPart, ToolPart } from "@opencode-ai/sdk/v2"
|
|
||||||
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
|
||||||
import { EOL } from "node:os"
|
|
||||||
import { UI } from "./ui"
|
|
||||||
|
|
||||||
type Model = {
|
|
||||||
providerID: string
|
|
||||||
modelID: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type File = {
|
|
||||||
url: string
|
|
||||||
filename: string
|
|
||||||
mime: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Input = {
|
|
||||||
client: OpenCodeClient
|
|
||||||
sessionID: string
|
|
||||||
message: string
|
|
||||||
files: File[]
|
|
||||||
agent?: string
|
|
||||||
model?: Model
|
|
||||||
variant?: string
|
|
||||||
thinking: boolean
|
|
||||||
format: "default" | "json"
|
|
||||||
dangerouslySkipPermissions: boolean
|
|
||||||
/** True when the client is attached to a shared server rather than an exclusive in-process one. */
|
|
||||||
attached: boolean
|
|
||||||
renderTool: (part: ToolPart) => Promise<void>
|
|
||||||
renderToolError: (part: ToolPart) => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
type StartedPart = {
|
|
||||||
id: string
|
|
||||||
timestamp: number
|
|
||||||
}
|
|
||||||
|
|
||||||
type ToolState = StartedPart & {
|
|
||||||
assistantMessageID: string
|
|
||||||
tool: string
|
|
||||||
input: Record<string, unknown>
|
|
||||||
raw?: string
|
|
||||||
provider?: unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
type V2Event = EventSubscribeOutput
|
|
||||||
type FormRequest = Extract<V2Event, { type: "form.created" }>["data"]["form"]
|
|
||||||
|
|
||||||
// MCP elicitations are temporarily owned by the "global" sentinel instead of a real
|
|
||||||
// session. An exclusive local process may treat them as this run's blockers; an
|
|
||||||
// attached client must not cancel input that may belong to another session.
|
|
||||||
const GLOBAL_FORM_SESSION_ID = "global"
|
|
||||||
|
|
||||||
export async function runNonInteractivePrompt(input: Input) {
|
|
||||||
const controller = new AbortController()
|
|
||||||
const stream = input.client.event.subscribe({ signal: controller.signal })[Symbol.asyncIterator]()
|
|
||||||
const connected = await stream.next()
|
|
||||||
if (connected.done) throw new Error("Event stream disconnected before prompt admission")
|
|
||||||
|
|
||||||
const messageID = SessionMessage.ID.create()
|
|
||||||
const starts = new Map<string, StartedPart>()
|
|
||||||
const tools = new Map<string, ToolState>()
|
|
||||||
let submitted = false
|
|
||||||
let promoted = false
|
|
||||||
let emittedError = false
|
|
||||||
let questionRejected = false
|
|
||||||
let permissionRejected = false
|
|
||||||
let formCancelled = false
|
|
||||||
let interrupted = false
|
|
||||||
let admission: AbortController | undefined
|
|
||||||
|
|
||||||
const emit = (type: string, timestamp: number, data: Record<string, unknown>) => {
|
|
||||||
if (input.format !== "json") return false
|
|
||||||
process.stdout.write(JSON.stringify({ type, timestamp, sessionID: input.sessionID, ...data }) + EOL)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const writeText = (part: TextPart, timestamp: number) => {
|
|
||||||
if (emit("text", timestamp, { part })) return
|
|
||||||
const text = part.text.trim()
|
|
||||||
if (!text) return
|
|
||||||
if (!process.stdout.isTTY) {
|
|
||||||
process.stdout.write(text + EOL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
UI.empty()
|
|
||||||
UI.println(text)
|
|
||||||
UI.empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
const replyPermission = async (request: { id: string; action: string; resources: ReadonlyArray<string> }) => {
|
|
||||||
if (!input.dangerouslySkipPermissions) {
|
|
||||||
permissionRejected = true
|
|
||||||
UI.println(
|
|
||||||
UI.Style.TEXT_WARNING_BOLD + "!",
|
|
||||||
UI.Style.TEXT_NORMAL +
|
|
||||||
`permission requested: ${request.action} (${request.resources.join(", ")}); auto-rejecting`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
await input.client.permission
|
|
||||||
.reply({
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
requestID: request.id,
|
|
||||||
reply: input.dangerouslySkipPermissions ? "once" : "reject",
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
if (!input.dangerouslySkipPermissions) {
|
|
||||||
await input.client.session.interrupt({ sessionID: input.sessionID }).catch(() => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rejectQuestion = async (request: { id: string }) => {
|
|
||||||
questionRejected = true
|
|
||||||
await input.client.question.reject({ sessionID: input.sessionID, requestID: request.id }).catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelForm = async (request: Pick<FormRequest, "id" | "sessionID">) => {
|
|
||||||
formCancelled = true
|
|
||||||
await input.client.form.cancel({ sessionID: request.sessionID, formID: request.id }).catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
const consume = async () => {
|
|
||||||
while (!controller.signal.aborted) {
|
|
||||||
const next = await stream.next().catch((error) => {
|
|
||||||
if (!emittedError) throw error
|
|
||||||
return { done: true as const, value: undefined }
|
|
||||||
})
|
|
||||||
if (next.done) {
|
|
||||||
if (emittedError) return
|
|
||||||
throw new Error("Event stream disconnected during prompt execution")
|
|
||||||
}
|
|
||||||
const event = next.value
|
|
||||||
|
|
||||||
if (event.type === "permission.v2.asked" && submitted && event.data.sessionID === input.sessionID) {
|
|
||||||
await replyPermission(event.data)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "question.v2.asked" && submitted && event.data.sessionID === input.sessionID) {
|
|
||||||
await rejectQuestion(event.data)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
event.type === "form.created" &&
|
|
||||||
submitted &&
|
|
||||||
(event.data.form.sessionID === input.sessionID ||
|
|
||||||
(!input.attached && event.data.form.sessionID === GLOBAL_FORM_SESSION_ID))
|
|
||||||
) {
|
|
||||||
await cancelForm(event.data.form)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (!("sessionID" in event.data) || event.data.sessionID !== input.sessionID) continue
|
|
||||||
const time = toMillis("created" in event ? event.created : undefined)
|
|
||||||
|
|
||||||
if (event.type === "session.prompt.promoted") {
|
|
||||||
if (event.data.inputID === messageID) {
|
|
||||||
promoted = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
event.type === "session.execution.interrupted" &&
|
|
||||||
event.data.reason === "user" &&
|
|
||||||
(interrupted || permissionRejected || questionRejected || formCancelled)
|
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!promoted) continue
|
|
||||||
|
|
||||||
if (event.type === "session.step.started") {
|
|
||||||
const part: StepStartPart = {
|
|
||||||
id: partID(event.id),
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
type: "step-start",
|
|
||||||
snapshot: event.data.snapshot,
|
|
||||||
}
|
|
||||||
if (!emit("step_start", time, { part }) && input.format !== "json") {
|
|
||||||
UI.empty()
|
|
||||||
UI.println(`> ${event.data.agent} · ${event.data.model.id}`)
|
|
||||||
UI.empty()
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === "session.text.started") {
|
|
||||||
starts.set("text", { id: partID(event.id), timestamp: time })
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "session.text.ended") {
|
|
||||||
const started = starts.get("text")
|
|
||||||
starts.delete("text")
|
|
||||||
const part: TextPart = {
|
|
||||||
id: started?.id ?? partID(event.id),
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
type: "text",
|
|
||||||
text: event.data.text,
|
|
||||||
time: { start: started?.timestamp ?? time, end: time },
|
|
||||||
}
|
|
||||||
writeText(part, time)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === "session.reasoning.started") {
|
|
||||||
starts.set("reasoning", { id: partID(event.id), timestamp: time })
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "session.reasoning.ended" && input.thinking) {
|
|
||||||
const started = starts.get("reasoning")
|
|
||||||
starts.delete("reasoning")
|
|
||||||
const part: ReasoningPart = {
|
|
||||||
id: started?.id ?? partID(event.id),
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
type: "reasoning",
|
|
||||||
text: event.data.text,
|
|
||||||
metadata: event.data.state,
|
|
||||||
time: { start: started?.timestamp ?? time, end: time },
|
|
||||||
}
|
|
||||||
if (emit("reasoning", time, { part })) continue
|
|
||||||
const text = part.text.trim()
|
|
||||||
if (!text) continue
|
|
||||||
const line = `Thinking: ${text}`
|
|
||||||
if (!process.stdout.isTTY) {
|
|
||||||
process.stdout.write(line + EOL)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
UI.empty()
|
|
||||||
UI.println(`${UI.Style.TEXT_DIM}\u001b[3m${line}\u001b[0m${UI.Style.TEXT_NORMAL}`)
|
|
||||||
UI.empty()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === "session.tool.input.started") {
|
|
||||||
tools.set(event.data.callID, {
|
|
||||||
id: partID(event.id),
|
|
||||||
timestamp: time,
|
|
||||||
assistantMessageID: event.data.assistantMessageID,
|
|
||||||
tool: event.data.name,
|
|
||||||
input: {},
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "session.tool.input.ended") {
|
|
||||||
const current = tools.get(event.data.callID)
|
|
||||||
if (current) current.raw = event.data.text
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "session.tool.called") {
|
|
||||||
const current = tools.get(event.data.callID)
|
|
||||||
tools.set(event.data.callID, {
|
|
||||||
id: current?.id ?? partID(event.id),
|
|
||||||
timestamp: current?.timestamp ?? time,
|
|
||||||
assistantMessageID: event.data.assistantMessageID,
|
|
||||||
tool: current?.tool ?? "tool",
|
|
||||||
input: event.data.input,
|
|
||||||
raw: current?.raw,
|
|
||||||
provider: { executed: event.data.executed, state: event.data.state },
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "session.tool.success") {
|
|
||||||
const current = tools.get(event.data.callID) ?? fallbackTool(event)
|
|
||||||
const part: ToolPart = {
|
|
||||||
id: current.id,
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
type: "tool",
|
|
||||||
callID: event.data.callID,
|
|
||||||
tool: current.tool,
|
|
||||||
state: {
|
|
||||||
status: "completed",
|
|
||||||
input: current.input,
|
|
||||||
output: event.data.content
|
|
||||||
.filter((item) => item.type === "text")
|
|
||||||
.map((item) => item.text)
|
|
||||||
.join("\n"),
|
|
||||||
title: current.tool,
|
|
||||||
metadata: {
|
|
||||||
structured: event.data.structured,
|
|
||||||
content: event.data.content,
|
|
||||||
outputPaths: event.data.outputPaths,
|
|
||||||
result: event.data.result,
|
|
||||||
providerCall: current.provider,
|
|
||||||
providerResult: { executed: event.data.executed, state: event.data.resultState },
|
|
||||||
rawInput: current.raw,
|
|
||||||
},
|
|
||||||
time: { start: current.timestamp, end: time },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tools.delete(event.data.callID)
|
|
||||||
if (!emit("tool_use", time, { part })) await input.renderTool(part)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "session.tool.failed") {
|
|
||||||
const current = tools.get(event.data.callID) ?? fallbackTool(event)
|
|
||||||
const error = event.data.error.message
|
|
||||||
const part: ToolPart = {
|
|
||||||
id: current.id,
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
type: "tool",
|
|
||||||
callID: event.data.callID,
|
|
||||||
tool: current.tool,
|
|
||||||
state: {
|
|
||||||
status: "error",
|
|
||||||
input: current.input,
|
|
||||||
error,
|
|
||||||
metadata: {
|
|
||||||
result: event.data.result,
|
|
||||||
providerCall: current.provider,
|
|
||||||
providerResult: { executed: event.data.executed, state: event.data.resultState },
|
|
||||||
rawInput: current.raw,
|
|
||||||
},
|
|
||||||
time: { start: current.timestamp, end: time },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tools.delete(event.data.callID)
|
|
||||||
if (!emit("tool_use", time, { part })) {
|
|
||||||
await input.renderToolError(part)
|
|
||||||
UI.error(error)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type === "session.step.ended") {
|
|
||||||
const part: StepFinishPart = {
|
|
||||||
id: partID(event.id),
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
type: "step-finish",
|
|
||||||
reason: event.data.finish,
|
|
||||||
snapshot: event.data.snapshot,
|
|
||||||
cost: event.data.cost,
|
|
||||||
tokens: event.data.tokens,
|
|
||||||
}
|
|
||||||
emit("step_finish", time, { part })
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "session.step.failed") {
|
|
||||||
if (interrupted || permissionRejected || questionRejected || formCancelled) continue
|
|
||||||
emittedError = true
|
|
||||||
process.exitCode = 1
|
|
||||||
if (!emit("error", time, { error: event.data.error })) UI.error(event.data.error.message)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (event.type === "session.execution.failed") {
|
|
||||||
if (!emittedError && !questionRejected && !formCancelled) {
|
|
||||||
emittedError = true
|
|
||||||
process.exitCode = 1
|
|
||||||
if (!emit("error", time, { error: event.data.error })) UI.error(event.data.error.message)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.execution.interrupted") {
|
|
||||||
if (event.data.reason === "user" && interrupted) process.exitCode = 130
|
|
||||||
if (event.data.reason !== "user" && !emittedError) {
|
|
||||||
emittedError = true
|
|
||||||
process.exitCode = 1
|
|
||||||
const error = { type: "aborted" as const, message: `Session interrupted: ${event.data.reason}` }
|
|
||||||
if (!emit("error", time, { error })) UI.error(error.message)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.execution.succeeded") return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const interrupt = () => {
|
|
||||||
if (interrupted) process.exit(130)
|
|
||||||
interrupted = true
|
|
||||||
process.exitCode = 130
|
|
||||||
admission?.abort()
|
|
||||||
void input.client.session.interrupt({ sessionID: input.sessionID }).catch(() => {})
|
|
||||||
}
|
|
||||||
process.on("SIGINT", interrupt)
|
|
||||||
|
|
||||||
let completed: Promise<void> | undefined
|
|
||||||
try {
|
|
||||||
if (input.agent) {
|
|
||||||
await input.client.session.switchAgent({ sessionID: input.sessionID, agent: input.agent })
|
|
||||||
}
|
|
||||||
const selected = input.model
|
|
||||||
? { providerID: input.model.providerID, id: input.model.modelID, variant: input.variant }
|
|
||||||
: input.variant
|
|
||||||
? await input.client.session
|
|
||||||
.get({ sessionID: input.sessionID })
|
|
||||||
.then((result) => result.model)
|
|
||||||
.then(async (model) => {
|
|
||||||
if (model) return { ...model, variant: input.variant }
|
|
||||||
const result = await input.client.model.default()
|
|
||||||
const fallback = result.data
|
|
||||||
return fallback ? { providerID: fallback.providerID, id: fallback.id, variant: input.variant } : undefined
|
|
||||||
})
|
|
||||||
: undefined
|
|
||||||
if (input.variant && !selected) throw new Error("Cannot select a variant before selecting a model")
|
|
||||||
if (selected) {
|
|
||||||
await input.client.session.switchModel({ sessionID: input.sessionID, model: selected })
|
|
||||||
}
|
|
||||||
|
|
||||||
const prepared = await Promise.all(input.files.map(prepareFile))
|
|
||||||
if (interrupted) return
|
|
||||||
submitted = true
|
|
||||||
completed = consume()
|
|
||||||
admission = new AbortController()
|
|
||||||
const response = await input.client.session
|
|
||||||
.prompt(
|
|
||||||
{
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
id: messageID,
|
|
||||||
prompt: {
|
|
||||||
text: [input.message, ...prepared.flatMap((file) => (file.text ? [file.text] : []))].join("\n\n"),
|
|
||||||
files: prepared.flatMap((file) => (file.attachment ? [file.attachment] : [])),
|
|
||||||
},
|
|
||||||
delivery: "steer",
|
|
||||||
},
|
|
||||||
{ signal: admission.signal },
|
|
||||||
)
|
|
||||||
.catch(async (error) => {
|
|
||||||
if (interrupted) {
|
|
||||||
await input.client.session.interrupt({ sessionID: input.sessionID }).catch(() => {})
|
|
||||||
}
|
|
||||||
controller.abort()
|
|
||||||
await completed?.catch(() => {})
|
|
||||||
if (interrupted || emittedError) return undefined
|
|
||||||
throw error
|
|
||||||
})
|
|
||||||
admission = undefined
|
|
||||||
if (!response) return
|
|
||||||
if (interrupted) await input.client.session.interrupt({ sessionID: input.sessionID }).catch(() => {})
|
|
||||||
|
|
||||||
const [permissions, questions, forms] = await Promise.all([
|
|
||||||
input.client.permission.list({ sessionID: input.sessionID }).catch(() => undefined),
|
|
||||||
input.client.question.list({ sessionID: input.sessionID }).catch(() => undefined),
|
|
||||||
Promise.all(
|
|
||||||
(input.attached ? [input.sessionID] : [input.sessionID, GLOBAL_FORM_SESSION_ID]).map((sessionID) =>
|
|
||||||
input.client.form.list({ sessionID }).catch(() => undefined),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
await Promise.all([
|
|
||||||
...(permissions ?? []).map(replyPermission),
|
|
||||||
...(questions ?? []).map(rejectQuestion),
|
|
||||||
...forms.flatMap((response) => response ?? []).map(cancelForm),
|
|
||||||
])
|
|
||||||
await completed
|
|
||||||
} finally {
|
|
||||||
process.off("SIGINT", interrupt)
|
|
||||||
controller.abort()
|
|
||||||
await stream.return?.(undefined).catch(() => {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function partID(eventID: string) {
|
|
||||||
return `prt_${eventID.replace(/^evt_/, "")}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function fallbackTool(event: {
|
|
||||||
id: string
|
|
||||||
created: number
|
|
||||||
data: { assistantMessageID: string; callID: string }
|
|
||||||
}): ToolState {
|
|
||||||
return {
|
|
||||||
id: partID(event.id),
|
|
||||||
timestamp: toMillis(event.created),
|
|
||||||
assistantMessageID: event.data.assistantMessageID,
|
|
||||||
tool: "tool",
|
|
||||||
input: {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toMillis(value: unknown) {
|
|
||||||
if (typeof value === "number") return value
|
|
||||||
if (typeof value === "string") return new Date(value).getTime()
|
|
||||||
return Date.now()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function prepareFile(file: File) {
|
|
||||||
if (file.mime !== "text/plain") {
|
|
||||||
const uri = file.url.startsWith("data:")
|
|
||||||
? file.url
|
|
||||||
: `data:${file.mime};base64,${Buffer.from(await Bun.file(new URL(file.url)).arrayBuffer()).toString("base64")}`
|
|
||||||
return { attachment: { uri, mime: file.mime, name: file.filename } }
|
|
||||||
}
|
|
||||||
const content = file.url.startsWith("data:")
|
|
||||||
? Buffer.from(file.url.slice(file.url.indexOf(",") + 1), "base64").toString("utf8")
|
|
||||||
: await Bun.file(new URL(file.url)).text()
|
|
||||||
return { text: `<file name="${file.filename}">\n${content}\n</file>` }
|
|
||||||
}
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
import { NodeFileSystem } from "@effect/platform-node"
|
|
||||||
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
|
|
||||||
import { Global } from "@opencode-ai/core/global"
|
|
||||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
|
||||||
import type { ToolPart } from "@opencode-ai/sdk/v2"
|
|
||||||
import { Effect } from "effect"
|
|
||||||
import { open } from "node:fs/promises"
|
|
||||||
import path from "node:path"
|
|
||||||
import { Daemon } from "../daemon"
|
|
||||||
import { Standalone } from "../services/standalone"
|
|
||||||
import { loadRunAgents, waitForCatalogReady } from "./catalog.shared"
|
|
||||||
import { runNonInteractivePrompt } from "./noninteractive"
|
|
||||||
import { toolInlineInfo } from "./tool"
|
|
||||||
import { UI } from "./ui"
|
|
||||||
|
|
||||||
export type RunCommandInput = {
|
|
||||||
message: string[]
|
|
||||||
continue?: boolean
|
|
||||||
session?: string
|
|
||||||
fork?: boolean
|
|
||||||
model?: string
|
|
||||||
agent?: string
|
|
||||||
format: "default" | "json"
|
|
||||||
file: string[]
|
|
||||||
title?: string
|
|
||||||
server?: string
|
|
||||||
password?: string
|
|
||||||
username?: string
|
|
||||||
directory?: string
|
|
||||||
variant?: string
|
|
||||||
thinking?: boolean
|
|
||||||
dangerouslySkipPermissions?: boolean
|
|
||||||
standaloneCommand?: ReadonlyArray<string>
|
|
||||||
}
|
|
||||||
|
|
||||||
type FilePart = {
|
|
||||||
url: string
|
|
||||||
filename: string
|
|
||||||
mime: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Transport = { readonly url: string; readonly headers?: HeadersInit }
|
|
||||||
|
|
||||||
type Prepared = {
|
|
||||||
directory?: string
|
|
||||||
message: string
|
|
||||||
files: FilePart[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const ATTACH_FILE_MAX_BYTES = 10 * 1024 * 1024
|
|
||||||
|
|
||||||
export function runNonInteractive(input: RunCommandInput) {
|
|
||||||
return run(input).catch((error) => reportError(input, error instanceof Error ? error.message : String(error)))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run(input: RunCommandInput) {
|
|
||||||
if (input.fork && !input.continue && !input.session) fail("--fork requires --continue or --session")
|
|
||||||
const root = process.env.PWD ?? process.cwd()
|
|
||||||
const directory = input.server ? input.directory : localDirectory(input.directory, root)
|
|
||||||
const message = mergeInput(formatMessage(input.message), process.stdin.isTTY ? undefined : await Bun.stdin.text())
|
|
||||||
if (!message?.trim()) fail("You must provide a message")
|
|
||||||
const files = await Promise.all(
|
|
||||||
input.file.map((file) => prepareFile(file, input.server ? root : (directory ?? root), input.server !== undefined)),
|
|
||||||
)
|
|
||||||
const prepared = { directory, message, files }
|
|
||||||
if (input.standaloneCommand)
|
|
||||||
return Effect.runPromise(
|
|
||||||
Effect.scoped(
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const transport = yield* Standalone.transport({ command: input.standaloneCommand })
|
|
||||||
yield* Effect.promise(() => execute(input, prepared, transport))
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const transport = await startTransport(input)
|
|
||||||
return execute(input, prepared, transport)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function execute(input: RunCommandInput, prepared: Prepared, transport: Transport) {
|
|
||||||
const client = OpenCode.make({ baseUrl: transport.url, headers: transport.headers })
|
|
||||||
const requestedDirectory = prepared.directory ?? (await client.location.get()).directory
|
|
||||||
if (!requestedDirectory) fail("Failed to resolve server directory")
|
|
||||||
const session = await selectSession(client, requestedDirectory, input)
|
|
||||||
const cwd = session?.location.directory ?? requestedDirectory
|
|
||||||
const workspace = session?.location.workspaceID
|
|
||||||
const explicitModel = parseModel(input.model)
|
|
||||||
const sessionModel = session?.model ? { providerID: session.model.providerID, modelID: session.model.id } : undefined
|
|
||||||
const defaultModel =
|
|
||||||
!explicitModel && !sessionModel
|
|
||||||
? await client.model
|
|
||||||
.default({ location: { directory: cwd, workspace } })
|
|
||||||
.then((result) =>
|
|
||||||
result.data ? { providerID: result.data.providerID, modelID: result.data.id } : undefined,
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
const model = pickRunModel(explicitModel, input.variant, sessionModel, defaultModel)
|
|
||||||
if (input.variant && !model) return reportError(input, "Cannot select a variant before selecting a model", session?.id)
|
|
||||||
if (model) {
|
|
||||||
await waitForCatalogReady({ sdk: client, directory: cwd, workspace, model })
|
|
||||||
const available = await client.model.list({ location: { directory: cwd, workspace } })
|
|
||||||
if (!available.data.some((item) => item.providerID === model.providerID && item.id === model.modelID))
|
|
||||||
return reportError(input, `Model unavailable: ${model.providerID}/${model.modelID}`, session?.id)
|
|
||||||
}
|
|
||||||
const agent = await validateAgent(client, cwd, input.agent, input.server)
|
|
||||||
const selected =
|
|
||||||
session ??
|
|
||||||
(await client.session.create({
|
|
||||||
agent,
|
|
||||||
model: model ? { providerID: model.providerID, id: model.modelID, variant: input.variant } : undefined,
|
|
||||||
location: { directory: cwd },
|
|
||||||
}))
|
|
||||||
if (!session && input.title !== undefined) {
|
|
||||||
await client.session.rename({
|
|
||||||
sessionID: selected.id,
|
|
||||||
title:
|
|
||||||
input.title ||
|
|
||||||
prepared.message.slice(0, 50) + (prepared.message.length > 50 ? "..." : ""),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
await runNonInteractivePrompt({
|
|
||||||
client,
|
|
||||||
sessionID: selected.id,
|
|
||||||
message: prepared.message,
|
|
||||||
files: prepared.files,
|
|
||||||
agent,
|
|
||||||
model,
|
|
||||||
variant: input.variant,
|
|
||||||
thinking: input.thinking ?? false,
|
|
||||||
format: input.format,
|
|
||||||
dangerouslySkipPermissions: input.dangerouslySkipPermissions ?? false,
|
|
||||||
attached: !input.standaloneCommand,
|
|
||||||
renderTool,
|
|
||||||
renderToolError,
|
|
||||||
}).catch((error) => reportError(input, error instanceof Error ? error.message : String(error), selected.id))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mergeInput(message: string | undefined, piped: string | undefined) {
|
|
||||||
if (!message) return piped || undefined
|
|
||||||
if (!piped) return message
|
|
||||||
return message + "\n" + piped
|
|
||||||
}
|
|
||||||
|
|
||||||
export function pickRunModel(
|
|
||||||
explicit: { providerID: string; modelID: string } | undefined,
|
|
||||||
variant: string | undefined,
|
|
||||||
session: { providerID: string; modelID: string } | undefined,
|
|
||||||
fallback: { providerID: string; modelID: string } | undefined,
|
|
||||||
) {
|
|
||||||
if (explicit) return explicit
|
|
||||||
if (!variant) return
|
|
||||||
return session ?? fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatMessage(message: string[]) {
|
|
||||||
const value = message.map((part) => (part.includes(" ") ? `"${part.replace(/"/g, '\\"')}"` : part)).join(" ")
|
|
||||||
return value || undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
function localDirectory(directory: string | undefined, root: string) {
|
|
||||||
try {
|
|
||||||
process.chdir(directory ? (path.isAbsolute(directory) ? directory : path.join(root, directory)) : root)
|
|
||||||
return process.cwd()
|
|
||||||
} catch {
|
|
||||||
fail(`Failed to change directory to ${directory}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startTransport(input: RunCommandInput) {
|
|
||||||
if (input.server) {
|
|
||||||
return Effect.runPromise(
|
|
||||||
Daemon.transport({
|
|
||||||
mode: "attach",
|
|
||||||
url: input.server,
|
|
||||||
password: input.password,
|
|
||||||
username: input.username,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return Effect.runPromise(
|
|
||||||
Daemon.transport({ mode: "shared" }).pipe(
|
|
||||||
Effect.provide(NodeFileSystem.layer),
|
|
||||||
Effect.provide(Global.layerWith({})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseModel(value?: string) {
|
|
||||||
if (!value) return
|
|
||||||
const [providerID, ...rest] = value.split("/")
|
|
||||||
const modelID = rest.join("/")
|
|
||||||
if (!providerID || !modelID) fail("--model must use the format provider/model")
|
|
||||||
return { providerID, modelID }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateAgent(client: OpenCodeClient, directory: string, name?: string, server?: string) {
|
|
||||||
if (!name) return
|
|
||||||
const agents = await loadRunAgents(client, directory).catch(() => undefined)
|
|
||||||
if (!agents) {
|
|
||||||
warning(`failed to list agents${server ? ` from ${server}` : ""}. Falling back to default agent`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const agent = agents.find((item) => item.name === name)
|
|
||||||
if (!agent) {
|
|
||||||
warning(`agent "${name}" not found. Falling back to default agent`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (agent.mode === "subagent") {
|
|
||||||
warning(`agent "${name}" is a subagent, not a primary agent. Falling back to default agent`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
async function selectSession(client: OpenCodeClient, directory: string, input: RunCommandInput) {
|
|
||||||
const selected = input.session
|
|
||||||
? await client.session.get({ sessionID: input.session }).catch(() => undefined)
|
|
||||||
: input.continue
|
|
||||||
? await client.session
|
|
||||||
.list({ directory, parentID: null, limit: 1, order: "desc" })
|
|
||||||
.then((result) => result.data[0])
|
|
||||||
: undefined
|
|
||||||
if (input.session && !selected) fail("Session not found")
|
|
||||||
if (!selected || !input.fork) return selected
|
|
||||||
return client.session.fork({ sessionID: selected.id })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function prepareFile(input: string, directory: string, remote: boolean): Promise<FilePart> {
|
|
||||||
const file = path.resolve(directory, input)
|
|
||||||
const handle = await open(file, "r").catch(() => fail(`File not found: ${input}`))
|
|
||||||
try {
|
|
||||||
const stat = await handle.stat()
|
|
||||||
if (remote && stat.isDirectory()) fail(`Cannot attach local directory without a shared filesystem: ${input}`)
|
|
||||||
if (!stat.isFile() || stat.size > ATTACH_FILE_MAX_BYTES)
|
|
||||||
fail(`Cannot attach a directory, special file, or file larger than 10 MiB: ${input}`)
|
|
||||||
const content = Buffer.alloc(Number(stat.size))
|
|
||||||
let offset = 0
|
|
||||||
while (offset < content.length) {
|
|
||||||
const read = await handle.read(content, offset, content.length - offset, offset)
|
|
||||||
if (read.bytesRead === 0) break
|
|
||||||
offset += read.bytesRead
|
|
||||||
}
|
|
||||||
const bytes = content.subarray(0, offset)
|
|
||||||
const detected = FSUtil.mimeType(file)
|
|
||||||
const text = bytes.toString("utf8")
|
|
||||||
const mime =
|
|
||||||
detected.startsWith("image/") || detected === "application/pdf"
|
|
||||||
? detected
|
|
||||||
: !isBinaryContent(bytes) && Buffer.from(text, "utf8").equals(bytes)
|
|
||||||
? "text/plain"
|
|
||||||
: detected
|
|
||||||
return {
|
|
||||||
url: `data:${mime};base64,${bytes.toString("base64")}`,
|
|
||||||
filename: path.basename(file),
|
|
||||||
mime,
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
await handle.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isBinaryContent(bytes: Uint8Array) {
|
|
||||||
if (bytes.length === 0) return false
|
|
||||||
if (bytes.includes(0)) return true
|
|
||||||
return bytes.reduce((count, byte) => count + Number(byte < 9 || (byte > 13 && byte < 32)), 0) / bytes.length > 0.3
|
|
||||||
}
|
|
||||||
|
|
||||||
async function renderTool(part: ToolPart) {
|
|
||||||
const info = toolInlineInfo(part)
|
|
||||||
if (info.mode === "block") {
|
|
||||||
UI.empty()
|
|
||||||
UI.println(UI.Style.TEXT_NORMAL + info.icon, UI.Style.TEXT_NORMAL + info.title)
|
|
||||||
if (info.body?.trim()) UI.println(info.body)
|
|
||||||
UI.empty()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
UI.println(
|
|
||||||
UI.Style.TEXT_NORMAL + info.icon,
|
|
||||||
UI.Style.TEXT_NORMAL + info.title,
|
|
||||||
info.description ? UI.Style.TEXT_DIM + info.description + UI.Style.TEXT_NORMAL : "",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function renderToolError(part: ToolPart) {
|
|
||||||
const info = toolInlineInfo(part)
|
|
||||||
UI.println(UI.Style.TEXT_NORMAL + "✗", UI.Style.TEXT_NORMAL + `${info.title} failed`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function warning(message: string) {
|
|
||||||
UI.println(UI.Style.TEXT_WARNING_BOLD + "!", UI.Style.TEXT_NORMAL, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
function reportError(input: RunCommandInput, message: string, sessionID?: string) {
|
|
||||||
process.exitCode = 1
|
|
||||||
if (input.format === "json") {
|
|
||||||
process.stdout.write(
|
|
||||||
JSON.stringify({
|
|
||||||
type: "error",
|
|
||||||
timestamp: Date.now(),
|
|
||||||
sessionID: sessionID ?? "",
|
|
||||||
error: { type: "unknown", message },
|
|
||||||
}) + "\n",
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
UI.error(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
function fail(message: string): never {
|
|
||||||
throw new Error(message)
|
|
||||||
}
|
|
||||||
@@ -1,783 +0,0 @@
|
|||||||
// Current-native subagent (child Session) tracking for the mini transport.
|
|
||||||
//
|
|
||||||
// Discovers child Sessions of the active parent from four current sources:
|
|
||||||
// 1. projected subagent tool output (`structured.sessionID`) during hydration
|
|
||||||
// 2. the current session list filtered by `parentID` during hydration
|
|
||||||
// 3. the process-local active-session map during hydration
|
|
||||||
// 4. live events from unknown sessions whose `parentID` matches the parent
|
|
||||||
//
|
|
||||||
// Tracks one footer tab per child and a detail transcript for the selected
|
|
||||||
// child, reduced from the same current live event stream the parent uses.
|
|
||||||
// Detail transcripts rebuild from projected messages on discovery, selection,
|
|
||||||
// and reconnect, then continue from live deltas using the same
|
|
||||||
// projected-prefix dedup the parent transport uses.
|
|
||||||
//
|
|
||||||
// Per-child interruption uses `v2.session.interrupt(childID)`. Per-child
|
|
||||||
// backgrounding is intentionally absent: subagent jobs block the parent
|
|
||||||
// session, so only whole-session `v2.session.background(parentID)` exists.
|
|
||||||
import type { EventSubscribeOutput, OpenCodeClient } from "@opencode-ai/client/promise"
|
|
||||||
import type { SessionMessage, SessionMessageAssistantTool, ToolPart } from "@opencode-ai/sdk/v2"
|
|
||||||
import { Locale } from "@opencode-ai/tui/util/locale"
|
|
||||||
import type { FooterSubagentDetail, FooterSubagentState, FooterSubagentTab, StreamCommit } from "./types"
|
|
||||||
|
|
||||||
const CHILD_MESSAGE_LIMIT = 80
|
|
||||||
const CHILD_FRAME_LIMIT = 80
|
|
||||||
const CHILD_EVENT_BUFFER_LIMIT = 64
|
|
||||||
const FAMILY_LIST_LIMIT = 100
|
|
||||||
const FALLBACK_LABEL = "Subagent"
|
|
||||||
|
|
||||||
type V2Event = EventSubscribeOutput
|
|
||||||
|
|
||||||
export function outputText(content: ReadonlyArray<{ type: string; text?: string }>) {
|
|
||||||
return content.flatMap((item) => (item.type === "text" && item.text ? [item.text] : [])).join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
export function legacyTool(input: {
|
|
||||||
sessionID: string
|
|
||||||
messageID: string
|
|
||||||
tool: SessionMessageAssistantTool
|
|
||||||
}): ToolPart {
|
|
||||||
const tool = input.tool
|
|
||||||
const providerCall =
|
|
||||||
tool.executed === undefined && tool.providerState === undefined
|
|
||||||
? undefined
|
|
||||||
: { executed: tool.executed, state: tool.providerState }
|
|
||||||
const providerResult =
|
|
||||||
tool.executed === undefined && tool.providerResultState === undefined
|
|
||||||
? undefined
|
|
||||||
: { executed: tool.executed, state: tool.providerResultState }
|
|
||||||
const base = {
|
|
||||||
id: `prt_${tool.id}`,
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
messageID: input.messageID,
|
|
||||||
type: "tool" as const,
|
|
||||||
callID: tool.id,
|
|
||||||
tool: tool.name,
|
|
||||||
}
|
|
||||||
if (tool.state.status === "pending") {
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
state: { status: "pending", input: {}, raw: tool.state.input },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tool.state.status === "running") {
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
state: {
|
|
||||||
status: "running",
|
|
||||||
input: tool.state.input,
|
|
||||||
title: tool.name,
|
|
||||||
metadata: { structured: tool.state.structured, content: tool.state.content, providerCall },
|
|
||||||
time: { start: tool.time.ran ?? tool.time.created },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tool.state.status === "completed") {
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
state: {
|
|
||||||
status: "completed",
|
|
||||||
input: tool.state.input,
|
|
||||||
output: outputText(tool.state.content),
|
|
||||||
title: tool.name,
|
|
||||||
metadata: {
|
|
||||||
structured: tool.state.structured,
|
|
||||||
content: tool.state.content,
|
|
||||||
outputPaths: tool.state.outputPaths,
|
|
||||||
result: tool.state.result,
|
|
||||||
providerCall,
|
|
||||||
providerResult,
|
|
||||||
},
|
|
||||||
time: { start: tool.time.ran ?? tool.time.created, end: tool.time.completed ?? tool.time.created },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...base,
|
|
||||||
state: {
|
|
||||||
status: "error",
|
|
||||||
input: tool.state.input,
|
|
||||||
error: tool.state.error.message,
|
|
||||||
metadata: {
|
|
||||||
structured: tool.state.structured,
|
|
||||||
content: tool.state.content,
|
|
||||||
result: tool.state.result,
|
|
||||||
providerCall,
|
|
||||||
providerResult,
|
|
||||||
},
|
|
||||||
time: { start: tool.time.ran ?? tool.time.created, end: tool.time.completed ?? tool.time.created },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toolCommit(part: ToolPart, phase: "start" | "progress" | "final"): StreamCommit {
|
|
||||||
const status = part.state.status
|
|
||||||
const text =
|
|
||||||
status === "running"
|
|
||||||
? part.tool === "task"
|
|
||||||
? "running task"
|
|
||||||
: `running ${part.tool}`
|
|
||||||
: status === "completed"
|
|
||||||
? part.state.output
|
|
||||||
: status === "error"
|
|
||||||
? part.state.error
|
|
||||||
: ""
|
|
||||||
return {
|
|
||||||
kind: "tool",
|
|
||||||
source: "tool",
|
|
||||||
text,
|
|
||||||
phase,
|
|
||||||
messageID: part.messageID,
|
|
||||||
partID: part.id,
|
|
||||||
tool: part.tool,
|
|
||||||
part,
|
|
||||||
toolState: status === "error" ? "error" : status === "completed" ? "completed" : "running",
|
|
||||||
toolError: status === "error" ? part.state.error : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Frame = {
|
|
||||||
key: string
|
|
||||||
commit: StreamCommit
|
|
||||||
}
|
|
||||||
|
|
||||||
type ToolTrack = {
|
|
||||||
name: string
|
|
||||||
input: Record<string, unknown>
|
|
||||||
started: number
|
|
||||||
providerState?: Record<string, unknown>
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChildState = {
|
|
||||||
sessionID: string
|
|
||||||
label: string
|
|
||||||
description: string
|
|
||||||
status: FooterSubagentTab["status"]
|
|
||||||
background: boolean
|
|
||||||
title?: string
|
|
||||||
callIDs: Set<string>
|
|
||||||
lastUpdatedAt: number
|
|
||||||
frames: Frame[]
|
|
||||||
text: Map<string, string>
|
|
||||||
projectedText: Map<string, string>
|
|
||||||
reasoning: Map<string, string>
|
|
||||||
projectedReasoning: Map<string, string>
|
|
||||||
tools: Map<string, ToolTrack>
|
|
||||||
finishedTools: Set<string>
|
|
||||||
messageIDs: Set<string>
|
|
||||||
prompts: Map<string, string>
|
|
||||||
hydrated: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SubagentTrackerInput = {
|
|
||||||
sdk: OpenCodeClient
|
|
||||||
sessionID: string
|
|
||||||
thinking: boolean
|
|
||||||
emit: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SubagentTracker = {
|
|
||||||
main(event: V2Event): void
|
|
||||||
foreign(sessionID: string, event: V2Event): void
|
|
||||||
hydrate(next: { messages: SessionMessage[]; active: Record<string, unknown> }): Promise<void>
|
|
||||||
select(sessionID: string | undefined): void
|
|
||||||
snapshot(): FooterSubagentState
|
|
||||||
}
|
|
||||||
|
|
||||||
function record(value: unknown): Record<string, unknown> | undefined {
|
|
||||||
if (typeof value === "object" && value !== null && !Array.isArray(value)) return value as Record<string, unknown>
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
function text(value: unknown): string | undefined {
|
|
||||||
if (typeof value !== "string") return undefined
|
|
||||||
const next = value.trim()
|
|
||||||
return next || undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
function childSessionID(structured: Record<string, unknown> | undefined) {
|
|
||||||
const sessionID = text(structured?.sessionID)
|
|
||||||
if (!sessionID || !sessionID.startsWith("ses")) return undefined
|
|
||||||
const status = structured?.status
|
|
||||||
if (status !== "running" && status !== "completed") return undefined
|
|
||||||
return { sessionID, running: status === "running" }
|
|
||||||
}
|
|
||||||
|
|
||||||
function tab(child: ChildState): FooterSubagentTab {
|
|
||||||
return {
|
|
||||||
sessionID: child.sessionID,
|
|
||||||
partID: `subagent:${child.sessionID}`,
|
|
||||||
callID: `subagent:${child.sessionID}`,
|
|
||||||
label: child.label,
|
|
||||||
description: child.description || child.title || "",
|
|
||||||
status: child.status,
|
|
||||||
background: child.background ? true : undefined,
|
|
||||||
title: child.title,
|
|
||||||
toolCalls: child.callIDs.size > 0 ? child.callIDs.size : undefined,
|
|
||||||
lastUpdatedAt: child.lastUpdatedAt,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createSubagentTracker(input: SubagentTrackerInput): SubagentTracker {
|
|
||||||
const children = new Map<string, ChildState>()
|
|
||||||
// Live subagent tool calls in the parent, so tool.success structured output
|
|
||||||
// can be joined with the call's input metadata.
|
|
||||||
const pendingCalls = new Map<string, Record<string, unknown>>()
|
|
||||||
// Foreign sessions already resolved through session.get. Non-children stay
|
|
||||||
// cached so unrelated concurrent sessions are checked at most once.
|
|
||||||
const checked = new Set<string>()
|
|
||||||
// Foreign events buffered while a session.get discovery is in flight, so a
|
|
||||||
// fast child (including its settled event) is not lost mid-discovery.
|
|
||||||
const pendingEvents = new Map<string, V2Event[]>()
|
|
||||||
const hydrationEvents = new Map<string, V2Event[]>()
|
|
||||||
const hydrationOverflow = new Set<string>()
|
|
||||||
const hydrations = new Map<string, Promise<void>>()
|
|
||||||
let selected: string | undefined
|
|
||||||
const fragmentKey = (messageID: string, partID: string) => `${messageID}\u0000${partID}`
|
|
||||||
|
|
||||||
const ensureChild = (sessionID: string): ChildState => {
|
|
||||||
const existing = children.get(sessionID)
|
|
||||||
const child: ChildState = existing ?? {
|
|
||||||
sessionID,
|
|
||||||
label: FALLBACK_LABEL,
|
|
||||||
description: "",
|
|
||||||
status: "running",
|
|
||||||
background: false,
|
|
||||||
callIDs: new Set(),
|
|
||||||
lastUpdatedAt: Date.now(),
|
|
||||||
frames: [],
|
|
||||||
text: new Map(),
|
|
||||||
projectedText: new Map(),
|
|
||||||
reasoning: new Map(),
|
|
||||||
projectedReasoning: new Map(),
|
|
||||||
tools: new Map(),
|
|
||||||
finishedTools: new Set(),
|
|
||||||
messageIDs: new Set(),
|
|
||||||
prompts: new Map(),
|
|
||||||
hydrated: false,
|
|
||||||
}
|
|
||||||
if (!existing) children.set(sessionID, child)
|
|
||||||
// Adopting a child while its session.get discovery is still in flight:
|
|
||||||
// drain the buffered events now. They arrived before whatever the caller
|
|
||||||
// applies next, so replaying them first preserves bus order, and the
|
|
||||||
// resolved discovery can no longer replay stale events (e.g. step.started)
|
|
||||||
// after a terminal settled event was applied directly.
|
|
||||||
const buffered = pendingEvents.get(sessionID)
|
|
||||||
if (buffered) {
|
|
||||||
pendingEvents.delete(sessionID)
|
|
||||||
for (const event of buffered) reduce(child, event)
|
|
||||||
}
|
|
||||||
return child
|
|
||||||
}
|
|
||||||
|
|
||||||
const touch = (child: ChildState, timestamp?: number) => {
|
|
||||||
child.lastUpdatedAt = Math.max(child.lastUpdatedAt, timestamp ?? Date.now())
|
|
||||||
}
|
|
||||||
|
|
||||||
const notifyDetail = (child: ChildState) => {
|
|
||||||
if (child.sessionID === selected) input.emit()
|
|
||||||
}
|
|
||||||
|
|
||||||
const setFrame = (child: ChildState, key: string, commit: StreamCommit) => {
|
|
||||||
const index = child.frames.findIndex((item) => item.key === key)
|
|
||||||
if (index === -1) {
|
|
||||||
child.frames.push({ key, commit })
|
|
||||||
if (child.frames.length > CHILD_FRAME_LIMIT) child.frames.splice(0, child.frames.length - CHILD_FRAME_LIMIT)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
child.frames[index] = { key, commit }
|
|
||||||
}
|
|
||||||
|
|
||||||
const applyMeta = (child: ChildState, meta: Record<string, unknown> | undefined) => {
|
|
||||||
if (!meta) return
|
|
||||||
const agent = text(meta.agent)
|
|
||||||
if (agent) child.label = Locale.titlecase(agent)
|
|
||||||
const description = text(meta.description)
|
|
||||||
if (description) child.description = description
|
|
||||||
if (meta.background === true) child.background = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const userFrame = (child: ChildState, messageID: string, value: string) => {
|
|
||||||
if (child.messageIDs.has(messageID)) return false
|
|
||||||
child.messageIDs.add(messageID)
|
|
||||||
setFrame(child, `user:${messageID}`, {
|
|
||||||
kind: "user",
|
|
||||||
source: "system",
|
|
||||||
text: value,
|
|
||||||
phase: "start",
|
|
||||||
messageID,
|
|
||||||
})
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const childTool = (child: ChildState, item: SessionMessageAssistantTool, messageID: string) => {
|
|
||||||
const part = legacyTool({
|
|
||||||
sessionID: child.sessionID,
|
|
||||||
messageID,
|
|
||||||
tool: item,
|
|
||||||
})
|
|
||||||
if (item.state.status === "pending") return
|
|
||||||
child.callIDs.add(item.id)
|
|
||||||
if (item.state.status === "running") {
|
|
||||||
setFrame(child, `tool:${item.id}`, toolCommit(part, "start"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
child.finishedTools.add(item.id)
|
|
||||||
child.tools.delete(item.id)
|
|
||||||
setFrame(child, `tool:${item.id}`, toolCommit(part, "final"))
|
|
||||||
}
|
|
||||||
|
|
||||||
const rebuild = (child: ChildState, messages: SessionMessage[]) => {
|
|
||||||
child.frames = []
|
|
||||||
child.text.clear()
|
|
||||||
child.projectedText.clear()
|
|
||||||
child.reasoning.clear()
|
|
||||||
child.projectedReasoning.clear()
|
|
||||||
child.finishedTools.clear()
|
|
||||||
child.messageIDs.clear()
|
|
||||||
child.callIDs.clear()
|
|
||||||
for (const message of messages) {
|
|
||||||
if (message.type === "user") {
|
|
||||||
child.prompts.delete(message.id)
|
|
||||||
userFrame(child, message.id, message.text)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (message.type !== "assistant") continue
|
|
||||||
child.messageIDs.add(message.id)
|
|
||||||
let textOrdinal = 0
|
|
||||||
let reasoningOrdinal = 0
|
|
||||||
for (const item of message.content) {
|
|
||||||
if (item.type === "text") {
|
|
||||||
const id = `text:${textOrdinal++}`
|
|
||||||
const key = fragmentKey(message.id, id)
|
|
||||||
child.text.set(key, item.text)
|
|
||||||
child.projectedText.set(key, item.text)
|
|
||||||
setFrame(child, key, {
|
|
||||||
kind: "assistant",
|
|
||||||
source: "assistant",
|
|
||||||
text: item.text,
|
|
||||||
phase: "progress",
|
|
||||||
messageID: message.id,
|
|
||||||
partID: id,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (item.type === "reasoning") {
|
|
||||||
const id = `reasoning:${reasoningOrdinal++}`
|
|
||||||
const key = fragmentKey(message.id, id)
|
|
||||||
child.reasoning.set(key, item.text)
|
|
||||||
child.projectedReasoning.set(key, item.text)
|
|
||||||
if (input.thinking)
|
|
||||||
setFrame(child, key, {
|
|
||||||
kind: "reasoning",
|
|
||||||
source: "reasoning",
|
|
||||||
text: `Thinking: ${item.text}`,
|
|
||||||
phase: "progress",
|
|
||||||
messageID: message.id,
|
|
||||||
partID: id,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
childTool(child, item, message.id)
|
|
||||||
}
|
|
||||||
if (message.error) {
|
|
||||||
setFrame(child, `error:${message.id}`, {
|
|
||||||
kind: "error",
|
|
||||||
source: "system",
|
|
||||||
text: message.error.message,
|
|
||||||
phase: "start",
|
|
||||||
messageID: message.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hydrateChild = (child: ChildState): Promise<void> => {
|
|
||||||
const existing = hydrations.get(child.sessionID)
|
|
||||||
if (existing) return existing
|
|
||||||
const pendingPrompts = new Map(child.prompts)
|
|
||||||
const pendingTools = new Map(child.tools)
|
|
||||||
let retry = false
|
|
||||||
const task = input.sdk.message
|
|
||||||
.list({ sessionID: child.sessionID, limit: CHILD_MESSAGE_LIMIT, order: "desc" })
|
|
||||||
.then((response) => {
|
|
||||||
const buffered = hydrationEvents.get(child.sessionID) ?? []
|
|
||||||
hydrationEvents.delete(child.sessionID)
|
|
||||||
if (hydrationOverflow.delete(child.sessionID)) {
|
|
||||||
child.hydrated = false
|
|
||||||
retry = true
|
|
||||||
notifyDetail(child)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for (const [id, prompt] of pendingPrompts) {
|
|
||||||
if (!child.prompts.has(id)) child.prompts.set(id, prompt)
|
|
||||||
}
|
|
||||||
rebuild(child, structuredClone(response.data).toReversed() as SessionMessage[])
|
|
||||||
for (const [id, tool] of pendingTools) {
|
|
||||||
if (!child.finishedTools.has(id) && !child.tools.has(id)) child.tools.set(id, tool)
|
|
||||||
}
|
|
||||||
for (const event of buffered) reduce(child, event)
|
|
||||||
child.hydrated = true
|
|
||||||
notifyDetail(child)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
hydrationEvents.delete(child.sessionID)
|
|
||||||
hydrationOverflow.delete(child.sessionID)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
hydrations.delete(child.sessionID)
|
|
||||||
if (retry) queueMicrotask(() => void hydrateChild(child))
|
|
||||||
})
|
|
||||||
hydrations.set(child.sessionID, task)
|
|
||||||
return task
|
|
||||||
}
|
|
||||||
|
|
||||||
const discover = (sessionID: string) => {
|
|
||||||
if (checked.has(sessionID) || children.has(sessionID) || sessionID === input.sessionID) return
|
|
||||||
checked.add(sessionID)
|
|
||||||
if (!pendingEvents.has(sessionID)) pendingEvents.set(sessionID, [])
|
|
||||||
void input.sdk.session
|
|
||||||
.get({ sessionID })
|
|
||||||
.then((session) => {
|
|
||||||
const buffered = pendingEvents.get(sessionID) ?? []
|
|
||||||
pendingEvents.delete(sessionID)
|
|
||||||
if (session.parentID !== input.sessionID) return
|
|
||||||
const child = ensureChild(sessionID)
|
|
||||||
if (session.agent) child.label = Locale.titlecase(session.agent)
|
|
||||||
child.title = session.title
|
|
||||||
for (const event of buffered) reduce(child, event)
|
|
||||||
touch(child)
|
|
||||||
input.emit()
|
|
||||||
void hydrateChild(child)
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// Allow a later event to retry discovery after transient failures.
|
|
||||||
pendingEvents.delete(sessionID)
|
|
||||||
checked.delete(sessionID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const reduce = (child: ChildState, event: V2Event) => {
|
|
||||||
if (event.type === "session.prompt.admitted") {
|
|
||||||
child.prompts.set(event.data.inputID, event.data.prompt.text)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.prompt.promoted") {
|
|
||||||
const prompt = child.prompts.get(event.data.inputID) ?? ""
|
|
||||||
child.prompts.delete(event.data.inputID)
|
|
||||||
if (userFrame(child, event.data.inputID, prompt)) {
|
|
||||||
touch(child, event.created)
|
|
||||||
notifyDetail(child)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.step.started") {
|
|
||||||
touch(child, event.created)
|
|
||||||
if (child.label === FALLBACK_LABEL && event.data.agent) child.label = Locale.titlecase(event.data.agent)
|
|
||||||
if (child.status !== "running") child.status = "running"
|
|
||||||
input.emit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.text.started") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.text.delta") {
|
|
||||||
const id = `text:${event.data.ordinal}`
|
|
||||||
const key = fragmentKey(event.data.assistantMessageID, id)
|
|
||||||
const projected = child.projectedText.get(key)
|
|
||||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
|
||||||
if (projected && covered >= 0) {
|
|
||||||
child.projectedText.set(key, projected.slice(covered + event.data.delta.length))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const next = (child.text.get(key) ?? "") + event.data.delta
|
|
||||||
child.text.set(key, next)
|
|
||||||
setFrame(child, key, {
|
|
||||||
kind: "assistant",
|
|
||||||
source: "assistant",
|
|
||||||
text: next,
|
|
||||||
phase: "progress",
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
partID: id,
|
|
||||||
})
|
|
||||||
touch(child, event.created)
|
|
||||||
notifyDetail(child)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.text.ended") {
|
|
||||||
const id = `text:${event.data.ordinal}`
|
|
||||||
const key = fragmentKey(event.data.assistantMessageID, id)
|
|
||||||
child.text.set(key, event.data.text)
|
|
||||||
child.projectedText.delete(key)
|
|
||||||
setFrame(child, key, {
|
|
||||||
kind: "assistant",
|
|
||||||
source: "assistant",
|
|
||||||
text: event.data.text,
|
|
||||||
phase: "progress",
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
partID: id,
|
|
||||||
})
|
|
||||||
touch(child, event.created)
|
|
||||||
notifyDetail(child)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.reasoning.started") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.reasoning.delta") {
|
|
||||||
const id = `reasoning:${event.data.ordinal}`
|
|
||||||
const key = fragmentKey(event.data.assistantMessageID, id)
|
|
||||||
const projected = child.projectedReasoning.get(key)
|
|
||||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
|
||||||
if (projected && covered >= 0) {
|
|
||||||
child.projectedReasoning.set(key, projected.slice(covered + event.data.delta.length))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const next = (child.reasoning.get(key) ?? "") + event.data.delta
|
|
||||||
child.reasoning.set(key, next)
|
|
||||||
if (!input.thinking) return
|
|
||||||
setFrame(child, key, {
|
|
||||||
kind: "reasoning",
|
|
||||||
source: "reasoning",
|
|
||||||
text: `Thinking: ${next}`,
|
|
||||||
phase: "progress",
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
partID: id,
|
|
||||||
})
|
|
||||||
notifyDetail(child)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.reasoning.ended") {
|
|
||||||
const id = `reasoning:${event.data.ordinal}`
|
|
||||||
const key = fragmentKey(event.data.assistantMessageID, id)
|
|
||||||
child.reasoning.set(key, event.data.text)
|
|
||||||
child.projectedReasoning.delete(key)
|
|
||||||
if (!input.thinking) return
|
|
||||||
setFrame(child, key, {
|
|
||||||
kind: "reasoning",
|
|
||||||
source: "reasoning",
|
|
||||||
text: `Thinking: ${event.data.text}`,
|
|
||||||
phase: "progress",
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
partID: id,
|
|
||||||
})
|
|
||||||
notifyDetail(child)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.tool.input.started") {
|
|
||||||
if (child.finishedTools.has(event.data.callID)) return
|
|
||||||
child.tools.set(event.data.callID, { name: event.data.name, input: {}, started: event.created })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.tool.called") {
|
|
||||||
if (child.finishedTools.has(event.data.callID)) return
|
|
||||||
const current = child.tools.get(event.data.callID)
|
|
||||||
child.tools.set(event.data.callID, {
|
|
||||||
name: current?.name ?? "tool",
|
|
||||||
input: event.data.input,
|
|
||||||
started: current?.started ?? event.created,
|
|
||||||
providerState: event.data.state,
|
|
||||||
})
|
|
||||||
childTool(
|
|
||||||
child,
|
|
||||||
structuredClone({
|
|
||||||
type: "tool",
|
|
||||||
id: event.data.callID,
|
|
||||||
name: current?.name ?? "tool",
|
|
||||||
executed: event.data.executed,
|
|
||||||
providerState: event.data.state,
|
|
||||||
state: { status: "running", input: event.data.input, structured: {}, content: [] },
|
|
||||||
time: { created: current?.started ?? event.created, ran: event.created },
|
|
||||||
}) as SessionMessageAssistantTool,
|
|
||||||
event.data.assistantMessageID,
|
|
||||||
)
|
|
||||||
touch(child, event.created)
|
|
||||||
notifyDetail(child)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.tool.success" || event.type === "session.tool.failed") {
|
|
||||||
if (child.finishedTools.has(event.data.callID)) return
|
|
||||||
const current = child.tools.get(event.data.callID)
|
|
||||||
const failed = event.type === "session.tool.failed"
|
|
||||||
childTool(
|
|
||||||
child,
|
|
||||||
structuredClone({
|
|
||||||
type: "tool",
|
|
||||||
id: event.data.callID,
|
|
||||||
name: current?.name ?? "tool",
|
|
||||||
executed: event.data.executed,
|
|
||||||
providerState: current?.providerState,
|
|
||||||
providerResultState: event.data.resultState,
|
|
||||||
state: failed
|
|
||||||
? {
|
|
||||||
status: "error",
|
|
||||||
input: current?.input ?? {},
|
|
||||||
structured: {},
|
|
||||||
content: [],
|
|
||||||
error: event.data.error,
|
|
||||||
result: event.data.result,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
status: "completed",
|
|
||||||
input: current?.input ?? {},
|
|
||||||
structured: event.data.structured,
|
|
||||||
content: event.data.content,
|
|
||||||
outputPaths: event.data.outputPaths,
|
|
||||||
result: event.data.result,
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
created: current?.started ?? event.created,
|
|
||||||
ran: current?.started,
|
|
||||||
completed: event.created,
|
|
||||||
},
|
|
||||||
}) as SessionMessageAssistantTool,
|
|
||||||
event.data.assistantMessageID,
|
|
||||||
)
|
|
||||||
touch(child, event.created)
|
|
||||||
notifyDetail(child)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.step.ended") return
|
|
||||||
if (event.type === "session.step.failed") {
|
|
||||||
setFrame(child, `error:step:${event.data.assistantMessageID}`, {
|
|
||||||
kind: "error",
|
|
||||||
source: "system",
|
|
||||||
text: event.data.error.message,
|
|
||||||
phase: "start",
|
|
||||||
messageID: event.data.assistantMessageID,
|
|
||||||
})
|
|
||||||
touch(child, event.created)
|
|
||||||
notifyDetail(child)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.execution.started") {
|
|
||||||
child.status = "running"
|
|
||||||
touch(child, event.created)
|
|
||||||
input.emit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
event.type === "session.execution.succeeded" ||
|
|
||||||
event.type === "session.execution.failed" ||
|
|
||||||
event.type === "session.execution.interrupted"
|
|
||||||
) {
|
|
||||||
child.status =
|
|
||||||
event.type === "session.execution.succeeded"
|
|
||||||
? "completed"
|
|
||||||
: event.type === "session.execution.interrupted"
|
|
||||||
? "cancelled"
|
|
||||||
: "error"
|
|
||||||
touch(child, event.created)
|
|
||||||
input.emit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainTool = (item: SessionMessageAssistantTool, active?: Record<string, unknown>) => {
|
|
||||||
if (item.name !== "subagent" || item.state.status !== "completed") return
|
|
||||||
const found = childSessionID(record(item.state.structured))
|
|
||||||
if (!found) return
|
|
||||||
const child = ensureChild(found.sessionID)
|
|
||||||
applyMeta(child, record(item.state.input))
|
|
||||||
if (found.running) child.background = true
|
|
||||||
if (child.status === "running") {
|
|
||||||
const running = found.running && (!active || found.sessionID in active)
|
|
||||||
child.status = running ? "running" : "completed"
|
|
||||||
}
|
|
||||||
touch(child, item.time.completed ?? item.time.created)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
main(event) {
|
|
||||||
if (event.type === "session.tool.input.started") {
|
|
||||||
if (event.data.name === "subagent") pendingCalls.set(event.data.callID, {})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.tool.called") {
|
|
||||||
if (pendingCalls.has(event.data.callID)) pendingCalls.set(event.data.callID, event.data.input)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type === "session.tool.failed") {
|
|
||||||
pendingCalls.delete(event.data.callID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (event.type !== "session.tool.success") return
|
|
||||||
const pending = pendingCalls.get(event.data.callID)
|
|
||||||
pendingCalls.delete(event.data.callID)
|
|
||||||
const found = childSessionID(record(event.data.structured))
|
|
||||||
if (!found) return
|
|
||||||
const child = ensureChild(found.sessionID)
|
|
||||||
applyMeta(child, pending)
|
|
||||||
if (found.running) {
|
|
||||||
child.background = true
|
|
||||||
child.status = "running"
|
|
||||||
}
|
|
||||||
if (!found.running && child.status === "running") child.status = "completed"
|
|
||||||
touch(child, event.created)
|
|
||||||
input.emit()
|
|
||||||
if (!child.hydrated) void hydrateChild(child)
|
|
||||||
},
|
|
||||||
foreign(sessionID, event) {
|
|
||||||
const child = children.get(sessionID)
|
|
||||||
if (child) {
|
|
||||||
if (hydrations.has(sessionID)) {
|
|
||||||
const buffered = hydrationEvents.get(sessionID) ?? []
|
|
||||||
if (buffered.length < CHILD_EVENT_BUFFER_LIMIT) buffered.push(event)
|
|
||||||
else hydrationOverflow.add(sessionID)
|
|
||||||
hydrationEvents.set(sessionID, buffered)
|
|
||||||
}
|
|
||||||
reduce(child, event)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
discover(sessionID)
|
|
||||||
const buffered = pendingEvents.get(sessionID)
|
|
||||||
if (buffered && buffered.length < CHILD_EVENT_BUFFER_LIMIT) buffered.push(event)
|
|
||||||
},
|
|
||||||
async hydrate(next) {
|
|
||||||
for (const message of next.messages) {
|
|
||||||
if (message.type !== "assistant") continue
|
|
||||||
for (const item of message.content) {
|
|
||||||
if (item.type === "tool") mainTool(item, next.active)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Family index: adopt children directly from the current session list so
|
|
||||||
// historical subagents beyond the projected message window still get tabs.
|
|
||||||
const family = await input.sdk.session
|
|
||||||
.list({ parentID: input.sessionID, limit: FAMILY_LIST_LIMIT, order: "desc" })
|
|
||||||
.then((response) => response.data)
|
|
||||||
.catch(() => [])
|
|
||||||
for (const session of family) {
|
|
||||||
const child = ensureChild(session.id)
|
|
||||||
if (session.agent && child.label === FALLBACK_LABEL) child.label = Locale.titlecase(session.agent)
|
|
||||||
if (!child.title) child.title = session.title
|
|
||||||
touch(child, session.time.updated)
|
|
||||||
}
|
|
||||||
for (const sessionID of Object.keys(next.active)) discover(sessionID)
|
|
||||||
for (const child of children.values()) {
|
|
||||||
// Reconnect can miss a child's settled event; the active map is the
|
|
||||||
// authoritative live signal for still-running children.
|
|
||||||
if (child.status === "running" && !(child.sessionID in next.active)) child.status = "completed"
|
|
||||||
}
|
|
||||||
const current = selected ? children.get(selected) : undefined
|
|
||||||
if (current) await hydrateChild(current)
|
|
||||||
if (children.size > 0) input.emit()
|
|
||||||
},
|
|
||||||
select(sessionID) {
|
|
||||||
selected = sessionID
|
|
||||||
const child = sessionID ? children.get(sessionID) : undefined
|
|
||||||
if (child && !child.hydrated) void hydrateChild(child)
|
|
||||||
input.emit()
|
|
||||||
},
|
|
||||||
snapshot() {
|
|
||||||
const tabs = [...children.values()].map(tab).toSorted((a, b) => {
|
|
||||||
const active = Number(b.status === "running") - Number(a.status === "running")
|
|
||||||
if (active !== 0) return active
|
|
||||||
return b.lastUpdatedAt - a.lastUpdatedAt
|
|
||||||
})
|
|
||||||
const child = selected ? children.get(selected) : undefined
|
|
||||||
const details: Record<string, FooterSubagentDetail> = child
|
|
||||||
? { [child.sessionID]: { sessionID: child.sessionID, commits: child.frames.map((item) => item.commit) } }
|
|
||||||
: {}
|
|
||||||
return { tabs, details, permissions: [], questions: [] }
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
|||||||
import type { StreamCommit } from "./types"
|
|
||||||
|
|
||||||
export function turnSummaryCommit(input: {
|
|
||||||
agent: string
|
|
||||||
model: string
|
|
||||||
duration: string
|
|
||||||
messageID?: string
|
|
||||||
}): StreamCommit {
|
|
||||||
return {
|
|
||||||
kind: "system",
|
|
||||||
text: `${input.agent} · ${input.model} · ${input.duration}`,
|
|
||||||
phase: "final",
|
|
||||||
source: "system",
|
|
||||||
summary: {
|
|
||||||
agent: input.agent,
|
|
||||||
model: input.model,
|
|
||||||
duration: input.duration,
|
|
||||||
},
|
|
||||||
messageID: input.messageID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { EOL } from "node:os"
|
|
||||||
|
|
||||||
export const Style = {
|
|
||||||
TEXT_DIM: "\x1b[90m",
|
|
||||||
TEXT_NORMAL: "\x1b[0m",
|
|
||||||
TEXT_WARNING_BOLD: "\x1b[93m\x1b[1m",
|
|
||||||
TEXT_DANGER_BOLD: "\x1b[91m\x1b[1m",
|
|
||||||
}
|
|
||||||
|
|
||||||
export function println(...message: string[]) {
|
|
||||||
process.stderr.write(message.join(" ") + EOL)
|
|
||||||
}
|
|
||||||
|
|
||||||
let blank = false
|
|
||||||
|
|
||||||
export function empty() {
|
|
||||||
if (blank) return
|
|
||||||
println(Style.TEXT_NORMAL)
|
|
||||||
blank = true
|
|
||||||
}
|
|
||||||
|
|
||||||
export function error(message: string) {
|
|
||||||
if (message.startsWith("Error: ")) message = message.slice("Error: ".length)
|
|
||||||
println(Style.TEXT_DANGER_BOLD + "Error: " + Style.TEXT_NORMAL + message)
|
|
||||||
}
|
|
||||||
|
|
||||||
export * as UI from "./ui"
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
export * as ServerProcess from "./server-process"
|
|
||||||
|
|
||||||
import { NodeServices } from "@effect/platform-node"
|
|
||||||
import { Service } from "@opencode-ai/client/effect"
|
|
||||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
||||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|
||||||
import { Global } from "@opencode-ai/core/global"
|
|
||||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
|
||||||
import { AppProcess } from "@opencode-ai/core/process"
|
|
||||||
import { Flock } from "@opencode-ai/core/util/flock"
|
|
||||||
import { start } from "@opencode-ai/server/process"
|
|
||||||
import { randomBytes, randomUUID } from "node:crypto"
|
|
||||||
import path from "node:path"
|
|
||||||
import { Effect, FileSystem, Logger, Option, Redacted, Schedule, Schema } from "effect"
|
|
||||||
import { HttpServer } from "effect/unstable/http"
|
|
||||||
import { CodeModeHost } from "./code-mode"
|
|
||||||
import { Env } from "./env"
|
|
||||||
import { ServiceConfig } from "./services/service-config"
|
|
||||||
import { Updater } from "./services/updater"
|
|
||||||
|
|
||||||
export type Mode = "default" | "service" | "stdio"
|
|
||||||
|
|
||||||
export type Options = {
|
|
||||||
readonly mode: Mode
|
|
||||||
readonly hostname?: string
|
|
||||||
readonly port?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const run = Effect.fn("cli.server-process.run")((options: Options) =>
|
|
||||||
processEffect(options).pipe(
|
|
||||||
Effect.provide(Updater.layer),
|
|
||||||
Effect.provide(AppNodeBuilder.build(LayerNode.group([Global.node, AppProcess.node]))),
|
|
||||||
Effect.provide(NodeServices.layer),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const processEffect = Effect.fnUntraced(function* (options: Options) {
|
|
||||||
if (options.mode === "service") yield* Effect.sync(() => process.chdir(Global.Path.home))
|
|
||||||
return yield* Effect.scoped(
|
|
||||||
Effect.gen(function* () {
|
|
||||||
if (options.mode === "service") {
|
|
||||||
const service = yield* ServiceConfig.options()
|
|
||||||
yield* Flock.effect(path.basename(service.file, ".json") + "-process", {
|
|
||||||
dir: path.dirname(service.file),
|
|
||||||
staleMs: 3_000,
|
|
||||||
timeoutMs: 15_000,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const environmentPassword = yield* Env.password
|
|
||||||
// Keep the lease credential out of the environment inherited by tools.
|
|
||||||
if (options.mode === "stdio") {
|
|
||||||
delete process.env.OPENCODE_PASSWORD
|
|
||||||
delete process.env.OPENCODE_SERVER_PASSWORD
|
|
||||||
}
|
|
||||||
const config = options.mode === "service" ? yield* ServiceConfig.read() : {}
|
|
||||||
const password =
|
|
||||||
options.mode === "service"
|
|
||||||
? yield* ServiceConfig.password()
|
|
||||||
: environmentPassword
|
|
||||||
? Redacted.value(environmentPassword)
|
|
||||||
: randomBytes(32).toString("base64url")
|
|
||||||
if (!password) return yield* Effect.fail(new Error("Missing server password"))
|
|
||||||
const address = yield* start({
|
|
||||||
hostname: options.hostname ?? config.hostname ?? "127.0.0.1",
|
|
||||||
port: Option.fromNullishOr(options.port ?? config.port),
|
|
||||||
password,
|
|
||||||
replacements: (server) => CodeModeHost.replacements(server, password),
|
|
||||||
}).pipe(Effect.provide(Logger.layer([], { mergeWithExisting: false })))
|
|
||||||
if (options.mode === "service") yield* register(address, password)
|
|
||||||
const url = HttpServer.formatAddress(address)
|
|
||||||
console.log(options.mode === "stdio" ? JSON.stringify({ url }) : `server listening on ${url}`)
|
|
||||||
if (options.mode === "default" && !environmentPassword) console.log(`server password ${password}`)
|
|
||||||
const updater = yield* Updater.Service
|
|
||||||
yield* updater.check().pipe(Effect.schedule(Schedule.spaced("10 minutes")), Effect.forkScoped)
|
|
||||||
return yield* options.mode === "stdio" ? waitForStdinClose() : Effect.never
|
|
||||||
}).pipe(Effect.annotateLogs({ role: "server" })),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
// The latest atomic registration wins. A displaced process notices the new id,
|
|
||||||
// exits, and cannot remove its successor's registration from its finalizer.
|
|
||||||
const infoJson = Schema.fromJsonString(Service.Info)
|
|
||||||
const encodeInfo = Schema.encodeEffect(infoJson)
|
|
||||||
const decodeInfo = Schema.decodeUnknownEffect(infoJson)
|
|
||||||
|
|
||||||
const register = Effect.fnUntraced(function* (address: HttpServer.Address, password: string) {
|
|
||||||
const fs = yield* FileSystem.FileSystem
|
|
||||||
const options = yield* ServiceConfig.options()
|
|
||||||
const id = randomUUID()
|
|
||||||
const temp = options.file + "." + id + ".tmp"
|
|
||||||
yield* fs.makeDirectory(path.dirname(options.file), { recursive: true })
|
|
||||||
const encoded = yield* encodeInfo({
|
|
||||||
id,
|
|
||||||
version: InstallationVersion,
|
|
||||||
url: HttpServer.formatAddress(address),
|
|
||||||
pid: process.pid,
|
|
||||||
password,
|
|
||||||
})
|
|
||||||
yield* fs.writeFileString(temp, encoded, { mode: 0o600 })
|
|
||||||
yield* fs.rename(temp, options.file)
|
|
||||||
const currentID = fs.readFileString(options.file).pipe(
|
|
||||||
Effect.flatMap(decodeInfo),
|
|
||||||
Effect.map((info) => info.id),
|
|
||||||
Effect.orElseSucceed(() => undefined),
|
|
||||||
)
|
|
||||||
yield* currentID.pipe(
|
|
||||||
Effect.flatMap((current) =>
|
|
||||||
current === id
|
|
||||||
? Effect.void
|
|
||||||
: Effect.try({ try: () => process.kill(process.pid, "SIGTERM"), catch: (cause) => cause }).pipe(Effect.ignore),
|
|
||||||
),
|
|
||||||
Effect.repeat(Schedule.spaced("10 seconds")),
|
|
||||||
Effect.forkScoped,
|
|
||||||
)
|
|
||||||
yield* Effect.addFinalizer(() =>
|
|
||||||
currentID.pipe(
|
|
||||||
Effect.flatMap((current) => (current === id ? fs.remove(options.file) : Effect.void)),
|
|
||||||
Effect.ignore,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
function waitForStdinClose() {
|
|
||||||
return Effect.callback<void>((resume) => {
|
|
||||||
const close = () => resume(Effect.void)
|
|
||||||
process.stdin.once("end", close)
|
|
||||||
process.stdin.once("close", close)
|
|
||||||
process.stdin.resume()
|
|
||||||
if (process.stdin.readableEnded || process.stdin.destroyed) close()
|
|
||||||
return Effect.sync(() => {
|
|
||||||
process.stdin.off("end", close)
|
|
||||||
process.stdin.off("close", close)
|
|
||||||
process.stdin.pause()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import { Global } from "@opencode-ai/core/global"
|
||||||
|
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||||
|
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||||
|
import { ServerAuth } from "@opencode-ai/server/auth"
|
||||||
|
import { Context, Effect, FileSystem, Layer, Option, Schedule, Schema, Scope } from "effect"
|
||||||
|
import { HttpServer } from "effect/unstable/http"
|
||||||
|
import { randomBytes, randomUUID } from "crypto"
|
||||||
|
import { spawn } from "node:child_process"
|
||||||
|
import path from "path"
|
||||||
|
|
||||||
|
export interface Interface {
|
||||||
|
readonly client: () => Effect.Effect<ReturnType<typeof createOpencodeClient>, unknown>
|
||||||
|
readonly transport: () => Effect.Effect<{ url: string; headers: RequestInit["headers"] }, unknown>
|
||||||
|
readonly start: () => Effect.Effect<string, Error>
|
||||||
|
readonly status: () => Effect.Effect<string | undefined>
|
||||||
|
readonly stop: () => Effect.Effect<void, unknown>
|
||||||
|
readonly password: (value?: string) => Effect.Effect<string, unknown>
|
||||||
|
readonly register: (address: HttpServer.Address) => Effect.Effect<void, unknown, Scope.Scope>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Service extends Context.Service<Service, Interface>()("@opencode/cli/Daemon") {}
|
||||||
|
|
||||||
|
const Registration = Schema.Struct({
|
||||||
|
id: Schema.optional(Schema.String),
|
||||||
|
version: Schema.optional(Schema.String),
|
||||||
|
url: Schema.String,
|
||||||
|
pid: Schema.Int.check(Schema.isGreaterThan(0)),
|
||||||
|
})
|
||||||
|
type Registration = typeof Registration.Type
|
||||||
|
|
||||||
|
function sameRegistration(left: Registration, right: Registration) {
|
||||||
|
return left.id === right.id && left.version === right.version && left.url === right.url && left.pid === right.pid
|
||||||
|
}
|
||||||
|
|
||||||
|
export const layer = Layer.effect(
|
||||||
|
Service,
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const fs = yield* FileSystem.FileSystem
|
||||||
|
const directory = Global.Path.state
|
||||||
|
const file = path.join(directory, "server.json")
|
||||||
|
const passwordFile = path.join(directory, "password")
|
||||||
|
const decodeRegistration = Schema.decodeUnknownEffect(Schema.fromJsonString(Registration))
|
||||||
|
|
||||||
|
const password = Effect.fn("cli.daemon.password")(function* (value?: string) {
|
||||||
|
const existing = yield* fs.readFileString(passwordFile).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||||
|
if (value === undefined && existing) return existing
|
||||||
|
|
||||||
|
// Keep one private credential across server restarts so discovered clients
|
||||||
|
// can reconnect without exposing a password flag or environment variable.
|
||||||
|
const generated = value ?? randomBytes(32).toString("base64url")
|
||||||
|
const temp = passwordFile + ".tmp"
|
||||||
|
yield* fs.makeDirectory(directory, { recursive: true })
|
||||||
|
yield* fs.writeFileString(temp, generated, { mode: 0o600 })
|
||||||
|
yield* fs.rename(temp, passwordFile)
|
||||||
|
return generated
|
||||||
|
})
|
||||||
|
|
||||||
|
const registration = Effect.fnUntraced(function* () {
|
||||||
|
return yield* fs.readFileString(file).pipe(Effect.flatMap(decodeRegistration))
|
||||||
|
})
|
||||||
|
|
||||||
|
const createClient = Effect.fnUntraced(function* (url: string) {
|
||||||
|
return createOpencodeClient({ baseUrl: url, headers: ServerAuth.headers({ password: yield* password() }) })
|
||||||
|
})
|
||||||
|
|
||||||
|
const healthy = Effect.fnUntraced(function* () {
|
||||||
|
const info = yield* registration()
|
||||||
|
const client = yield* createClient(info.url)
|
||||||
|
const response = yield* Effect.tryPromise(() => client.v2.health.get({ signal: AbortSignal.timeout(2_000) }))
|
||||||
|
if (response.data?.healthy === true) return info
|
||||||
|
return yield* Effect.fail(new Error("Registered server is not healthy"))
|
||||||
|
})
|
||||||
|
|
||||||
|
const compatible = Effect.fnUntraced(function* () {
|
||||||
|
const info = yield* healthy()
|
||||||
|
if (info.version === InstallationVersion) return info
|
||||||
|
return yield* Effect.fail(new Error("Registered server version does not match the client"))
|
||||||
|
})
|
||||||
|
|
||||||
|
const signal = (pid: number, signal: NodeJS.Signals) =>
|
||||||
|
Effect.try({ try: () => process.kill(pid, signal), catch: (cause) => cause }).pipe(Effect.ignore)
|
||||||
|
|
||||||
|
const awaitStopped = Effect.fnUntraced(function* (pid: number) {
|
||||||
|
const running = yield* Effect.try({ try: () => process.kill(pid, 0), catch: () => false }).pipe(
|
||||||
|
Effect.orElseSucceed(() => false),
|
||||||
|
)
|
||||||
|
if (!running) return true
|
||||||
|
return yield* Effect.fail(new Error(`Server process ${pid} is still running`))
|
||||||
|
})
|
||||||
|
|
||||||
|
const stopProcess = Effect.fnUntraced(function* (info: Registration) {
|
||||||
|
const current = yield* healthy().pipe(Effect.option)
|
||||||
|
if (Option.isNone(current) || !sameRegistration(current.value, info)) return
|
||||||
|
|
||||||
|
yield* signal(info.pid, "SIGTERM")
|
||||||
|
const stopped = yield* awaitStopped(info.pid).pipe(
|
||||||
|
Effect.retry(Schedule.spaced("50 millis").pipe(Schedule.both(Schedule.recurs(100)))),
|
||||||
|
Effect.option,
|
||||||
|
)
|
||||||
|
if (Option.isSome(stopped)) return
|
||||||
|
|
||||||
|
const latest = yield* healthy().pipe(Effect.option)
|
||||||
|
if (Option.isNone(latest) || !sameRegistration(latest.value, info)) return
|
||||||
|
yield* signal(info.pid, "SIGKILL")
|
||||||
|
yield* awaitStopped(info.pid).pipe(
|
||||||
|
Effect.retry(Schedule.spaced("50 millis").pipe(Schedule.both(Schedule.recurs(100)))),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const start = Effect.fn("cli.daemon.start")(function* () {
|
||||||
|
const existing = yield* healthy().pipe(Effect.option)
|
||||||
|
const found = Option.getOrUndefined(existing)
|
||||||
|
const compiled = path.basename(process.execPath).replace(/\.exe$/, "") !== "bun"
|
||||||
|
if (found?.version === InstallationVersion && compiled) return found.url
|
||||||
|
if (found) yield* stopProcess(found).pipe(Effect.ignore)
|
||||||
|
|
||||||
|
const entrypoint = compiled ? undefined : process.argv[1]
|
||||||
|
if (!compiled && entrypoint === undefined)
|
||||||
|
return yield* Effect.fail(new Error("Failed to resolve CLI entrypoint"))
|
||||||
|
yield* Effect.try({
|
||||||
|
try: () => {
|
||||||
|
spawn(process.execPath, [...(entrypoint ? [entrypoint] : []), "serve", "--register"], {
|
||||||
|
detached: true,
|
||||||
|
stdio: "ignore",
|
||||||
|
}).unref()
|
||||||
|
},
|
||||||
|
catch: (cause) => new Error("Failed to start server", { cause }),
|
||||||
|
})
|
||||||
|
|
||||||
|
return yield* compatible().pipe(
|
||||||
|
Effect.retry(Schedule.spaced("50 millis").pipe(Schedule.both(Schedule.recurs(100)))),
|
||||||
|
Effect.map((info) => info.url),
|
||||||
|
Effect.mapError(() => new Error("Failed to start server")),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const transport = Effect.fn("cli.daemon.transport")(function* () {
|
||||||
|
return { url: yield* start(), headers: ServerAuth.headers({ password: yield* password() }) }
|
||||||
|
})
|
||||||
|
|
||||||
|
const client = Effect.fn("cli.daemon.client")(function* () {
|
||||||
|
const connection = yield* transport()
|
||||||
|
return createOpencodeClient({ baseUrl: connection.url, headers: connection.headers })
|
||||||
|
})
|
||||||
|
|
||||||
|
const status = Effect.fn("cli.daemon.status")(function* () {
|
||||||
|
const existing = yield* healthy().pipe(Effect.option)
|
||||||
|
const found = Option.getOrUndefined(existing)
|
||||||
|
if (found?.version === InstallationVersion) return found.url
|
||||||
|
if (found) return undefined
|
||||||
|
yield* fs.remove(file).pipe(Effect.ignore)
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const stop = Effect.fn("cli.daemon.stop")(function* () {
|
||||||
|
const existing = yield* healthy().pipe(Effect.option)
|
||||||
|
// A stale registration may point at a PID that has since been reused by
|
||||||
|
// another process. Only signal the PID after authenticating the server.
|
||||||
|
if (Option.isNone(existing)) return yield* fs.remove(file).pipe(Effect.ignore)
|
||||||
|
yield* stopProcess(existing.value)
|
||||||
|
yield* fs.remove(file).pipe(Effect.ignore)
|
||||||
|
})
|
||||||
|
|
||||||
|
const register = Effect.fn("cli.daemon.register")(function* (address: HttpServer.Address) {
|
||||||
|
const id = randomUUID()
|
||||||
|
const temp = file + "." + id + ".tmp"
|
||||||
|
yield* fs.makeDirectory(directory, { recursive: true })
|
||||||
|
yield* fs.writeFileString(
|
||||||
|
temp,
|
||||||
|
JSON.stringify({ id, version: InstallationVersion, url: HttpServer.formatAddress(address), pid: process.pid }),
|
||||||
|
{ mode: 0o600 },
|
||||||
|
)
|
||||||
|
yield* fs.rename(temp, file)
|
||||||
|
yield* registration().pipe(
|
||||||
|
Effect.flatMap((info) => (info.id === id ? Effect.void : signal(process.pid, "SIGTERM"))),
|
||||||
|
Effect.catch(() => signal(process.pid, "SIGTERM")),
|
||||||
|
Effect.repeat(Schedule.spaced("10 seconds")),
|
||||||
|
Effect.forkScoped,
|
||||||
|
)
|
||||||
|
yield* Effect.addFinalizer(() =>
|
||||||
|
registration().pipe(
|
||||||
|
Effect.flatMap((info) => (info.id === id ? fs.remove(file) : Effect.void)),
|
||||||
|
Effect.ignore,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Service.of({ client, transport, start, status, stop, password, register })
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
export * as Daemon from "./daemon"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user