mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-15 17:08:21 -04:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 11b14dff4d | |||
| c9db6e9a1f | |||
| 130038eb63 | |||
| 4438f69aac | |||
| 76ced5418f | |||
| c40e3e74a7 | |||
| 0317531906 | |||
| 5f241f1cc1 | |||
| b513fafe83 | |||
| 21c4c93770 | |||
| abc4b83f58 | |||
| cb562b2c62 | |||
| 2d2339d37d | |||
| 849c2598ab | |||
| 3033afba51 | |||
| 5a8ee27254 | |||
| 4cc022481c | |||
| d36a2d8981 | |||
| 96ff82a11f | |||
| 4872c48c23 | |||
| 47fc6f2991 | |||
| da312b009c | |||
| 70535a1bad | |||
| 43e472bba7 | |||
| b67fda133a | |||
| e2531dff0f | |||
| cc3615b535 | |||
| 5542415b64 | |||
| 8bec40a816 | |||
| 4a81e8392b | |||
| a19b52e85b | |||
| 9105f35966 | |||
| 3ba3954aa0 | |||
| 3fc5af6dd1 | |||
| 5586f9675e | |||
| 3b719aba48 | |||
| 20a3a2138e | |||
| cd46f22d51 | |||
| cfa134f042 | |||
| 61653a20a5 | |||
| 2a097f3af7 | |||
| ba4b8e21f4 | |||
| 7985c2066a | |||
| 67caf894e0 | |||
| 78587c141b | |||
| f5573281ca | |||
| 4817228f96 | |||
| 5a5117b36c | |||
| 6a288426b8 | |||
| 573ac85983 | |||
| 1221bbbe14 | |||
| 6bea0f4fa4 | |||
| cc34084dfe | |||
| b8142c7aa8 | |||
| ba3f966ed0 | |||
| b95fe7b2d7 | |||
| 9da9ed9de6 | |||
| fab2133129 | |||
| 901c9e7329 | |||
| 3476e6baa5 | |||
| 86e04d4174 |
@@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"packages/app": {
|
"packages/app": {
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@corvu/drawer": "catalog:",
|
"@corvu/drawer": "catalog:",
|
||||||
"@dnd-kit/abstract": "0.5.0",
|
"@dnd-kit/abstract": "0.5.0",
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
"diff": "catalog:",
|
"diff": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
"ghostty-web": "github:anomalyco/ghostty-web#513463a6f1190253057e8a3f0dac8f6ee8393553",
|
"ghostty-web": "github:anomalyco/ghostty-web#83c0a07b8628b748aed073b232cb4b52a6ca11c1",
|
||||||
"luxon": "catalog:",
|
"luxon": "catalog:",
|
||||||
"marked": "catalog:",
|
"marked": "catalog:",
|
||||||
"marked-shiki": "catalog:",
|
"marked-shiki": "catalog:",
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
},
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "@opencode-ai/cli",
|
"name": "@opencode-ai/cli",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"bin": {
|
"bin": {
|
||||||
"lildax": "./bin/lildax.cjs",
|
"lildax": "./bin/lildax.cjs",
|
||||||
},
|
},
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
},
|
},
|
||||||
"packages/codemode": {
|
"packages/codemode": {
|
||||||
"name": "@opencode-ai/codemode",
|
"name": "@opencode-ai/codemode",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "8.15.0",
|
"acorn": "8.15.0",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/app": {
|
"packages/console/app": {
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/core": {
|
"packages/console/core": {
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-sts": "3.782.0",
|
"@aws-sdk/client-sts": "3.782.0",
|
||||||
"@jsx-email/render": "1.1.1",
|
"@jsx-email/render": "1.1.1",
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/function": {
|
"packages/console/function": {
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "3.0.82",
|
"@ai-sdk/anthropic": "3.0.82",
|
||||||
"@ai-sdk/openai": "3.0.48",
|
"@ai-sdk/openai": "3.0.48",
|
||||||
@@ -242,7 +242,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/mail": {
|
"packages/console/mail": {
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
},
|
},
|
||||||
"packages/console/support": {
|
"packages/console/support": {
|
||||||
"name": "@opencode-ai/console-support",
|
"name": "@opencode-ai/console-support",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cloudflare/vite-plugin": "1.15.2",
|
"@cloudflare/vite-plugin": "1.15.2",
|
||||||
"@opencode-ai/console-core": "workspace:*",
|
"@opencode-ai/console-core": "workspace:*",
|
||||||
@@ -286,7 +286,7 @@
|
|||||||
},
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@opencode-ai/core",
|
"name": "@opencode-ai/core",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -380,7 +380,7 @@
|
|||||||
},
|
},
|
||||||
"packages/desktop": {
|
"packages/desktop": {
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@zip.js/zip.js": "2.7.62",
|
"@zip.js/zip.js": "2.7.62",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -434,7 +434,7 @@
|
|||||||
},
|
},
|
||||||
"packages/effect-drizzle-sqlite": {
|
"packages/effect-drizzle-sqlite": {
|
||||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-orm": "catalog:",
|
"drizzle-orm": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
@@ -448,7 +448,7 @@
|
|||||||
},
|
},
|
||||||
"packages/effect-sqlite-node": {
|
"packages/effect-sqlite-node": {
|
||||||
"name": "@opencode-ai/effect-sqlite-node",
|
"name": "@opencode-ai/effect-sqlite-node",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -460,7 +460,7 @@
|
|||||||
},
|
},
|
||||||
"packages/enterprise": {
|
"packages/enterprise": {
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/standard-validator": "catalog:",
|
"@hono/standard-validator": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
@@ -492,7 +492,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-app": "8.0.1",
|
"@octokit/auth-app": "8.0.1",
|
||||||
"@octokit/rest": "catalog:",
|
"@octokit/rest": "catalog:",
|
||||||
@@ -508,7 +508,7 @@
|
|||||||
},
|
},
|
||||||
"packages/http-recorder": {
|
"packages/http-recorder": {
|
||||||
"name": "@opencode-ai/http-recorder",
|
"name": "@opencode-ai/http-recorder",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/platform-node": "4.0.0-beta.83",
|
"@effect/platform-node": "4.0.0-beta.83",
|
||||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||||
@@ -539,7 +539,7 @@
|
|||||||
},
|
},
|
||||||
"packages/llm": {
|
"packages/llm": {
|
||||||
"name": "@opencode-ai/llm",
|
"name": "@opencode-ai/llm",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/schema": "workspace:*",
|
"@opencode-ai/schema": "workspace:*",
|
||||||
"@smithy/eventstream-codec": "4.2.14",
|
"@smithy/eventstream-codec": "4.2.14",
|
||||||
@@ -558,7 +558,7 @@
|
|||||||
},
|
},
|
||||||
"packages/opencode": {
|
"packages/opencode": {
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"bin": {
|
"bin": {
|
||||||
"opencode": "./bin/opencode",
|
"opencode": "./bin/opencode",
|
||||||
},
|
},
|
||||||
@@ -689,7 +689,7 @@
|
|||||||
},
|
},
|
||||||
"packages/plugin": {
|
"packages/plugin": {
|
||||||
"name": "@opencode-ai/plugin",
|
"name": "@opencode-ai/plugin",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "3.0.8",
|
"@ai-sdk/provider": "3.0.8",
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
@@ -765,7 +765,7 @@
|
|||||||
},
|
},
|
||||||
"packages/sdk/js": {
|
"packages/sdk/js": {
|
||||||
"name": "@opencode-ai/sdk",
|
"name": "@opencode-ai/sdk",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "catalog:",
|
"cross-spawn": "catalog:",
|
||||||
},
|
},
|
||||||
@@ -780,7 +780,7 @@
|
|||||||
},
|
},
|
||||||
"packages/server": {
|
"packages/server": {
|
||||||
"name": "@opencode-ai/server",
|
"name": "@opencode-ai/server",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/protocol": "workspace:*",
|
"@opencode-ai/protocol": "workspace:*",
|
||||||
@@ -795,7 +795,7 @@
|
|||||||
},
|
},
|
||||||
"packages/session-ui": {
|
"packages/session-ui": {
|
||||||
"name": "@opencode-ai/session-ui",
|
"name": "@opencode-ai/session-ui",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
@@ -839,7 +839,7 @@
|
|||||||
},
|
},
|
||||||
"packages/slack": {
|
"packages/slack": {
|
||||||
"name": "@opencode-ai/slack",
|
"name": "@opencode-ai/slack",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/sdk": "workspace:*",
|
"@opencode-ai/sdk": "workspace:*",
|
||||||
"@slack/bolt": "^3.17.1",
|
"@slack/bolt": "^3.17.1",
|
||||||
@@ -852,7 +852,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/app": {
|
"packages/stats/app": {
|
||||||
"name": "@opencode-ai/stats-app",
|
"name": "@opencode-ai/stats-app",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ibm/plex": "6.4.1",
|
"@ibm/plex": "6.4.1",
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
@@ -886,7 +886,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/core": {
|
"packages/stats/core": {
|
||||||
"name": "@opencode-ai/stats-core",
|
"name": "@opencode-ai/stats-core",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-athena": "3.933.0",
|
"@aws-sdk/client-athena": "3.933.0",
|
||||||
"@planetscale/database": "1.19.0",
|
"@planetscale/database": "1.19.0",
|
||||||
@@ -905,7 +905,7 @@
|
|||||||
},
|
},
|
||||||
"packages/stats/server": {
|
"packages/stats/server": {
|
||||||
"name": "@opencode-ai/stats-server",
|
"name": "@opencode-ai/stats-server",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-firehose": "3.933.0",
|
"@aws-sdk/client-firehose": "3.933.0",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
@@ -947,7 +947,7 @@
|
|||||||
},
|
},
|
||||||
"packages/tui": {
|
"packages/tui": {
|
||||||
"name": "@opencode-ai/tui",
|
"name": "@opencode-ai/tui",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opencode-ai/core": "workspace:*",
|
"@opencode-ai/core": "workspace:*",
|
||||||
"@opencode-ai/plugin": "workspace:*",
|
"@opencode-ai/plugin": "workspace:*",
|
||||||
@@ -974,7 +974,7 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@opencode-ai/ui",
|
"name": "@opencode-ai/ui",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "catalog:",
|
"@kobalte/core": "catalog:",
|
||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
@@ -1025,7 +1025,7 @@
|
|||||||
},
|
},
|
||||||
"packages/web": {
|
"packages/web": {
|
||||||
"name": "@opencode-ai/web",
|
"name": "@opencode-ai/web",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/cloudflare": "12.6.3",
|
"@astrojs/cloudflare": "12.6.3",
|
||||||
"@astrojs/markdown-remark": "6.3.1",
|
"@astrojs/markdown-remark": "6.3.1",
|
||||||
@@ -3845,7 +3845,7 @@
|
|||||||
|
|
||||||
"get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="],
|
"get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="],
|
||||||
|
|
||||||
"ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#513463a", {}, "anomalyco-ghostty-web-513463a", "sha512-GZR8LSmgGzViWnBJrqRI8MpAZRCJxhcr1Hi9Tyeh7YRooHZQjK9J97FQRD3tbBaM2wjq05gzGY2UEsG+JtZeBw=="],
|
"ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#83c0a07", {}, "anomalyco-ghostty-web-83c0a07", "sha512-Lf2v1agHkVUpMpHBWWuCZrhOEmcwwin5/Hboc9rZwQ7/CKkIh5rU1r1CvfLlhkMoFv+ed8z52RZ8hkzGZZj3MQ=="],
|
||||||
|
|
||||||
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
|
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,25 @@ export const zoneID = "430ba34c138cfb5360826c4909f99be8"
|
|||||||
export const awsStage = $app.stage === "production" ? "production" : "dev"
|
export const awsStage = $app.stage === "production" ? "production" : "dev"
|
||||||
export const deployAws = $app.stage === awsStage
|
export const deployAws = $app.stage === awsStage
|
||||||
|
|
||||||
|
if ($app.stage === "production") {
|
||||||
|
new cloudflare.DnsRecord("TrustCenter", {
|
||||||
|
zoneId: zoneID,
|
||||||
|
name: "trust.opencode.ai",
|
||||||
|
type: "CNAME",
|
||||||
|
content: "3a69a5bb27875189.vercel-dns-016.com",
|
||||||
|
proxied: false,
|
||||||
|
ttl: 60,
|
||||||
|
})
|
||||||
|
|
||||||
|
new cloudflare.DnsRecord("TrustCenterVerification", {
|
||||||
|
zoneId: zoneID,
|
||||||
|
name: "opencode.ai",
|
||||||
|
type: "TXT",
|
||||||
|
content: "compai-domain-verification=org_6993a99c6200a2d642bb115d",
|
||||||
|
ttl: 60,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
new cloudflare.RegionalHostname("RegionalHostname", {
|
new cloudflare.RegionalHostname("RegionalHostname", {
|
||||||
hostname: domain,
|
hostname: domain,
|
||||||
regionKey: "us",
|
regionKey: "us",
|
||||||
|
|||||||
+66
-33
@@ -8,6 +8,8 @@
|
|||||||
makeWrapper,
|
makeWrapper,
|
||||||
writableTmpDirAsHomeHook,
|
writableTmpDirAsHomeHook,
|
||||||
autoPatchelfHook,
|
autoPatchelfHook,
|
||||||
|
copyDesktopItems,
|
||||||
|
makeDesktopItem,
|
||||||
opencode,
|
opencode,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
@@ -27,9 +29,12 @@ stdenv.mkDerivation (finalAttrs: {
|
|||||||
nodejs
|
nodejs
|
||||||
makeWrapper
|
makeWrapper
|
||||||
writableTmpDirAsHomeHook
|
writableTmpDirAsHomeHook
|
||||||
] ++ lib.optionals stdenv.hostPlatform.isLinux [
|
]
|
||||||
|
++ lib.optionals stdenv.hostPlatform.isLinux [
|
||||||
autoPatchelfHook
|
autoPatchelfHook
|
||||||
] ++ lib.optionals stdenv.hostPlatform.isDarwin [
|
copyDesktopItems
|
||||||
|
]
|
||||||
|
++ lib.optionals stdenv.hostPlatform.isDarwin [
|
||||||
# Ad-hoc sign the .app: --config.mac.identity=null below skips signing.
|
# Ad-hoc sign the .app: --config.mac.identity=null below skips signing.
|
||||||
darwin.autoSignDarwinBinariesHook
|
darwin.autoSignDarwinBinariesHook
|
||||||
];
|
];
|
||||||
@@ -38,20 +43,37 @@ stdenv.mkDerivation (finalAttrs: {
|
|||||||
(lib.getLib stdenv.cc.cc)
|
(lib.getLib stdenv.cc.cc)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
desktopItems = lib.optional stdenv.hostPlatform.isLinux (makeDesktopItem {
|
||||||
|
name = "ai.opencode.desktop";
|
||||||
|
desktopName = "OpenCode";
|
||||||
|
exec = "opencode-desktop %U";
|
||||||
|
icon = "ai.opencode.desktop";
|
||||||
|
# Electron 41 derives X11 WM_CLASS from app.name.
|
||||||
|
startupWMClass = "OpenCode";
|
||||||
|
categories = [ "Development" ];
|
||||||
|
});
|
||||||
|
|
||||||
env = opencode.env // {
|
env = opencode.env // {
|
||||||
ELECTRON_SKIP_BINARY_DOWNLOAD = "1";
|
ELECTRON_SKIP_BINARY_DOWNLOAD = "1";
|
||||||
};
|
};
|
||||||
|
|
||||||
# https://github.com/electron/electron/issues/31121
|
postPatch =
|
||||||
# mac builds use a .app bundle which doesnt have this issue
|
# NOTE: Relax Bun version check to be a warning instead of an error
|
||||||
postPatch = lib.optionalString stdenv.isLinux ''
|
''
|
||||||
BASE_PATH=packages/desktop
|
substituteInPlace packages/script/src/index.ts \
|
||||||
FILES=(src/main/windows.ts)
|
--replace-fail 'throw new Error(`This script requires bun@''${expectedBunVersionRange}' \
|
||||||
for file in "''${FILES[@]}"; do
|
'console.warn(`Warning: This script requires bun@''${expectedBunVersionRange}'
|
||||||
substituteInPlace $BASE_PATH/$file \
|
''
|
||||||
--replace-fail "process.resourcesPath" "'$out/opt/opencode-desktop/resources'"
|
# https://github.com/electron/electron/issues/31121
|
||||||
done
|
# mac builds use a .app bundle which doesnt have this issue
|
||||||
'';
|
+ lib.optionalString stdenv.isLinux ''
|
||||||
|
BASE_PATH=packages/desktop
|
||||||
|
FILES=(src/main/windows.ts)
|
||||||
|
for file in "''${FILES[@]}"; do
|
||||||
|
substituteInPlace $BASE_PATH/$file \
|
||||||
|
--replace-fail "process.resourcesPath" "'$out/opt/opencode-desktop/resources'"
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
preBuild = ''
|
preBuild = ''
|
||||||
cp -r "${electron.dist}" $HOME/.electron-dist
|
cp -r "${electron.dist}" $HOME/.electron-dist
|
||||||
@@ -76,27 +98,38 @@ stdenv.mkDerivation (finalAttrs: {
|
|||||||
runHook postBuild
|
runHook postBuild
|
||||||
'';
|
'';
|
||||||
|
|
||||||
installPhase =
|
installPhase = ''
|
||||||
''
|
runHook preInstall
|
||||||
runHook preInstall
|
''
|
||||||
''
|
+ lib.optionalString stdenv.hostPlatform.isDarwin ''
|
||||||
+ lib.optionalString stdenv.hostPlatform.isDarwin ''
|
mkdir -p $out/Applications
|
||||||
mkdir -p $out/Applications
|
mv dist/mac*/*.app $out/Applications
|
||||||
mv dist/mac*/*.app $out/Applications
|
makeWrapper "$out/Applications/OpenCode.app/Contents/MacOS/OpenCode" $out/bin/opencode-desktop
|
||||||
makeWrapper "$out/Applications/OpenCode.app/Contents/MacOS/OpenCode" $out/bin/opencode-desktop
|
''
|
||||||
''
|
+ lib.optionalString stdenv.hostPlatform.isLinux ''
|
||||||
+ lib.optionalString stdenv.hostPlatform.isLinux ''
|
mkdir -p $out/opt/opencode-desktop
|
||||||
mkdir -p $out/opt/opencode-desktop
|
cp -r dist/linux*-unpacked/{resources,LICENSE*} $out/opt/opencode-desktop
|
||||||
cp -r dist/linux*-unpacked/{resources,LICENSE*} $out/opt/opencode-desktop
|
install -Dm644 resources/icons/32x32.png \
|
||||||
makeWrapper ${lib.getExe electron} $out/bin/opencode-desktop \
|
"$out/share/icons/hicolor/32x32/apps/ai.opencode.desktop.png"
|
||||||
--inherit-argv0 \
|
install -Dm644 resources/icons/64x64.png \
|
||||||
--set ELECTRON_FORCE_IS_PACKAGED 1 \
|
"$out/share/icons/hicolor/64x64/apps/ai.opencode.desktop.png"
|
||||||
--add-flags $out/opt/opencode-desktop/resources/app.asar \
|
install -Dm644 resources/icons/128x128.png \
|
||||||
--add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime=true}}"
|
"$out/share/icons/hicolor/128x128/apps/ai.opencode.desktop.png"
|
||||||
''
|
install -Dm644 resources/icons/128x128@2x.png \
|
||||||
+ ''
|
"$out/share/icons/hicolor/256x256/apps/ai.opencode.desktop.png"
|
||||||
runHook postInstall
|
install -Dm644 resources/icons/icon.png \
|
||||||
'';
|
"$out/share/icons/hicolor/512x512/apps/ai.opencode.desktop.png"
|
||||||
|
install -Dm644 resources/ai.opencode.desktop.metainfo.xml \
|
||||||
|
"$out/share/metainfo/ai.opencode.desktop.metainfo.xml"
|
||||||
|
makeWrapper ${lib.getExe electron} $out/bin/opencode-desktop \
|
||||||
|
--inherit-argv0 \
|
||||||
|
--set ELECTRON_FORCE_IS_PACKAGED 1 \
|
||||||
|
--add-flags $out/opt/opencode-desktop/resources/app.asar \
|
||||||
|
--add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime=true}}"
|
||||||
|
''
|
||||||
|
+ ''
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
autoPatchelfIgnoreMissingDeps = [
|
autoPatchelfIgnoreMissingDeps = [
|
||||||
"libc.musl-x86_64.so.1"
|
"libc.musl-x86_64.so.1"
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"nodeModules": {
|
"nodeModules": {
|
||||||
"x86_64-linux": "sha256-zAftZieWR0hWQQVkhEa826iueHaRnKxtyRodhNb+Apc=",
|
"x86_64-linux": "sha256-qt11SKmOjq0KU542QFbs+u7YyJicn4drCcwCdg325yk=",
|
||||||
"aarch64-linux": "sha256-cTG+gB7CuiFgaYHSFbzXaXUI1C2kiugV3B9rUDRUwlg=",
|
"aarch64-linux": "sha256-z68doReXTrWS7HeiAjc0btIjAsvzeZZ7hXAlHr0c77Q=",
|
||||||
"aarch64-darwin": "sha256-ap4Xv5ZliourkI4mcD0ktbzSI88jb4oVBv5mMK3WdUs=",
|
"aarch64-darwin": "sha256-PILYH1Pi8XBvSkuZ+1sNnUTao5kba+m5Z8iJKx6YXPo=",
|
||||||
"x86_64-darwin": "sha256-NGRJMMnaMNrf3FkcYwr5PcfVm5thhlNLGIeMmAK0MeE="
|
"x86_64-darwin": "sha256-KpcJzP4m0SUavu/WaSffgzOxrHq8ljdy0GOzs9p16lo="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { expect, test } from "@playwright/test"
|
||||||
|
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||||
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
|
import { expectAppVisible } from "../utils/waits"
|
||||||
|
|
||||||
|
const directory = "C:/OpenCode/PromptInputV2Editing"
|
||||||
|
const projectID = "proj_prompt_input_v2_editing"
|
||||||
|
const sessionID = "ses_prompt_input_v2_editing"
|
||||||
|
|
||||||
|
test("preserves the draft when a populated command menu triggers a built-in", async ({ page }) => {
|
||||||
|
await mockOpenCodeServer(page, {
|
||||||
|
directory,
|
||||||
|
project: {
|
||||||
|
id: projectID,
|
||||||
|
worktree: directory,
|
||||||
|
vcs: "git",
|
||||||
|
name: "prompt-input-v2-editing",
|
||||||
|
time: { created: 1700000000000, updated: 1700000000000 },
|
||||||
|
sandboxes: [],
|
||||||
|
},
|
||||||
|
provider: { all: [], connected: [], default: {} },
|
||||||
|
sessions: [
|
||||||
|
{
|
||||||
|
id: sessionID,
|
||||||
|
slug: "prompt-input-v2-editing",
|
||||||
|
projectID,
|
||||||
|
directory,
|
||||||
|
title: "Prompt input V2 editing",
|
||||||
|
version: "dev",
|
||||||
|
time: { created: 1700000000000, updated: 1700000000000 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageMessages: () => ({ items: [] }),
|
||||||
|
})
|
||||||
|
await page.addInitScript(() => {
|
||||||
|
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
|
||||||
|
const composer = page.locator('[data-component="prompt-input-v2"]')
|
||||||
|
const input = composer.locator('[data-component="prompt-input"]')
|
||||||
|
await expectAppVisible(composer)
|
||||||
|
|
||||||
|
await input.fill("keep me")
|
||||||
|
await composer.getByRole("button", { name: "Add images and files" }).click()
|
||||||
|
await page.getByRole("menuitem", { name: "Commands" }).click()
|
||||||
|
await page.locator('[data-suggestion-id="model.choose"]').click()
|
||||||
|
|
||||||
|
await expect(input).toHaveText("keep me")
|
||||||
|
})
|
||||||
@@ -56,7 +56,7 @@ test("shows the V2 thinking level control while relevant", async ({ page }) => {
|
|||||||
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
|
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
|
||||||
const composer = page.locator('[data-component="prompt-input-v2"]')
|
const composer = page.locator('[data-component="prompt-input-v2"]')
|
||||||
const input = composer.locator('[data-component="prompt-input"]')
|
const input = composer.locator('[data-component="prompt-input"]')
|
||||||
const control = composer.locator('button[title="Choose model variant"]')
|
const control = composer.getByRole("button", { name: "Choose model variant" })
|
||||||
await expectAppVisible(composer)
|
await expectAppVisible(composer)
|
||||||
|
|
||||||
await idleComposer(page)
|
await idleComposer(page)
|
||||||
|
|||||||
@@ -32,6 +32,23 @@ for (const expanded of [false, true]) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("shows and expands a running shell command without shimmering it", async ({ page }) => {
|
||||||
|
const id = "prt_shell_running_command"
|
||||||
|
const command = "sleep 10 && echo done"
|
||||||
|
await setupTimeline(page, {
|
||||||
|
messages: [userMessage(), assistantMessage([shell(id, "running", "still running", command)], { completed: false })],
|
||||||
|
settings: { shellToolPartsExpanded: false },
|
||||||
|
})
|
||||||
|
|
||||||
|
const tool = page.locator(`[data-timeline-part-id="${id}"]`)
|
||||||
|
await expect(tool.locator('[data-component="text-shimmer"]')).toHaveAttribute("data-active", "true")
|
||||||
|
await expect(tool.locator('[data-component="shell-submessage"]')).toHaveText(command)
|
||||||
|
await expect(tool.locator('[data-component="shell-submessage"] [data-component="text-shimmer"]')).toHaveCount(0)
|
||||||
|
await tool.locator('[data-slot="collapsible-trigger"]').click()
|
||||||
|
await expect(tool.locator('[data-slot="collapsible-trigger"]')).toHaveAttribute("aria-expanded", "true")
|
||||||
|
await expect(tool.locator('[data-slot="bash-pre"]')).toContainText("still running")
|
||||||
|
})
|
||||||
|
|
||||||
test("transitions thinking and hidden reasoning through busy to idle", async ({ page }) => {
|
test("transitions thinking and hidden reasoning through busy to idle", async ({ page }) => {
|
||||||
const reasoningID = "prt_reasoning_hidden"
|
const reasoningID = "prt_reasoning_hidden"
|
||||||
const assistant = assistantMessage([reasoningPart(reasoningID, "## Inspecting stability")], { completed: false })
|
const assistant = assistantMessage([reasoningPart(reasoningID, "## Inspecting stability")], { completed: false })
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/app",
|
"name": "@opencode-ai/app",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
"diff": "catalog:",
|
"diff": "catalog:",
|
||||||
"effect": "catalog:",
|
"effect": "catalog:",
|
||||||
"fuzzysort": "catalog:",
|
"fuzzysort": "catalog:",
|
||||||
"ghostty-web": "github:anomalyco/ghostty-web#513463a6f1190253057e8a3f0dac8f6ee8393553",
|
"ghostty-web": "github:anomalyco/ghostty-web#83c0a07b8628b748aed073b232cb4b52a6ca11c1",
|
||||||
"luxon": "catalog:",
|
"luxon": "catalog:",
|
||||||
"marked": "catalog:",
|
"marked": "catalog:",
|
||||||
"marked-shiki": "catalog:",
|
"marked-shiki": "catalog:",
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ function writeAndWait(term: Terminal, data: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("SerializeAddon", () => {
|
describe("SerializeAddon", () => {
|
||||||
|
test("preserves color scheme reporting mode", async () => {
|
||||||
|
const { term, addon } = createTerminal()
|
||||||
|
await writeAndWait(term, "\x1b[?2031h")
|
||||||
|
|
||||||
|
expect(addon.serialize().startsWith("\x1b[?2031h")).toBe(true)
|
||||||
|
expect(addon.serialize({ excludeModes: true }).startsWith("\x1b[?2031h")).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
describe("ANSI color preservation", () => {
|
describe("ANSI color preservation", () => {
|
||||||
test("should preserve text attributes (bold, italic, underline)", async () => {
|
test("should preserve text attributes (bold, italic, underline)", async () => {
|
||||||
const { term, addon } = createTerminal()
|
const { term, addon } = createTerminal()
|
||||||
|
|||||||
@@ -89,6 +89,13 @@ const getTerminalBuffers = (value: ITerminalCore): TerminalBuffers | undefined =
|
|||||||
return { active, normal, alternate }
|
return { active, normal, alternate }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTerminalMode = (value: ITerminalCore, mode: number) => {
|
||||||
|
if (!isRecord(value)) return false
|
||||||
|
const terminal = value.wasmTerm
|
||||||
|
if (!isRecord(terminal) || typeof terminal.getMode !== "function") return false
|
||||||
|
return terminal.getMode(mode) === true
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Types
|
// Types
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -544,7 +551,8 @@ export class SerializeAddon implements ITerminalAddon {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = options?.range
|
let content = !options?.excludeModes && getTerminalMode(this._terminal, 2031) ? "\u001b[?2031h" : ""
|
||||||
|
content += options?.range
|
||||||
? this._serializeBufferByRange(normalBuffer, options.range, true)
|
? this._serializeBufferByRange(normalBuffer, options.range, true)
|
||||||
: this._serializeBufferByScrollback(normalBuffer, options?.scrollback)
|
: this._serializeBufferByScrollback(normalBuffer, options?.scrollback)
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export function TabsInfoPopup() {
|
|||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const [drawerOpen, setDrawerOpen] = createSignal(false)
|
const [drawerOpen, setDrawerOpen] = createSignal(false)
|
||||||
|
const windows = () => platform.platform === "desktop" && platform.os === "windows"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side="right">
|
<Drawer open={drawerOpen()} onOpenChange={setDrawerOpen} side="right">
|
||||||
@@ -70,12 +71,40 @@ export function TabsInfoPopup() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<DrawerContent>
|
<DrawerContent
|
||||||
<div class="flex h-[52px] w-full shrink-0 items-center gap-4 self-stretch border-b border-v2-border-border-muted p-4">
|
style={
|
||||||
|
windows()
|
||||||
|
? {
|
||||||
|
inset: "0 0 0 auto",
|
||||||
|
"max-height": "100vh",
|
||||||
|
"max-width": "100vw",
|
||||||
|
"border-radius": "0",
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Show when={windows()}>
|
||||||
|
<DrawerClose
|
||||||
|
as={IconButtonV2}
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
variant="neutral"
|
||||||
|
aria-label="Close"
|
||||||
|
icon={<IconV2 name="xmark-small" />}
|
||||||
|
class="absolute top-[10px] left-[-36px]"
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
<div
|
||||||
|
class="flex w-full shrink-0 items-center gap-4 self-stretch border-b border-v2-border-border-muted"
|
||||||
|
classList={{
|
||||||
|
"h-[40px] px-4": windows(),
|
||||||
|
"h-[52px] p-4": !windows(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
<p class="min-h-0 min-w-0 flex-1 text-[13px] font-[530] leading-5 tracking-[-0.04px] tabular-nums text-v2-text-text-muted">
|
<p class="min-h-0 min-w-0 flex-1 text-[13px] font-[530] leading-5 tracking-[-0.04px] tabular-nums text-v2-text-text-muted">
|
||||||
July 14
|
July 14
|
||||||
</p>
|
</p>
|
||||||
<Show when={platform.platform !== "desktop" || platform.os !== "windows"}>
|
<Show when={!windows()}>
|
||||||
<DrawerClose
|
<DrawerClose
|
||||||
as={IconButtonV2}
|
as={IconButtonV2}
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
export type PromptInputV2ComposerProps = {
|
export type PromptInputV2ComposerProps = {
|
||||||
class?: string
|
class?: string
|
||||||
controller: PromptInputV2ComposerController
|
controller: PromptInputV2ComposerController
|
||||||
|
borderUnderlay?: boolean
|
||||||
edit?: PromptInputProps["edit"]
|
edit?: PromptInputProps["edit"]
|
||||||
onEditLoaded?: PromptInputProps["onEditLoaded"]
|
onEditLoaded?: PromptInputProps["onEditLoaded"]
|
||||||
}
|
}
|
||||||
@@ -57,7 +58,10 @@ export function PromptInputV2Composer(props: PromptInputV2ComposerProps) {
|
|||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<PromptInputV2
|
<PromptInputV2
|
||||||
controller={props.controller}
|
controller={props.controller}
|
||||||
|
borderUnderlay={props.borderUnderlay}
|
||||||
class={props.class}
|
class={props.class}
|
||||||
|
attachKeybind={command.keybindParts("file.attach")}
|
||||||
|
attachShortcut={command.keybind("file.attach")}
|
||||||
modelControl={
|
modelControl={
|
||||||
<PromptInputV2ModelControl
|
<PromptInputV2ModelControl
|
||||||
loading={props.controller.model.loading}
|
loading={props.controller.model.loading}
|
||||||
@@ -449,12 +453,14 @@ export function usePromptInputV2Controller(props: PromptInputV2ControllerProps):
|
|||||||
options: () => props.controls.agents.options.map((name) => ({ id: name, label: name })),
|
options: () => props.controls.agents.options.map((name) => ({ id: name, label: name })),
|
||||||
current: () => props.controls.agents.current,
|
current: () => props.controls.agents.current,
|
||||||
onSelect: props.controls.agents.select,
|
onSelect: props.controls.agents.select,
|
||||||
|
keybind: () => command.keybindParts("agent.cycle"),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
variant: {
|
variant: {
|
||||||
options: () => variants().map((value) => ({ id: value, label: value })),
|
options: () => variants().map((value) => ({ id: value, label: value })),
|
||||||
current: () => props.controls.model.selection.variant.current() ?? "default",
|
current: () => props.controls.model.selection.variant.current() ?? "default",
|
||||||
onSelect: (value) => props.controls.model.selection.variant.set(value === "default" ? undefined : value),
|
onSelect: (value) => props.controls.model.selection.variant.set(value === "default" ? undefined : value),
|
||||||
|
keybind: () => command.keybindParts("model.variant.cycle"),
|
||||||
},
|
},
|
||||||
submit: {
|
submit: {
|
||||||
stopping,
|
stopping,
|
||||||
|
|||||||
@@ -1460,10 +1460,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
t={(key) => language.t(key as Parameters<typeof language.t>[0])}
|
t={(key) => language.t(key as Parameters<typeof language.t>[0])}
|
||||||
/>
|
/>
|
||||||
<DockShellForm
|
<DockShellForm
|
||||||
|
data-dock-border-underlay="legacy"
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
classList={{
|
classList={{
|
||||||
"group/prompt-input": true,
|
"group/prompt-input": true,
|
||||||
"focus-within:shadow-xs-border": true,
|
|
||||||
"border-icon-info-active border-dashed": store.draggingType !== null,
|
"border-icon-info-active border-dashed": store.draggingType !== null,
|
||||||
[props.class ?? ""]: !!props.class,
|
[props.class ?? ""]: !!props.class,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -89,13 +89,15 @@ const toOptimisticPart = (part: PromptRequestPart, sessionID: string, messageID:
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildRequestParts(input: BuildRequestPartsInput) {
|
export function buildRequestParts(input: BuildRequestPartsInput) {
|
||||||
const requestParts: PromptRequestPart[] = [
|
const requestParts: PromptRequestPart[] = input.text.trim()
|
||||||
{
|
? [
|
||||||
id: Identifier.ascending("part"),
|
{
|
||||||
type: "text",
|
id: Identifier.ascending("part"),
|
||||||
text: input.text,
|
type: "text",
|
||||||
},
|
text: input.text,
|
||||||
]
|
},
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
|
||||||
const files = input.prompt.filter(isFileAttachment).map((attachment) => {
|
const files = input.prompt.filter(isFileAttachment).map((attachment) => {
|
||||||
const path = absolute(input.sessionDirectory, attachment.path)
|
const path = absolute(input.sessionDirectory, attachment.path)
|
||||||
|
|||||||
@@ -93,24 +93,36 @@ export function PromptWorkspaceSelector(props: {
|
|||||||
</MenuV2.Content>
|
</MenuV2.Content>
|
||||||
</MenuV2.Portal>
|
</MenuV2.Portal>
|
||||||
</MenuV2>
|
</MenuV2>
|
||||||
<Show when={props.branch}>
|
<PromptGitStatus branch={props.branch} />
|
||||||
{(branch) => (
|
|
||||||
<>
|
|
||||||
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
|
|
||||||
<TooltipV2
|
|
||||||
placement="top"
|
|
||||||
value={branch()}
|
|
||||||
class="min-w-0 max-w-[220px]"
|
|
||||||
contentClass="max-w-[calc(100vw-32px)] break-all"
|
|
||||||
>
|
|
||||||
<div class="flex h-7 min-w-0 max-w-[220px] items-center gap-1.5 px-2 text-[13px] font-[440] leading-5 tracking-[-0.04px]">
|
|
||||||
<Icon name="branch" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
|
||||||
<span class="min-w-0 truncate">{branch()}</span>
|
|
||||||
</div>
|
|
||||||
</TooltipV2>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Show>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function PromptGitStatus(props: { branch?: string; noGit?: boolean }) {
|
||||||
|
const language = useLanguage()
|
||||||
|
const label = () => {
|
||||||
|
if (props.noGit) return language.t("session.new.git.none")
|
||||||
|
return props.branch
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={label()}>
|
||||||
|
{(value) => (
|
||||||
|
<>
|
||||||
|
<span class="hidden select-none opacity-50 sm:inline mx-1">/</span>
|
||||||
|
<TooltipV2
|
||||||
|
placement="top"
|
||||||
|
value={value()}
|
||||||
|
class="min-w-0 max-w-[220px]"
|
||||||
|
contentClass="max-w-[calc(100vw-32px)] break-all"
|
||||||
|
>
|
||||||
|
<div class="flex h-7 min-w-0 max-w-[220px] items-center gap-1.5 px-2 text-[13px] font-[440] leading-5 tracking-[-0.04px]">
|
||||||
|
<Icon name="branch" size="small" class="shrink-0 text-v2-icon-icon-muted" />
|
||||||
|
<span class="min-w-0 truncate">{value()}</span>
|
||||||
|
</div>
|
||||||
|
</TooltipV2>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -133,9 +133,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="settings-v2-row-description"] {
|
[data-slot="settings-v2-row-description"] {
|
||||||
|
margin-block: -3.5px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 440;
|
font-weight: 440;
|
||||||
line-height: 1;
|
line-height: 20px;
|
||||||
color: var(--v2-text-text-muted);
|
color: var(--v2-text-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,10 +271,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.settings-v2-provider-description {
|
.settings-v2-provider-description {
|
||||||
margin: 0;
|
margin-block: -3.5px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 440;
|
font-weight: 440;
|
||||||
line-height: 1;
|
line-height: 20px;
|
||||||
color: var(--v2-text-text-muted);
|
color: var(--v2-text-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -292,7 +292,12 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
|
|
||||||
const scheduleSize = (cols: number, rows: number) => {
|
const scheduleSize = (cols: number, rows: number) => {
|
||||||
if (disposed) return
|
if (disposed) return
|
||||||
if (lastSize?.cols === cols && lastSize?.rows === rows) return
|
if (lastSize?.cols === cols && lastSize?.rows === rows) {
|
||||||
|
pendingSize = undefined
|
||||||
|
if (sizeTimer !== undefined) clearTimeout(sizeTimer)
|
||||||
|
sizeTimer = undefined
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
pendingSize = { cols, rows }
|
pendingSize = { cols, rows }
|
||||||
|
|
||||||
@@ -317,8 +322,10 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const colors = terminalColors()
|
const colors = terminalColors()
|
||||||
|
const mode = theme.mode() === "dark" ? "dark" : "light"
|
||||||
if (!term) return
|
if (!term) return
|
||||||
setOptionIfSupported(term, "theme", colors)
|
setOptionIfSupported(term, "theme", colors)
|
||||||
|
setOptionIfSupported(term, "colorScheme", mode)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@@ -396,6 +403,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
}
|
}
|
||||||
_ghostty = g
|
_ghostty = g
|
||||||
term = t
|
term = t
|
||||||
|
setOptionIfSupported(t, "colorScheme", theme.mode() === "dark" ? "dark" : "light")
|
||||||
output = terminalWriter((data, done) =>
|
output = terminalWriter((data, done) =>
|
||||||
t.write(data, () => {
|
t.write(data, () => {
|
||||||
done?.()
|
done?.()
|
||||||
@@ -595,6 +603,7 @@ export const Terminal = (props: TerminalProps) => {
|
|||||||
tries = 0
|
tries = 0
|
||||||
local.onConnect?.()
|
local.onConnect?.()
|
||||||
scheduleSize(t.cols, t.rows)
|
scheduleSize(t.cols, t.rows)
|
||||||
|
if (t.getMode(2031)) t.write("\x1b[?996n")
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMessage = (event: MessageEvent) => {
|
const handleMessage = (event: MessageEvent) => {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function useTitlebarRightMount() {
|
|||||||
return mount
|
return mount
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Titlebar(props: { update?: TitlebarUpdate }) {
|
export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visible: boolean; toggle: () => void } }) {
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const command = useCommand()
|
const command = useCommand()
|
||||||
@@ -462,7 +462,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
"md:pl-4": !mac(),
|
"md:pl-4": !mac(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ChannelIndicator />
|
<ChannelIndicator debugTools={props.debugTools} />
|
||||||
<Show when={windows() || linux()}>
|
<Show when={windows() || linux()}>
|
||||||
<WindowsAppMenu command={command} platform={platform} variant="v2" />
|
<WindowsAppMenu command={command} platform={platform} variant="v2" />
|
||||||
</Show>
|
</Show>
|
||||||
@@ -660,9 +660,9 @@ export function Titlebar(props: { update?: TitlebarUpdate }) {
|
|||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
|
<div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
|
||||||
<ChannelIndicator />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ChannelIndicator debugTools={props.debugTools} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -747,12 +747,27 @@ function TitlebarUpdateIconButton(props: { state: TitlebarUpdatePillState }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChannelIndicator() {
|
function ChannelIndicator(props: { debugTools?: { visible: boolean; toggle: () => void } }) {
|
||||||
|
const channel = import.meta.env.VITE_OPENCODE_CHANNEL
|
||||||
|
if (channel === "dev" && props.debugTools) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="bg-icon-interactive-base text-[#FFF] font-medium px-2 rounded-sm uppercase font-mono cursor-pointer"
|
||||||
|
onClick={props.debugTools.toggle}
|
||||||
|
aria-label="Toggle debug tools"
|
||||||
|
aria-pressed={props.debugTools.visible}
|
||||||
|
>
|
||||||
|
DEV
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{["beta", "dev"].includes(import.meta.env.VITE_OPENCODE_CHANNEL) && (
|
{["beta", "dev"].includes(channel) && (
|
||||||
<div class="bg-icon-interactive-base text-[#FFF] font-medium px-2 rounded-sm uppercase font-mono">
|
<div class="bg-icon-interactive-base text-[#FFF] font-medium px-2 rounded-sm uppercase font-mono">
|
||||||
{import.meta.env.VITE_OPENCODE_CHANNEL.toUpperCase()}
|
{channel.toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -176,7 +176,14 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|||||||
|
|
||||||
onCleanup(() => states.forEach((value) => value.dispose()))
|
onCleanup(() => states.forEach((value) => value.dispose()))
|
||||||
|
|
||||||
const selected = () => ensure(activeServer())
|
const selected = () => {
|
||||||
|
const list = global.servers.list()
|
||||||
|
const key = activeServer()
|
||||||
|
if (list.some((conn) => ServerConnection.key(conn) === key)) return ensure(key)
|
||||||
|
const conn = list.find((conn) => ServerConnection.key(conn) === server.key) ?? list[0]
|
||||||
|
if (!conn) throw new Error("Notification server not found")
|
||||||
|
return ensure(ServerConnection.key(conn))
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ready: () => selected().ready(),
|
ready: () => selected().ready(),
|
||||||
|
|||||||
@@ -200,6 +200,49 @@ describe("server session", () => {
|
|||||||
expect(store.history.more("child")).toBe(true)
|
expect(store.history.more("child")).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("keeps assistant history when its deleted parent cannot be backfilled", async () => {
|
||||||
|
const missing = Promise.withResolvers<SingleMessageResponse>()
|
||||||
|
const assistant = assistantMessage("message-2", "message-missing")
|
||||||
|
const client = rootMessageClient([response([{ info: assistant, parts: [] }], "older")], [missing.promise])
|
||||||
|
const store = createServerSession(client)
|
||||||
|
const loading = store.sync("child")
|
||||||
|
await client.rootRequested(1)
|
||||||
|
|
||||||
|
missing.reject(new Error("Message not found: message-missing", { cause: { status: 404 } }))
|
||||||
|
await loading
|
||||||
|
|
||||||
|
expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: "message-missing" }])
|
||||||
|
expect(store.data.message.child).toEqual([assistant])
|
||||||
|
expect(store.history.more("child")).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("drops a cached parent when a forced refresh confirms it was deleted", async () => {
|
||||||
|
const missing = Promise.withResolvers<SingleMessageResponse>()
|
||||||
|
const parent = userMessage("message-1")
|
||||||
|
const part = textPart(parent.id)
|
||||||
|
const assistant = assistantMessage("message-2", parent.id)
|
||||||
|
const client = rootMessageClient(
|
||||||
|
[
|
||||||
|
response([
|
||||||
|
{ info: parent, parts: [part] },
|
||||||
|
{ info: assistant, parts: [] },
|
||||||
|
]),
|
||||||
|
response([{ info: assistant, parts: [] }], "older"),
|
||||||
|
],
|
||||||
|
[missing.promise],
|
||||||
|
)
|
||||||
|
const store = createServerSession(client)
|
||||||
|
await store.sync("child")
|
||||||
|
const loading = store.sync("child", { force: true })
|
||||||
|
await client.rootRequested(1)
|
||||||
|
|
||||||
|
missing.reject(new Error(`Message not found: ${parent.id}`, { cause: { status: 404 } }))
|
||||||
|
await loading
|
||||||
|
|
||||||
|
expect(store.data.message.child).toEqual([assistant])
|
||||||
|
expect(store.data.part[parent.id]).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
test("does not let an optimistic user suppress initial root backfill", async () => {
|
test("does not let an optimistic user suppress initial root backfill", async () => {
|
||||||
const user = userMessage("message-1")
|
const user = userMessage("message-1")
|
||||||
const part = textPart(user.id)
|
const part = textPart(user.id)
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ function reconcileFetched<T extends { id: string }>(
|
|||||||
options: {
|
options: {
|
||||||
touched?: ReadonlySet<string>
|
touched?: ReadonlySet<string>
|
||||||
retained?: ReadonlySet<string>
|
retained?: ReadonlySet<string>
|
||||||
|
removed?: ReadonlySet<string>
|
||||||
preserveUnfetched?: boolean | ((item: T) => boolean)
|
preserveUnfetched?: boolean | ((item: T) => boolean)
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
@@ -132,6 +133,7 @@ function reconcileFetched<T extends { id: string }>(
|
|||||||
if (item) result.set(id, item)
|
if (item) result.set(id, item)
|
||||||
if (!item) result.delete(id)
|
if (!item) result.delete(id)
|
||||||
}
|
}
|
||||||
|
for (const id of options.removed ?? emptyIDs) result.delete(id)
|
||||||
return [...result.values()].sort((a, b) => cmp(a.id, b.id))
|
return [...result.values()].sort((a, b) => cmp(a.id, b.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,6 +579,7 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
|
|||||||
const messages = reconcileFetched(merged.session, data.message[sessionID] ?? [], {
|
const messages = reconcileFetched(merged.session, data.message[sessionID] ?? [], {
|
||||||
touched: touchedMessages,
|
touched: touchedMessages,
|
||||||
retained: load?.retainedMessages,
|
retained: load?.retainedMessages,
|
||||||
|
removed: load?.removedMessages,
|
||||||
preserveUnfetched,
|
preserveUnfetched,
|
||||||
})
|
})
|
||||||
batch(() => {
|
batch(() => {
|
||||||
@@ -645,7 +648,15 @@ export function createServerSession(client: OpencodeClient, options?: { retry?:
|
|||||||
if (generations.get(sessionID) !== active) break
|
if (generations.get(sessionID) !== active) break
|
||||||
const parent = await fetchMessage(sessionID, parentID, () =>
|
const parent = await fetchMessage(sessionID, parentID, () =>
|
||||||
resetMessageLoad(sessionID, load, messageLoadBaseline(load, parentID)),
|
resetMessageLoad(sessionID, load, messageLoadBaseline(load, parentID)),
|
||||||
)
|
).catch((error) => {
|
||||||
|
const cause = error instanceof Error && typeof error.cause === "object" ? error.cause : undefined
|
||||||
|
if (cause && "status" in cause && cause.status === 404) {
|
||||||
|
load.removedMessages.add(parentID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
if (!parent) continue
|
||||||
if (parent.message.role !== "user") throw new Error(`Assistant parent is not a user message: ${parentID}`)
|
if (parent.message.role !== "user") throw new Error(`Assistant parent is not a user message: ${parentID}`)
|
||||||
parents.push(parent)
|
parents.push(parent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "تمكين أو تعطيل مساحات العمل المتعددة في الشريط الجانبي",
|
"command.workspace.toggle.description": "تمكين أو تعطيل مساحات العمل المتعددة في الشريط الجانبي",
|
||||||
"command.session.undo": "تراجع",
|
"command.session.undo": "تراجع",
|
||||||
"command.session.undo.description": "تراجع عن الرسالة الأخيرة",
|
"command.session.undo.description": "تراجع عن الرسالة الأخيرة",
|
||||||
"command.session.undo.gitRequired": "لم يتم اكتشاف مستودع Git",
|
|
||||||
"command.session.redo": "إعادة",
|
"command.session.redo": "إعادة",
|
||||||
"command.session.redo.description": "إعادة الرسالة التي تم التراجع عنها",
|
"command.session.redo.description": "إعادة الرسالة التي تم التراجع عنها",
|
||||||
"command.session.compact": "ضغط الجلسة",
|
"command.session.compact": "ضغط الجلسة",
|
||||||
@@ -614,6 +613,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "محلي",
|
"session.new.workspace.triggerLocal": "محلي",
|
||||||
"session.new.workspace.local": "المستودع المحلي",
|
"session.new.workspace.local": "المستودع المحلي",
|
||||||
"session.new.workspace.existing": "مساحة عمل…",
|
"session.new.workspace.existing": "مساحة عمل…",
|
||||||
|
"session.new.git.none": "لا يوجد Git",
|
||||||
"session.new.lastModified": "آخر تعديل",
|
"session.new.lastModified": "آخر تعديل",
|
||||||
"session.header.search.placeholder": "بحث {{project}}",
|
"session.header.search.placeholder": "بحث {{project}}",
|
||||||
"session.header.searchFiles": "بحث عن الملفات",
|
"session.header.searchFiles": "بحث عن الملفات",
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Habilitar ou desabilitar múltiplos espaços de trabalho na barra lateral",
|
"command.workspace.toggle.description": "Habilitar ou desabilitar múltiplos espaços de trabalho na barra lateral",
|
||||||
"command.session.undo": "Desfazer",
|
"command.session.undo": "Desfazer",
|
||||||
"command.session.undo.description": "Desfazer a última mensagem",
|
"command.session.undo.description": "Desfazer a última mensagem",
|
||||||
"command.session.undo.gitRequired": "Nenhum repositório Git detectado",
|
|
||||||
"command.session.redo": "Refazer",
|
"command.session.redo": "Refazer",
|
||||||
"command.session.redo.description": "Refazer a última mensagem desfeita",
|
"command.session.redo.description": "Refazer a última mensagem desfeita",
|
||||||
"command.session.compact": "Compactar sessão",
|
"command.session.compact": "Compactar sessão",
|
||||||
@@ -620,6 +619,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Local",
|
"session.new.workspace.triggerLocal": "Local",
|
||||||
"session.new.workspace.local": "Repositório local",
|
"session.new.workspace.local": "Repositório local",
|
||||||
"session.new.workspace.existing": "Espaço de trabalho…",
|
"session.new.workspace.existing": "Espaço de trabalho…",
|
||||||
|
"session.new.git.none": "Sem Git",
|
||||||
"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",
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Omogući ili onemogući više radnih prostora u bočnoj traci",
|
"command.workspace.toggle.description": "Omogući ili onemogući više radnih prostora u bočnoj traci",
|
||||||
"command.session.undo": "Poništi",
|
"command.session.undo": "Poništi",
|
||||||
"command.session.undo.description": "Poništi posljednju poruku",
|
"command.session.undo.description": "Poništi posljednju poruku",
|
||||||
"command.session.undo.gitRequired": "Git repozitorij nije otkriven",
|
|
||||||
"command.session.redo": "Vrati",
|
"command.session.redo": "Vrati",
|
||||||
"command.session.redo.description": "Vrati posljednju poništenu poruku",
|
"command.session.redo.description": "Vrati posljednju poništenu poruku",
|
||||||
"command.session.compact": "Sažmi sesiju",
|
"command.session.compact": "Sažmi sesiju",
|
||||||
@@ -676,6 +675,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Lokalno",
|
"session.new.workspace.triggerLocal": "Lokalno",
|
||||||
"session.new.workspace.local": "Lokalni repozitorij",
|
"session.new.workspace.local": "Lokalni repozitorij",
|
||||||
"session.new.workspace.existing": "Radni prostor…",
|
"session.new.workspace.existing": "Radni prostor…",
|
||||||
|
"session.new.git.none": "Nema Gita",
|
||||||
"session.new.lastModified": "Posljednja izmjena",
|
"session.new.lastModified": "Posljednja izmjena",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Pretraži {{project}}",
|
"session.header.search.placeholder": "Pretraži {{project}}",
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Aktiver eller deaktiver flere arbejdsområder i sidebjælken",
|
"command.workspace.toggle.description": "Aktiver eller deaktiver flere arbejdsområder i sidebjælken",
|
||||||
"command.session.undo": "Fortryd",
|
"command.session.undo": "Fortryd",
|
||||||
"command.session.undo.description": "Fortryd den sidste besked",
|
"command.session.undo.description": "Fortryd den sidste besked",
|
||||||
"command.session.undo.gitRequired": "Intet Git-repository fundet",
|
|
||||||
"command.session.redo": "Omgør",
|
"command.session.redo": "Omgør",
|
||||||
"command.session.redo.description": "Omgør den sidste fortrudte besked",
|
"command.session.redo.description": "Omgør den sidste fortrudte besked",
|
||||||
"command.session.compact": "Komprimér session",
|
"command.session.compact": "Komprimér session",
|
||||||
@@ -671,6 +670,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Lokal",
|
"session.new.workspace.triggerLocal": "Lokal",
|
||||||
"session.new.workspace.local": "Lokalt repository",
|
"session.new.workspace.local": "Lokalt repository",
|
||||||
"session.new.workspace.existing": "Arbejdsområde…",
|
"session.new.workspace.existing": "Arbejdsområde…",
|
||||||
|
"session.new.git.none": "Ingen Git",
|
||||||
"session.new.lastModified": "Sidst ændret",
|
"session.new.lastModified": "Sidst ændret",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Søg {{project}}",
|
"session.header.search.placeholder": "Søg {{project}}",
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Mehrere Arbeitsbereiche in der Seitenleiste aktivieren oder deaktivieren",
|
"command.workspace.toggle.description": "Mehrere Arbeitsbereiche in der Seitenleiste aktivieren oder deaktivieren",
|
||||||
"command.session.undo": "Rückgängig",
|
"command.session.undo": "Rückgängig",
|
||||||
"command.session.undo.description": "Letzte Nachricht rückgängig machen",
|
"command.session.undo.description": "Letzte Nachricht rückgängig machen",
|
||||||
"command.session.undo.gitRequired": "Kein Git-Repository erkannt",
|
|
||||||
"command.session.redo": "Wiederherstellen",
|
"command.session.redo": "Wiederherstellen",
|
||||||
"command.session.redo.description": "Letzte rückgängig gemachte Nachricht wiederherstellen",
|
"command.session.redo.description": "Letzte rückgängig gemachte Nachricht wiederherstellen",
|
||||||
"command.session.compact": "Sitzung komprimieren",
|
"command.session.compact": "Sitzung komprimieren",
|
||||||
@@ -629,6 +628,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Lokal",
|
"session.new.workspace.triggerLocal": "Lokal",
|
||||||
"session.new.workspace.local": "Lokales Repository",
|
"session.new.workspace.local": "Lokales Repository",
|
||||||
"session.new.workspace.existing": "Arbeitsbereich…",
|
"session.new.workspace.existing": "Arbeitsbereich…",
|
||||||
|
"session.new.git.none": "Kein Git",
|
||||||
"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",
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar",
|
"command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar",
|
||||||
"command.session.undo": "Undo",
|
"command.session.undo": "Undo",
|
||||||
"command.session.undo.description": "Undo the last message",
|
"command.session.undo.description": "Undo the last message",
|
||||||
"command.session.undo.gitRequired": "No Git repository detected",
|
|
||||||
"command.session.redo": "Redo",
|
"command.session.redo": "Redo",
|
||||||
"command.session.redo.description": "Redo the last undone message",
|
"command.session.redo.description": "Redo the last undone message",
|
||||||
"command.session.compact": "Compact session",
|
"command.session.compact": "Compact session",
|
||||||
@@ -701,6 +700,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Local",
|
"session.new.workspace.triggerLocal": "Local",
|
||||||
"session.new.workspace.local": "Local repository",
|
"session.new.workspace.local": "Local repository",
|
||||||
"session.new.workspace.existing": "Workspace…",
|
"session.new.workspace.existing": "Workspace…",
|
||||||
|
"session.new.git.none": "No Git",
|
||||||
"session.new.lastModified": "Last modified",
|
"session.new.lastModified": "Last modified",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Search {{project}}",
|
"session.header.search.placeholder": "Search {{project}}",
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Habilitar o deshabilitar múltiples espacios de trabajo en la barra lateral",
|
"command.workspace.toggle.description": "Habilitar o deshabilitar múltiples espacios de trabajo en la barra lateral",
|
||||||
"command.session.undo": "Deshacer",
|
"command.session.undo": "Deshacer",
|
||||||
"command.session.undo.description": "Deshacer el último mensaje",
|
"command.session.undo.description": "Deshacer el último mensaje",
|
||||||
"command.session.undo.gitRequired": "No se detectó ningún repositorio Git",
|
|
||||||
"command.session.redo": "Rehacer",
|
"command.session.redo": "Rehacer",
|
||||||
"command.session.redo.description": "Rehacer el último mensaje deshecho",
|
"command.session.redo.description": "Rehacer el último mensaje deshecho",
|
||||||
"command.session.compact": "Compactar sesión",
|
"command.session.compact": "Compactar sesión",
|
||||||
@@ -677,6 +676,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Local",
|
"session.new.workspace.triggerLocal": "Local",
|
||||||
"session.new.workspace.local": "Repositorio local",
|
"session.new.workspace.local": "Repositorio local",
|
||||||
"session.new.workspace.existing": "Espacio de trabajo…",
|
"session.new.workspace.existing": "Espacio de trabajo…",
|
||||||
|
"session.new.git.none": "Sin Git",
|
||||||
"session.new.lastModified": "Última modificación",
|
"session.new.lastModified": "Última modificación",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Buscar {{project}}",
|
"session.header.search.placeholder": "Buscar {{project}}",
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Activer ou désactiver plusieurs espaces de travail dans la barre latérale",
|
"command.workspace.toggle.description": "Activer ou désactiver plusieurs espaces de travail dans la barre latérale",
|
||||||
"command.session.undo": "Annuler",
|
"command.session.undo": "Annuler",
|
||||||
"command.session.undo.description": "Annuler le dernier message",
|
"command.session.undo.description": "Annuler le dernier message",
|
||||||
"command.session.undo.gitRequired": "Aucun dépôt Git détecté",
|
|
||||||
"command.session.redo": "Rétablir",
|
"command.session.redo": "Rétablir",
|
||||||
"command.session.redo.description": "Rétablir le dernier message annulé",
|
"command.session.redo.description": "Rétablir le dernier message annulé",
|
||||||
"command.session.compact": "Compacter la session",
|
"command.session.compact": "Compacter la session",
|
||||||
@@ -625,6 +624,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Local",
|
"session.new.workspace.triggerLocal": "Local",
|
||||||
"session.new.workspace.local": "Dépôt local",
|
"session.new.workspace.local": "Dépôt local",
|
||||||
"session.new.workspace.existing": "Espace de travail…",
|
"session.new.workspace.existing": "Espace de travail…",
|
||||||
|
"session.new.git.none": "Pas de Git",
|
||||||
"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",
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "サイドバーでの複数のワークスペースの有効化・無効化",
|
"command.workspace.toggle.description": "サイドバーでの複数のワークスペースの有効化・無効化",
|
||||||
"command.session.undo": "元に戻す",
|
"command.session.undo": "元に戻す",
|
||||||
"command.session.undo.description": "最後のメッセージを元に戻す",
|
"command.session.undo.description": "最後のメッセージを元に戻す",
|
||||||
"command.session.undo.gitRequired": "Git リポジトリが検出されませんでした",
|
|
||||||
"command.session.redo": "やり直す",
|
"command.session.redo": "やり直す",
|
||||||
"command.session.redo.description": "元に戻したメッセージをやり直す",
|
"command.session.redo.description": "元に戻したメッセージをやり直す",
|
||||||
"command.session.compact": "セッションを圧縮",
|
"command.session.compact": "セッションを圧縮",
|
||||||
@@ -616,6 +615,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "ローカル",
|
"session.new.workspace.triggerLocal": "ローカル",
|
||||||
"session.new.workspace.local": "ローカルリポジトリ",
|
"session.new.workspace.local": "ローカルリポジトリ",
|
||||||
"session.new.workspace.existing": "ワークスペース…",
|
"session.new.workspace.existing": "ワークスペース…",
|
||||||
|
"session.new.git.none": "Git なし",
|
||||||
"session.new.lastModified": "最終更新",
|
"session.new.lastModified": "最終更新",
|
||||||
"session.header.search.placeholder": "{{project}}を検索",
|
"session.header.search.placeholder": "{{project}}を検索",
|
||||||
"session.header.searchFiles": "ファイルを検索",
|
"session.header.searchFiles": "ファイルを検索",
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "사이드바에서 다중 작업 공간 활성화 또는 비활성화",
|
"command.workspace.toggle.description": "사이드바에서 다중 작업 공간 활성화 또는 비활성화",
|
||||||
"command.session.undo": "실행 취소",
|
"command.session.undo": "실행 취소",
|
||||||
"command.session.undo.description": "마지막 메시지 실행 취소",
|
"command.session.undo.description": "마지막 메시지 실행 취소",
|
||||||
"command.session.undo.gitRequired": "Git 저장소가 감지되지 않았습니다",
|
|
||||||
"command.session.redo": "다시 실행",
|
"command.session.redo": "다시 실행",
|
||||||
"command.session.redo.description": "마지막 실행 취소된 메시지 다시 실행",
|
"command.session.redo.description": "마지막 실행 취소된 메시지 다시 실행",
|
||||||
"command.session.compact": "세션 압축",
|
"command.session.compact": "세션 압축",
|
||||||
@@ -977,6 +976,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "로컬",
|
"session.new.workspace.triggerLocal": "로컬",
|
||||||
"session.new.workspace.local": "로컬 저장소",
|
"session.new.workspace.local": "로컬 저장소",
|
||||||
"session.new.workspace.existing": "작업 공간…",
|
"session.new.workspace.existing": "작업 공간…",
|
||||||
|
"session.new.git.none": "Git 없음",
|
||||||
|
|
||||||
"sidebar.empty.title": "열린 프로젝트 없음",
|
"sidebar.empty.title": "열린 프로젝트 없음",
|
||||||
"sidebar.empty.description": "프로젝트를 열어 시작하세요",
|
"sidebar.empty.description": "프로젝트를 열어 시작하세요",
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Aktiver eller deaktiver flere arbeidsområder i sidefeltet",
|
"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.undo.gitRequired": "Fant ikke noe Git-repositorium",
|
|
||||||
"command.session.redo": "Gjør om",
|
"command.session.redo": "Gjør om",
|
||||||
"command.session.redo.description": "Gjør om siste angrede melding",
|
"command.session.redo.description": "Gjør om siste angrede melding",
|
||||||
"command.session.compact": "Komprimer sesjon",
|
"command.session.compact": "Komprimer sesjon",
|
||||||
@@ -1071,6 +1070,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Lokalt",
|
"session.new.workspace.triggerLocal": "Lokalt",
|
||||||
"session.new.workspace.local": "Lokalt depot",
|
"session.new.workspace.local": "Lokalt depot",
|
||||||
"session.new.workspace.existing": "Arbeidsområde…",
|
"session.new.workspace.existing": "Arbeidsområde…",
|
||||||
|
"session.new.git.none": "Ingen Git",
|
||||||
|
|
||||||
"sidebar.empty.title": "Ingen åpne prosjekter",
|
"sidebar.empty.title": "Ingen åpne prosjekter",
|
||||||
"sidebar.empty.description": "Åpne et prosjekt for å komme i gang",
|
"sidebar.empty.description": "Åpne et prosjekt for å komme i gang",
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Włącz lub wyłącz wiele przestrzeni roboczych na pasku bocznym",
|
"command.workspace.toggle.description": "Włącz lub wyłącz wiele przestrzeni roboczych na pasku bocznym",
|
||||||
"command.session.undo": "Cofnij",
|
"command.session.undo": "Cofnij",
|
||||||
"command.session.undo.description": "Cofnij ostatnią wiadomość",
|
"command.session.undo.description": "Cofnij ostatnią wiadomość",
|
||||||
"command.session.undo.gitRequired": "Nie wykryto repozytorium Git",
|
|
||||||
"command.session.redo": "Ponów",
|
"command.session.redo": "Ponów",
|
||||||
"command.session.redo.description": "Ponów ostatnią cofniętą wiadomość",
|
"command.session.redo.description": "Ponów ostatnią cofniętą wiadomość",
|
||||||
"command.session.compact": "Kompaktuj sesję",
|
"command.session.compact": "Kompaktuj sesję",
|
||||||
@@ -620,6 +619,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Lokalnie",
|
"session.new.workspace.triggerLocal": "Lokalnie",
|
||||||
"session.new.workspace.local": "Lokalne repozytorium",
|
"session.new.workspace.local": "Lokalne repozytorium",
|
||||||
"session.new.workspace.existing": "Przestrzeń robocza…",
|
"session.new.workspace.existing": "Przestrzeń robocza…",
|
||||||
|
"session.new.git.none": "Brak Git",
|
||||||
"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",
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Включить или отключить несколько рабочих пространств в боковой панели",
|
"command.workspace.toggle.description": "Включить или отключить несколько рабочих пространств в боковой панели",
|
||||||
"command.session.undo": "Отменить",
|
"command.session.undo": "Отменить",
|
||||||
"command.session.undo.description": "Отменить последнее сообщение",
|
"command.session.undo.description": "Отменить последнее сообщение",
|
||||||
"command.session.undo.gitRequired": "Репозиторий Git не обнаружен",
|
|
||||||
"command.session.redo": "Повторить",
|
"command.session.redo": "Повторить",
|
||||||
"command.session.redo.description": "Повторить отменённое сообщение",
|
"command.session.redo.description": "Повторить отменённое сообщение",
|
||||||
"command.session.compact": "Сжать сессию",
|
"command.session.compact": "Сжать сессию",
|
||||||
@@ -674,6 +673,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Локально",
|
"session.new.workspace.triggerLocal": "Локально",
|
||||||
"session.new.workspace.local": "Локальный репозиторий",
|
"session.new.workspace.local": "Локальный репозиторий",
|
||||||
"session.new.workspace.existing": "Рабочее пространство…",
|
"session.new.workspace.existing": "Рабочее пространство…",
|
||||||
|
"session.new.git.none": "Нет Git",
|
||||||
"session.new.lastModified": "Последнее изменение",
|
"session.new.lastModified": "Последнее изменение",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Поиск {{project}}",
|
"session.header.search.placeholder": "Поиск {{project}}",
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "เปิดหรือปิดใช้งานพื้นที่ทำงานหลายรายการในแถบด้านข้าง",
|
"command.workspace.toggle.description": "เปิดหรือปิดใช้งานพื้นที่ทำงานหลายรายการในแถบด้านข้าง",
|
||||||
"command.session.undo": "ยกเลิก",
|
"command.session.undo": "ยกเลิก",
|
||||||
"command.session.undo.description": "ยกเลิกข้อความล่าสุด",
|
"command.session.undo.description": "ยกเลิกข้อความล่าสุด",
|
||||||
"command.session.undo.gitRequired": "ไม่พบที่เก็บ Git",
|
|
||||||
"command.session.redo": "ทำซ้ำ",
|
"command.session.redo": "ทำซ้ำ",
|
||||||
"command.session.redo.description": "ทำซ้ำข้อความที่ถูกยกเลิกล่าสุด",
|
"command.session.redo.description": "ทำซ้ำข้อความที่ถูกยกเลิกล่าสุด",
|
||||||
"command.session.compact": "บีบอัดเซสชัน",
|
"command.session.compact": "บีบอัดเซสชัน",
|
||||||
@@ -670,6 +669,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "ในเครื่อง",
|
"session.new.workspace.triggerLocal": "ในเครื่อง",
|
||||||
"session.new.workspace.local": "รีโพซิทอรีในเครื่อง",
|
"session.new.workspace.local": "รีโพซิทอรีในเครื่อง",
|
||||||
"session.new.workspace.existing": "พื้นที่ทำงาน…",
|
"session.new.workspace.existing": "พื้นที่ทำงาน…",
|
||||||
|
"session.new.git.none": "ไม่มี Git",
|
||||||
"session.new.lastModified": "แก้ไขล่าสุด",
|
"session.new.lastModified": "แก้ไขล่าสุด",
|
||||||
|
|
||||||
"session.header.search.placeholder": "ค้นหา {{project}}",
|
"session.header.search.placeholder": "ค้นหา {{project}}",
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Kenar çubuğunda birden fazla çalışma alanını göster veya gizle",
|
"command.workspace.toggle.description": "Kenar çubuğunda birden fazla çalışma alanını göster veya gizle",
|
||||||
"command.session.undo": "Geri al",
|
"command.session.undo": "Geri al",
|
||||||
"command.session.undo.description": "Son mesajı geri al",
|
"command.session.undo.description": "Son mesajı geri al",
|
||||||
"command.session.undo.gitRequired": "Git deposu algılanmadı",
|
|
||||||
"command.session.redo": "Yinele",
|
"command.session.redo": "Yinele",
|
||||||
"command.session.redo.description": "Son geri alınan mesajı yinele",
|
"command.session.redo.description": "Son geri alınan mesajı yinele",
|
||||||
"command.session.compact": "Oturumu sıkıştır",
|
"command.session.compact": "Oturumu sıkıştır",
|
||||||
@@ -680,6 +679,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Yerel",
|
"session.new.workspace.triggerLocal": "Yerel",
|
||||||
"session.new.workspace.local": "Yerel depo",
|
"session.new.workspace.local": "Yerel depo",
|
||||||
"session.new.workspace.existing": "Çalışma alanı…",
|
"session.new.workspace.existing": "Çalışma alanı…",
|
||||||
|
"session.new.git.none": "Git yok",
|
||||||
"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",
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "Увімкнути або вимкнути декілька робочих областей на бічній панелі",
|
"command.workspace.toggle.description": "Увімкнути або вимкнути декілька робочих областей на бічній панелі",
|
||||||
"command.session.undo": "Скасувати",
|
"command.session.undo": "Скасувати",
|
||||||
"command.session.undo.description": "Скасувати останнє повідомлення",
|
"command.session.undo.description": "Скасувати останнє повідомлення",
|
||||||
"command.session.undo.gitRequired": "Репозиторій Git не виявлено",
|
|
||||||
"command.session.redo": "Повторити",
|
"command.session.redo": "Повторити",
|
||||||
"command.session.redo.description": "Повторити останнє скасоване повідомлення",
|
"command.session.redo.description": "Повторити останнє скасоване повідомлення",
|
||||||
"command.session.compact": "Стиснути сесію",
|
"command.session.compact": "Стиснути сесію",
|
||||||
@@ -704,6 +703,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "Локально",
|
"session.new.workspace.triggerLocal": "Локально",
|
||||||
"session.new.workspace.local": "Локальний репозиторій",
|
"session.new.workspace.local": "Локальний репозиторій",
|
||||||
"session.new.workspace.existing": "Робоча область…",
|
"session.new.workspace.existing": "Робоча область…",
|
||||||
|
"session.new.git.none": "Немає Git",
|
||||||
"session.new.lastModified": "Востаннє змінено",
|
"session.new.lastModified": "Востаннє змінено",
|
||||||
|
|
||||||
"session.header.search.placeholder": "Пошук {{project}}",
|
"session.header.search.placeholder": "Пошук {{project}}",
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ export const dict = {
|
|||||||
|
|
||||||
"command.session.undo": "撤销",
|
"command.session.undo": "撤销",
|
||||||
"command.session.undo.description": "撤销上一条消息",
|
"command.session.undo.description": "撤销上一条消息",
|
||||||
"command.session.undo.gitRequired": "未检测到 Git 仓库",
|
|
||||||
"command.session.redo": "重做",
|
"command.session.redo": "重做",
|
||||||
"command.session.redo.description": "重做上一条撤销的消息",
|
"command.session.redo.description": "重做上一条撤销的消息",
|
||||||
"command.session.compact": "精简会话",
|
"command.session.compact": "精简会话",
|
||||||
@@ -667,6 +666,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "本地",
|
"session.new.workspace.triggerLocal": "本地",
|
||||||
"session.new.workspace.local": "本地仓库",
|
"session.new.workspace.local": "本地仓库",
|
||||||
"session.new.workspace.existing": "工作区…",
|
"session.new.workspace.existing": "工作区…",
|
||||||
|
"session.new.git.none": "无 Git",
|
||||||
"session.new.lastModified": "最后修改",
|
"session.new.lastModified": "最后修改",
|
||||||
"session.header.search.placeholder": "搜索 {{project}}",
|
"session.header.search.placeholder": "搜索 {{project}}",
|
||||||
"session.header.searchFiles": "搜索文件",
|
"session.header.searchFiles": "搜索文件",
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ export const dict = {
|
|||||||
"command.workspace.toggle.description": "在側邊欄啟用或停用多個工作區",
|
"command.workspace.toggle.description": "在側邊欄啟用或停用多個工作區",
|
||||||
"command.session.undo": "復原",
|
"command.session.undo": "復原",
|
||||||
"command.session.undo.description": "復原上一則訊息",
|
"command.session.undo.description": "復原上一則訊息",
|
||||||
"command.session.undo.gitRequired": "未偵測到 Git 儲存庫",
|
|
||||||
"command.session.redo": "重做",
|
"command.session.redo": "重做",
|
||||||
"command.session.redo.description": "重做上一則復原的訊息",
|
"command.session.redo.description": "重做上一則復原的訊息",
|
||||||
"command.session.compact": "精簡工作階段",
|
"command.session.compact": "精簡工作階段",
|
||||||
@@ -664,6 +663,7 @@ export const dict = {
|
|||||||
"session.new.workspace.triggerLocal": "本機",
|
"session.new.workspace.triggerLocal": "本機",
|
||||||
"session.new.workspace.local": "本機儲存庫",
|
"session.new.workspace.local": "本機儲存庫",
|
||||||
"session.new.workspace.existing": "工作區…",
|
"session.new.workspace.existing": "工作區…",
|
||||||
|
"session.new.git.none": "無 Git",
|
||||||
"session.new.lastModified": "最後修改",
|
"session.new.lastModified": "最後修改",
|
||||||
|
|
||||||
"session.header.search.placeholder": "搜尋 {{project}}",
|
"session.header.search.placeholder": "搜尋 {{project}}",
|
||||||
|
|||||||
+128
-25
@@ -17,6 +17,11 @@ import {
|
|||||||
} from "solid-js"
|
} from "solid-js"
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
|
import { DragDropProvider, PointerSensor } from "@dnd-kit/solid"
|
||||||
|
import { isSortable, useSortable } from "@dnd-kit/solid/sortable"
|
||||||
|
import { AutoScroller, Feedback, PointerActivationConstraints } from "@dnd-kit/dom"
|
||||||
|
import { RestrictToVerticalAxis } from "@dnd-kit/abstract/modifiers"
|
||||||
|
import { RestrictToElement } from "@dnd-kit/dom/modifiers"
|
||||||
import { useQuery } from "@tanstack/solid-query"
|
import { useQuery } from "@tanstack/solid-query"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Logo } from "@opencode-ai/ui/logo"
|
import { Logo } from "@opencode-ai/ui/logo"
|
||||||
@@ -1037,7 +1042,7 @@ function HomeServerRow(props: {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function HomeProjectList(props: {
|
type HomeProjectListProps = {
|
||||||
server: ServerConnection.Any
|
server: ServerConnection.Any
|
||||||
projects: LocalProject[]
|
projects: LocalProject[]
|
||||||
selected: HomeProjectSelection
|
selected: HomeProjectSelection
|
||||||
@@ -1048,29 +1053,82 @@ function HomeProjectList(props: {
|
|||||||
clearNotifications: (server: ServerConnection.Any, project: LocalProject) => void
|
clearNotifications: (server: ServerConnection.Any, project: LocalProject) => void
|
||||||
unseenCount: (server: ServerConnection.Any, project: LocalProject) => number
|
unseenCount: (server: ServerConnection.Any, project: LocalProject) => number
|
||||||
language: ReturnType<typeof useLanguage>
|
language: ReturnType<typeof useLanguage>
|
||||||
}) {
|
}
|
||||||
|
|
||||||
|
function HomeProjectList(props: HomeProjectListProps) {
|
||||||
|
const global = useGlobal()
|
||||||
|
let listRef!: HTMLDivElement
|
||||||
|
const projects = () => global.ensureServerCtx(props.server).projects
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex min-w-0 flex-col gap-1">
|
<DragDropProvider
|
||||||
<For each={props.projects}>
|
sensors={(defaults) => [
|
||||||
{(project) => (
|
...defaults.filter((sensor) => sensor !== PointerSensor),
|
||||||
<HomeProjectRow
|
PointerSensor.configure({
|
||||||
project={project}
|
activationConstraints: (event) =>
|
||||||
server={props.server}
|
event.pointerType === "touch"
|
||||||
selected={
|
? [new PointerActivationConstraints.Delay({ value: 250, tolerance: 5 })]
|
||||||
props.selected.server === ServerConnection.key(props.server) &&
|
: [new PointerActivationConstraints.Distance({ value: 4 })],
|
||||||
props.selected.directory === project.worktree
|
preventActivation: (event) => event.target instanceof Element && !!event.target.closest("[data-action]"),
|
||||||
}
|
}),
|
||||||
unseenCount={props.unseenCount(props.server, project)}
|
]}
|
||||||
selectProject={props.selectProject}
|
modifiers={[RestrictToVerticalAxis, RestrictToElement.configure({ element: () => listRef })]}
|
||||||
openNewSession={props.openNewSession}
|
plugins={(defaults) => [
|
||||||
editProject={props.editProject}
|
...defaults.filter((plugin) => plugin !== AutoScroller && plugin !== Feedback),
|
||||||
closeProject={props.closeProject}
|
AutoScroller.configure({ acceleration: 8, threshold: { x: 0, y: 0.05 } }),
|
||||||
clearNotifications={props.clearNotifications}
|
Feedback.configure({ dropAnimation: null }),
|
||||||
language={props.language}
|
]}
|
||||||
/>
|
onDragEnd={(event) => {
|
||||||
)}
|
const source = event.operation.source
|
||||||
</For>
|
if (event.canceled || !isSortable(source)) return
|
||||||
</div>
|
if (source.initialIndex !== source.index) projects().move(source.id.toString(), source.index)
|
||||||
|
if (props.selected.server !== ServerConnection.key(props.server))
|
||||||
|
props.selectProject(props.server, source.id.toString())
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="flex min-w-0 flex-col gap-1" ref={listRef}>
|
||||||
|
{/* Keyed on worktree strings: the enriched project objects are
|
||||||
|
recreated on every store or sync update, so iterating them directly
|
||||||
|
remounts all rows — killing any in-flight drag activation (the
|
||||||
|
row's sortable unregisters on unmount) and discarding animations.
|
||||||
|
String keys keep row elements alive and move them on reorder. */}
|
||||||
|
<For each={props.projects.map((project) => project.worktree)}>
|
||||||
|
{(worktree, index) => <HomeProjectSlot {...props} worktree={worktree} index={index} />}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</DragDropProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function HomeProjectSlot(
|
||||||
|
props: HomeProjectListProps & {
|
||||||
|
worktree: string
|
||||||
|
index: () => number
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const project = createMemo(() => props.projects.find((item) => item.worktree === props.worktree))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={project()}>
|
||||||
|
{(item) => (
|
||||||
|
<HomeProjectRow
|
||||||
|
project={item()}
|
||||||
|
server={props.server}
|
||||||
|
index={props.index}
|
||||||
|
serverSelected={props.selected.server === ServerConnection.key(props.server)}
|
||||||
|
selected={
|
||||||
|
props.selected.server === ServerConnection.key(props.server) && props.selected.directory === props.worktree
|
||||||
|
}
|
||||||
|
unseenCount={props.unseenCount(props.server, item())}
|
||||||
|
selectProject={props.selectProject}
|
||||||
|
openNewSession={props.openNewSession}
|
||||||
|
editProject={props.editProject}
|
||||||
|
closeProject={props.closeProject}
|
||||||
|
clearNotifications={props.clearNotifications}
|
||||||
|
language={props.language}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1150,6 +1208,8 @@ function HomeRecentlyClosedRow(props: {
|
|||||||
function HomeProjectRow(props: {
|
function HomeProjectRow(props: {
|
||||||
project: LocalProject
|
project: LocalProject
|
||||||
server: ServerConnection.Any
|
server: ServerConnection.Any
|
||||||
|
index: () => number
|
||||||
|
serverSelected: boolean
|
||||||
selected: boolean
|
selected: boolean
|
||||||
unseenCount: number
|
unseenCount: number
|
||||||
selectProject: (server: ServerConnection.Any, directory: string) => void
|
selectProject: (server: ServerConnection.Any, directory: string) => void
|
||||||
@@ -1163,6 +1223,15 @@ function HomeProjectRow(props: {
|
|||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const serverUnreachable = () => global.servers.health[ServerConnection.key(props.server)]?.healthy === false
|
const serverUnreachable = () => global.servers.health[ServerConnection.key(props.server)]?.healthy === false
|
||||||
const [state, setState] = createStore({ menuOpen: false })
|
const [state, setState] = createStore({ menuOpen: false })
|
||||||
|
const sortable = useSortable({
|
||||||
|
get id() {
|
||||||
|
return props.project.worktree
|
||||||
|
},
|
||||||
|
get index() {
|
||||||
|
return props.index()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
let pointerDownSelected: boolean | undefined
|
||||||
const canRevealInFileManager = () =>
|
const canRevealInFileManager = () =>
|
||||||
platform.platform === "desktop" && !!platform.openPath && ServerConnection.local(props.server)
|
platform.platform === "desktop" && !!platform.openPath && ServerConnection.local(props.server)
|
||||||
const fileManagerActionLabel = () =>
|
const fileManagerActionLabel = () =>
|
||||||
@@ -1179,15 +1248,49 @@ function HomeProjectRow(props: {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div class="group/project relative flex h-7 min-w-0 items-center rounded-[6px]">
|
<div
|
||||||
|
ref={sortable.ref}
|
||||||
|
class="group/project relative flex h-7 min-w-0 items-center rounded-[6px]"
|
||||||
|
classList={{ "z-10": sortable.isDragSource() }}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-component="home-project-row"
|
data-component="home-project-row"
|
||||||
class={`${HOME_PROJECT_NAV_ROW} pr-16 disabled:opacity-60`}
|
class={`${HOME_PROJECT_NAV_ROW} pr-16 disabled:opacity-60`}
|
||||||
|
classList={{
|
||||||
|
"bg-v2-background-bg-layer-01 text-v2-text-text-base [box-shadow:inset_0_0_0_0.5px_var(--v2-border-border-muted)]":
|
||||||
|
sortable.isDragSource(),
|
||||||
|
}}
|
||||||
data-selected={props.selected ? "" : undefined}
|
data-selected={props.selected ? "" : undefined}
|
||||||
aria-current={props.selected ? "page" : undefined}
|
aria-current={props.selected ? "page" : undefined}
|
||||||
disabled={serverUnreachable()}
|
disabled={serverUnreachable()}
|
||||||
onClick={() => props.selectProject(props.server, props.project.worktree)}
|
onPointerDown={(event) => {
|
||||||
|
// Same-server mouse selection happens on pointerdown (like tabs),
|
||||||
|
// but only ever selects; selectProject toggles, and deselecting here
|
||||||
|
// would fire on every drag before the threshold is met. Cross-server
|
||||||
|
// selection waits for click so reordering a remote server's projects
|
||||||
|
// does not focus that server and load its session index. Touch is
|
||||||
|
// excluded so flick-scrolling the list cannot select rows.
|
||||||
|
pointerDownSelected = undefined
|
||||||
|
if (event.button !== 0 || event.pointerType === "touch") return
|
||||||
|
if (!props.serverSelected) return
|
||||||
|
pointerDownSelected = props.selected
|
||||||
|
if (!props.selected) props.selectProject(props.server, props.project.worktree)
|
||||||
|
}}
|
||||||
|
onClick={(event) => {
|
||||||
|
// The drag sensor calls preventDefault on post-drag clicks; never
|
||||||
|
// toggle selection as part of a reorder.
|
||||||
|
if (event.defaultPrevented) return
|
||||||
|
// Keyboard activation and touch taps keep the original toggle.
|
||||||
|
if (event.detail === 0 || pointerDownSelected === undefined) {
|
||||||
|
props.selectProject(props.server, props.project.worktree)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Mouse: pointerdown already selected unselected rows; a plain click
|
||||||
|
// on an already-selected row toggles it off.
|
||||||
|
if (pointerDownSelected) props.selectProject(props.server, props.project.worktree)
|
||||||
|
pointerDownSelected = undefined
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<HomeProjectAvatar project={props.project} />
|
<HomeProjectAvatar project={props.project} />
|
||||||
<span class={HOME_PROJECT_NAV_LABEL}>{displayName(props.project)}</span>
|
<span class={HOME_PROJECT_NAV_LABEL}>{displayName(props.project)}</span>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createEffect, Suspense, type ParentProps } from "solid-js"
|
import { createEffect, Suspense, type ParentProps } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { useNavigate } from "@solidjs/router"
|
import { useNavigate } from "@solidjs/router"
|
||||||
import { DebugBar } from "@/components/debug-bar"
|
import { DebugBar } from "@/components/debug-bar"
|
||||||
import { TabsInfoPopup } from "@/components/help-button"
|
import { TabsInfoPopup } from "@/components/help-button"
|
||||||
@@ -11,6 +12,7 @@ export default function NewLayout(props: ParentProps) {
|
|||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
setNavigate(navigate)
|
setNavigate(navigate)
|
||||||
|
const [state, setState] = createStore({ debugTools: true })
|
||||||
|
|
||||||
createEffect(() => setV2Toast(true))
|
createEffect(() => setV2Toast(true))
|
||||||
|
|
||||||
@@ -32,11 +34,18 @@ export default function NewLayout(props: ParentProps) {
|
|||||||
"padding-bottom": "env(safe-area-inset-bottom, 0px)",
|
"padding-bottom": "env(safe-area-inset-bottom, 0px)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Titlebar update={update} />
|
<Titlebar
|
||||||
|
update={update}
|
||||||
|
debugTools={
|
||||||
|
import.meta.env.DEV
|
||||||
|
? { visible: state.debugTools, toggle: () => setState("debugTools", (value) => !value) }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
<main class="flex-1 min-h-0 min-w-0 overflow-x-hidden flex flex-col items-start contain-strict">
|
<main class="flex-1 min-h-0 min-w-0 overflow-x-hidden flex flex-col items-start contain-strict">
|
||||||
<Suspense>{props.children}</Suspense>
|
<Suspense>{props.children}</Suspense>
|
||||||
</main>
|
</main>
|
||||||
{import.meta.env.DEV && <DebugBar inline />}
|
{import.meta.env.DEV && state.debugTools && <DebugBar inline />}
|
||||||
<TabsInfoPopup />
|
<TabsInfoPopup />
|
||||||
<ToastRegion v2 />
|
<ToastRegion v2 />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ export default function LegacyLayout(props: ParentProps) {
|
|||||||
sizing: false,
|
sizing: false,
|
||||||
peek: undefined as string | undefined,
|
peek: undefined as string | undefined,
|
||||||
peeked: false,
|
peeked: false,
|
||||||
|
debugTools: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateVersion = () => {
|
const updateVersion = () => {
|
||||||
@@ -2248,7 +2249,14 @@ export default function LegacyLayout(props: ParentProps) {
|
|||||||
return (
|
return (
|
||||||
<div class="relative bg-background-base flex-1 min-h-0 min-w-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text">
|
<div class="relative bg-background-base flex-1 min-h-0 min-w-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text">
|
||||||
{autoselecting() ?? ""}
|
{autoselecting() ?? ""}
|
||||||
<Titlebar update={titlebarUpdate} />
|
<Titlebar
|
||||||
|
update={titlebarUpdate}
|
||||||
|
debugTools={
|
||||||
|
import.meta.env.DEV && import.meta.env.VITE_DISABLE_DEBUG_BAR !== "1"
|
||||||
|
? { visible: state.debugTools, toggle: () => setState("debugTools", (value) => !value) }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Show when={updateVersion() !== undefined}>
|
<Show when={updateVersion() !== undefined}>
|
||||||
<UpdateAvailableToast version={updateVersion() ?? ""} install={installUpdate} language={language} />
|
<UpdateAvailableToast version={updateVersion() ?? ""} install={installUpdate} language={language} />
|
||||||
</Show>
|
</Show>
|
||||||
@@ -2393,7 +2401,7 @@ export default function LegacyLayout(props: ParentProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{import.meta.env.DEV && import.meta.env.VITE_DISABLE_DEBUG_BAR !== "1" && <DebugBar />}
|
{import.meta.env.DEV && import.meta.env.VITE_DISABLE_DEBUG_BAR !== "1" && state.debugTools && <DebugBar />}
|
||||||
</div>
|
</div>
|
||||||
<TabsInfoPopup />
|
<TabsInfoPopup />
|
||||||
<ToastRegion v2={false} />
|
<ToastRegion v2={false} />
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export function useSessionTabAvatarState(
|
|||||||
const global = useGlobal()
|
const global = useGlobal()
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
const permissionState = createMemo(() => permission.ensureServerState(server()))
|
|
||||||
const connection = createMemo(() => global.servers.list().find((item) => ServerConnection.key(item) === server()))
|
const connection = createMemo(() => global.servers.list().find((item) => ServerConnection.key(item) === server()))
|
||||||
const sync = createMemo(() => {
|
const sync = createMemo(() => {
|
||||||
const conn = connection()
|
const conn = connection()
|
||||||
@@ -22,9 +21,10 @@ export function useSessionTabAvatarState(
|
|||||||
const hasPermissions = createMemo(() => {
|
const hasPermissions = createMemo(() => {
|
||||||
const serverSync = sync()
|
const serverSync = sync()
|
||||||
if (!serverSync) return false
|
if (!serverSync) return false
|
||||||
|
const permissionState = permission.ensureServerState(server())
|
||||||
const [store] = serverSync.child(directory(), { bootstrap: false })
|
const [store] = serverSync.child(directory(), { bootstrap: false })
|
||||||
return !!sessionPermissionRequest(store.session, serverSync.session.data.permission, sessionId(), (item) => {
|
return !!sessionPermissionRequest(store.session, serverSync.session.data.permission, sessionId(), (item) => {
|
||||||
return !permissionState().autoResponds(item, directory())
|
return !permissionState.autoResponds(item, directory())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const hasQuestions = createMemo(() => {
|
const hasQuestions = createMemo(() => {
|
||||||
@@ -34,9 +34,11 @@ export function useSessionTabAvatarState(
|
|||||||
return !!sessionQuestionRequest(store.session, serverSync.session.data.question, sessionId())
|
return !!sessionQuestionRequest(store.session, serverSync.session.data.question, sessionId())
|
||||||
})
|
})
|
||||||
const needsAttention = createMemo(() => hasPermissions() || hasQuestions())
|
const needsAttention = createMemo(() => hasPermissions() || hasQuestions())
|
||||||
const unread = createMemo(
|
const notificationState = createMemo(() => {
|
||||||
() => needsAttention() || notification.ensureServerState(server()).session.unseenCount(sessionId()) > 0,
|
if (!connection()) return
|
||||||
)
|
return notification.ensureServerState(server())
|
||||||
|
})
|
||||||
|
const unread = createMemo(() => needsAttention() || (notificationState()?.session.unseenCount(sessionId()) ?? 0) > 0)
|
||||||
const loading = createMemo(() => {
|
const loading = createMemo(() => {
|
||||||
const serverSync = sync()
|
const serverSync = sync()
|
||||||
if (!serverSync) return false
|
if (!serverSync) return false
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { createPromptInputController, createPromptProjectControls } from "@/page
|
|||||||
import { useSessionKey } from "@/pages/session/session-layout"
|
import { useSessionKey } from "@/pages/session/session-layout"
|
||||||
import { useComposerCommands } from "@/pages/session/use-composer-commands"
|
import { useComposerCommands } from "@/pages/session/use-composer-commands"
|
||||||
import { NEW_SESSION_CONTENT_WIDTH } from "@/pages/session/new-session-layout"
|
import { NEW_SESSION_CONTENT_WIDTH } from "@/pages/session/new-session-layout"
|
||||||
import { PromptWorkspaceSelector } from "@/components/prompt-workspace-selector"
|
import { PromptGitStatus, PromptWorkspaceSelector } from "@/components/prompt-workspace-selector"
|
||||||
import { useTitlebarRightMount } from "@/components/titlebar"
|
import { useTitlebarRightMount } from "@/components/titlebar"
|
||||||
import { useCommand } from "@/context/command"
|
import { useCommand } from "@/context/command"
|
||||||
import { useProviders } from "@/hooks/use-providers"
|
import { useProviders } from "@/hooks/use-providers"
|
||||||
@@ -168,24 +168,18 @@ export default function NewSessionPage() {
|
|||||||
<div class="flex-1 min-h-0 overflow-hidden rounded-[10px]">
|
<div class="flex-1 min-h-0 overflow-hidden rounded-[10px]">
|
||||||
<NewSessionDesignView>
|
<NewSessionDesignView>
|
||||||
<div class={NEW_SESSION_CONTENT_WIDTH}>
|
<div class={NEW_SESSION_CONTENT_WIDTH}>
|
||||||
<div class="flex flex-col" classList={{ "gap-8": showWorkspaceBar(), "gap-3": !showWorkspaceBar() }}>
|
<div class="flex flex-col gap-8">
|
||||||
<PromptInputV2Composer controller={promptInputV2Controller} />
|
<PromptInputV2Composer controller={promptInputV2Controller} />
|
||||||
<Show when={projectController.empty()}>
|
<Show when={projectController.empty()}>
|
||||||
<PromptProjectAddButton controller={projectController} />
|
<PromptProjectAddButton controller={projectController} />
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={projectController.selected()}>
|
<Show when={projectController.selected()}>
|
||||||
<div
|
<div class="flex min-h-7 min-w-0 flex-col items-center justify-center gap-0 text-v2-text-text-faint sm:flex-row">
|
||||||
class="flex min-h-7 min-w-0 items-center gap-0 text-v2-text-text-faint"
|
<PromptProjectSelector controller={projectController} placement="bottom" />
|
||||||
classList={{
|
<Show
|
||||||
"flex-col justify-center sm:flex-row": showWorkspaceBar(),
|
when={showWorkspaceBar()}
|
||||||
"justify-start": !showWorkspaceBar(),
|
fallback={<PromptGitStatus branch={selectedBranch()} noGit={sync().project?.vcs !== "git"} />}
|
||||||
}}
|
>
|
||||||
>
|
|
||||||
<PromptProjectSelector
|
|
||||||
controller={projectController}
|
|
||||||
placement={showWorkspaceBar() ? "bottom" : "bottom-start"}
|
|
||||||
/>
|
|
||||||
<Show when={showWorkspaceBar()}>
|
|
||||||
<PromptWorkspaceSelector
|
<PromptWorkspaceSelector
|
||||||
value={newSessionWorktree()}
|
value={newSessionWorktree()}
|
||||||
projectRoot={projectRoot()}
|
projectRoot={projectRoot()}
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ import { TerminalPanel } from "@/pages/session/terminal-panel"
|
|||||||
import { TerminalPanelV2 } from "@/pages/session/terminal-panel-v2"
|
import { TerminalPanelV2 } from "@/pages/session/terminal-panel-v2"
|
||||||
import { useComposerCommands } from "@/pages/session/use-composer-commands"
|
import { useComposerCommands } from "@/pages/session/use-composer-commands"
|
||||||
import { useSessionCommands } from "@/pages/session/use-session-commands"
|
import { useSessionCommands } from "@/pages/session/use-session-commands"
|
||||||
import { supportsSessionHistory } from "@/pages/session/session-history-actions"
|
|
||||||
import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll"
|
import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll"
|
||||||
import { Identifier } from "@/utils/id"
|
import { Identifier } from "@/utils/id"
|
||||||
import { diffs as list } from "@/utils/diffs"
|
import { diffs as list } from "@/utils/diffs"
|
||||||
@@ -651,7 +650,6 @@ export default function Page() {
|
|||||||
}, desktopReviewOpen())
|
}, desktopReviewOpen())
|
||||||
|
|
||||||
const turnDiffs = createMemo(() => list(lastUserMessage()?.summary?.diffs))
|
const turnDiffs = createMemo(() => list(lastUserMessage()?.summary?.diffs))
|
||||||
const git = createMemo(() => supportsSessionHistory(sync().project?.vcs))
|
|
||||||
const nogit = createMemo(() => {
|
const nogit = createMemo(() => {
|
||||||
const project = sync().project
|
const project = sync().project
|
||||||
return !!project && project.vcs !== "git"
|
return !!project && project.vcs !== "git"
|
||||||
@@ -670,7 +668,8 @@ export default function Page() {
|
|||||||
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
|
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
|
||||||
const wantsReview = createMemo(() =>
|
const wantsReview = createMemo(() =>
|
||||||
isDesktop()
|
isDesktop()
|
||||||
? desktopFileTreeOpen() || (desktopReviewOpen() && activeTab() === "review")
|
? desktopFileTreeOpen() ||
|
||||||
|
(desktopReviewOpen() && (activeTab() === "review" || (newSessionDesign() && !!activeFileTab())))
|
||||||
: store.mobileTab === "changes",
|
: store.mobileTab === "changes",
|
||||||
)
|
)
|
||||||
const vcsMode = createMemo<VcsMode | undefined>(() => {
|
const vcsMode = createMemo<VcsMode | undefined>(() => {
|
||||||
@@ -1917,12 +1916,12 @@ export default function Page() {
|
|||||||
const restoring = createMemo(() => (restoreMutation.isPending ? restoreMutation.variables : undefined))
|
const restoring = createMemo(() => (restoreMutation.isPending ? restoreMutation.variables : undefined))
|
||||||
|
|
||||||
const revert = (input: { sessionID: string; messageID: string }) => {
|
const revert = (input: { sessionID: string; messageID: string }) => {
|
||||||
if (reverting() || !git()) return
|
if (reverting()) return
|
||||||
return revertMutation.mutateAsync(input)
|
return revertMutation.mutateAsync(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
const restore = (id: string) => {
|
const restore = (id: string) => {
|
||||||
if (!params.id || reverting() || !git()) return
|
if (!params.id || reverting()) return
|
||||||
return restoreMutation.mutateAsync(id)
|
return restoreMutation.mutateAsync(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1957,16 +1956,7 @@ export default function Page() {
|
|||||||
download()
|
download()
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = { revert, openAttachment }
|
||||||
revert,
|
|
||||||
get revertDisabled() {
|
|
||||||
return !git()
|
|
||||||
},
|
|
||||||
get revertDisabledReason() {
|
|
||||||
return nogit() ? language.t("command.session.undo.gitRequired") : undefined
|
|
||||||
},
|
|
||||||
openAttachment,
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
@@ -2198,8 +2188,7 @@ export default function Page() {
|
|||||||
? {
|
? {
|
||||||
items: rolled(),
|
items: rolled(),
|
||||||
restoring: restoring(),
|
restoring: restoring(),
|
||||||
disabled: reverting() || !git(),
|
disabled: reverting(),
|
||||||
disabledReason: nogit() ? language.t("command.session.undo.gitRequired") : undefined,
|
|
||||||
onRestore: restore,
|
onRestore: restore,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -2277,6 +2266,7 @@ export default function Page() {
|
|||||||
return (
|
return (
|
||||||
<PromptInputV2Composer
|
<PromptInputV2Composer
|
||||||
controller={controller}
|
controller={controller}
|
||||||
|
borderUnderlay
|
||||||
edit={editingFollowup()}
|
edit={editingFollowup()}
|
||||||
onEditLoaded={clearFollowupEdit}
|
onEditLoaded={clearFollowupEdit}
|
||||||
/>
|
/>
|
||||||
@@ -2377,6 +2367,9 @@ export default function Page() {
|
|||||||
reviewHasFocusableContent={() => hasReview() || reviewV2State.sidebarOpened()}
|
reviewHasFocusableContent={() => hasReview() || reviewV2State.sidebarOpened()}
|
||||||
reviewCount={reviewCount}
|
reviewCount={reviewCount}
|
||||||
reviewPanel={reviewPanelV2}
|
reviewPanel={reviewPanelV2}
|
||||||
|
diffVersion={vcsQuery.dataUpdatedAt}
|
||||||
|
loadDiff={loadReviewDiff}
|
||||||
|
expandUnchanged={reviewV2State.expandMode() === "expand"}
|
||||||
reviewSidebarToggle={(disabled) => (
|
reviewSidebarToggle={(disabled) => (
|
||||||
<SessionReviewV2SidebarToggle
|
<SessionReviewV2SidebarToggle
|
||||||
opened={reviewV2State.sidebarOpened()}
|
opened={reviewV2State.sidebarOpened()}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export type SessionComposerRevertDock = {
|
|||||||
items: { id: string; text: string }[]
|
items: { id: string; text: string }[]
|
||||||
restoring?: string
|
restoring?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
disabledReason?: string
|
|
||||||
onRestore: (id: string) => void
|
onRestore: (id: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ export function SessionComposerRegion(props: {
|
|||||||
items={revert.items}
|
items={revert.items}
|
||||||
restoring={revert.restoring}
|
restoring={revert.restoring}
|
||||||
disabled={revert.disabled}
|
disabled={revert.disabled}
|
||||||
disabledReason={revert.disabledReason}
|
|
||||||
onRestore={revert.onRestore}
|
onRestore={revert.onRestore}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,7 +118,6 @@ export function SessionComposerRegion(props: {
|
|||||||
items={revert.items}
|
items={revert.items}
|
||||||
restoring={revert.restoring}
|
restoring={revert.restoring}
|
||||||
disabled={revert.disabled}
|
disabled={revert.disabled}
|
||||||
disabledReason={revert.disabledReason}
|
|
||||||
onRestore={revert.onRestore}
|
onRestore={revert.onRestore}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { For } from "solid-js"
|
import { For } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
|
import { DockShell } from "@opencode-ai/ui/dock-surface"
|
||||||
import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock"
|
import { SessionRevertDock } from "@/pages/session/composer/session-revert-dock"
|
||||||
import { SettingsProvider, useSettings } from "@/context/settings"
|
import { SettingsProvider, useSettings } from "@/context/settings"
|
||||||
|
|
||||||
@@ -78,12 +79,17 @@ function Stage(props: { count: number }) {
|
|||||||
{/* Reproduce the real composer stack: dock + card overlapping the dock's bottom by lift() = 18px */}
|
{/* Reproduce the real composer stack: dock + card overlapping the dock's bottom by lift() = 18px */}
|
||||||
<div style={{ display: "flex", "flex-direction": "column" }}>
|
<div style={{ display: "flex", "flex-direction": "column" }}>
|
||||||
<SessionRevertDock items={store.items} onRestore={restore} />
|
<SessionRevertDock items={store.items} onRestore={restore} />
|
||||||
<div
|
<DockShell
|
||||||
|
data-dock-border-underlay={v2() ? "v2" : "legacy"}
|
||||||
style={{ position: "relative", "z-index": 70, "margin-top": "-18px" }}
|
style={{ position: "relative", "z-index": 70, "margin-top": "-18px" }}
|
||||||
class="min-h-24 w-full rounded-[12px] border border-v2-border-border-base bg-v2-background-bg-base px-4 py-3 text-[13px] text-v2-text-text-faint"
|
classList={{
|
||||||
|
"min-h-24 w-full rounded-[12px] px-4 py-3 text-[13px]": true,
|
||||||
|
"bg-v2-background-bg-base text-v2-text-text-faint": v2(),
|
||||||
|
"text-text-weak": !v2(),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Ask anything...
|
Ask anything...
|
||||||
</div>
|
</DockShell>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-[12px] text-v2-text-text-faint">
|
<div class="text-[12px] text-v2-text-text-faint">
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
|||||||
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||||
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
|
||||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
|
||||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
|
|
||||||
@@ -15,7 +13,6 @@ export function SessionRevertDock(props: {
|
|||||||
items: { id: string; text: string }[]
|
items: { id: string; text: string }[]
|
||||||
restoring?: string
|
restoring?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
disabledReason?: string
|
|
||||||
onRestore: (id: string) => void
|
onRestore: (id: string) => void
|
||||||
}) {
|
}) {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
@@ -92,17 +89,15 @@ export function SessionRevertDock(props: {
|
|||||||
{(item) => (
|
{(item) => (
|
||||||
<div class="flex items-center gap-2 min-w-0 py-1">
|
<div class="flex items-center gap-2 min-w-0 py-1">
|
||||||
<span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span>
|
<span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span>
|
||||||
<Tooltip inactive={!props.disabledReason} value={props.disabledReason} placement="top">
|
<Button
|
||||||
<Button
|
size="small"
|
||||||
size="small"
|
variant="secondary"
|
||||||
variant="secondary"
|
class="shrink-0"
|
||||||
class="shrink-0"
|
disabled={props.disabled || !!props.restoring}
|
||||||
disabled={props.disabled || !!props.restoring}
|
onClick={() => props.onRestore(item.id)}
|
||||||
onClick={() => props.onRestore(item.id)}
|
>
|
||||||
>
|
{language.t("session.revertDock.restore")}
|
||||||
{language.t("session.revertDock.restore")}
|
</Button>
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
@@ -172,17 +167,15 @@ export function SessionRevertDock(props: {
|
|||||||
<span class="min-w-0 flex-1 truncate text-[13px] font-[400] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
<span class="min-w-0 flex-1 truncate text-[13px] font-[400] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
||||||
{item.text}
|
{item.text}
|
||||||
</span>
|
</span>
|
||||||
<TooltipV2 inactive={!props.disabledReason} value={props.disabledReason} placement="top">
|
<ButtonV2
|
||||||
<ButtonV2
|
size="small"
|
||||||
size="small"
|
variant="neutral"
|
||||||
variant="neutral"
|
class="shrink-0"
|
||||||
class="shrink-0"
|
disabled={props.disabled || !!props.restoring}
|
||||||
disabled={props.disabled || !!props.restoring}
|
onClick={() => props.onRestore(item.id)}
|
||||||
onClick={() => props.onRestore(item.id)}
|
>
|
||||||
>
|
{language.t("session.revertDock.restore")}
|
||||||
{language.t("session.revertDock.restore")}
|
</ButtonV2>
|
||||||
</ButtonV2>
|
|
||||||
</TooltipV2>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import { TextReveal } from "@opencode-ai/ui/text-reveal"
|
|||||||
import { TextStrikethrough } from "@opencode-ai/ui/text-strikethrough"
|
import { TextStrikethrough } from "@opencode-ai/ui/text-strikethrough"
|
||||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||||
import { Index, createEffect, createMemo } from "solid-js"
|
import { Index, createEffect, createMemo } from "solid-js"
|
||||||
|
import { Dynamic } from "solid-js/web"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { useSettings } from "@/context/settings"
|
||||||
|
|
||||||
const doneToken = "\u0000done\u0000"
|
const doneToken = "\u0000done\u0000"
|
||||||
const totalToken = "\u0000total\u0000"
|
const totalToken = "\u0000total\u0000"
|
||||||
@@ -48,6 +50,7 @@ export function SessionTodoDock(props: {
|
|||||||
dockProgress: number
|
dockProgress: number
|
||||||
}) {
|
}) {
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const settings = useSettings()
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
height: 78,
|
height: 78,
|
||||||
})
|
})
|
||||||
@@ -91,8 +94,13 @@ export function SessionTodoDock(props: {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DockTray
|
<Dynamic
|
||||||
|
component={settings.general.newLayoutDesigns() ? "div" : DockTray}
|
||||||
data-component="session-todo-dock"
|
data-component="session-todo-dock"
|
||||||
|
classList={{
|
||||||
|
"w-full overflow-hidden rounded-xl border-[0.5px] border-v2-border-border-base bg-v2-background-bg-layer-01":
|
||||||
|
settings.general.newLayoutDesigns(),
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
"overflow-x": "visible",
|
"overflow-x": "visible",
|
||||||
"overflow-y": "hidden",
|
"overflow-y": "hidden",
|
||||||
@@ -102,7 +110,11 @@ export function SessionTodoDock(props: {
|
|||||||
<div ref={contentRef}>
|
<div ref={contentRef}>
|
||||||
<div
|
<div
|
||||||
data-action="session-todo-toggle"
|
data-action="session-todo-toggle"
|
||||||
class="pl-3 pr-2 py-2 flex items-center gap-2 overflow-visible"
|
classList={{
|
||||||
|
"flex items-center gap-2 overflow-visible": true,
|
||||||
|
"h-[42px] pl-4 pr-2": settings.general.newLayoutDesigns(),
|
||||||
|
"pl-3 pr-2 py-2": !settings.general.newLayoutDesigns(),
|
||||||
|
}}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={props.onToggle}
|
onClick={props.onToggle}
|
||||||
@@ -113,7 +125,12 @@ export function SessionTodoDock(props: {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="text-14-regular text-text-strong cursor-default inline-flex items-baseline shrink-0 overflow-visible"
|
classList={{
|
||||||
|
"cursor-default inline-flex items-baseline shrink-0 overflow-visible": true,
|
||||||
|
"font-[440] text-[13px] leading-5 tracking-[-0.04px] text-v2-text-text-muted":
|
||||||
|
settings.general.newLayoutDesigns(),
|
||||||
|
"text-14-regular text-text-strong": !settings.general.newLayoutDesigns(),
|
||||||
|
}}
|
||||||
aria-label={label()}
|
aria-label={label()}
|
||||||
style={{
|
style={{
|
||||||
"--tool-motion-odometer-ms": "600ms",
|
"--tool-motion-odometer-ms": "600ms",
|
||||||
@@ -145,7 +162,11 @@ export function SessionTodoDock(props: {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TextReveal
|
<TextReveal
|
||||||
class="text-14-regular text-text-base cursor-default"
|
class={
|
||||||
|
settings.general.newLayoutDesigns()
|
||||||
|
? "cursor-default text-[13px] font-[440] leading-5 tracking-[-0.04px] text-v2-text-text-faint"
|
||||||
|
: "text-14-regular text-text-base cursor-default"
|
||||||
|
}
|
||||||
text={props.collapsed ? preview() : undefined}
|
text={props.collapsed ? preview() : undefined}
|
||||||
duration={600}
|
duration={600}
|
||||||
travel={25}
|
travel={25}
|
||||||
@@ -191,7 +212,7 @@ export function SessionTodoDock(props: {
|
|||||||
<TodoList todos={props.todos} />
|
<TodoList todos={props.todos} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DockTray>
|
</Dynamic>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createEffect, createMemo, createSignal, Match, on, onCleanup, Show, Switch } from "solid-js"
|
import { createEffect, createMemo, createResource, createSignal, Match, on, onCleanup, Show, Switch } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { Dynamic } from "solid-js/web"
|
import { Dynamic } from "solid-js/web"
|
||||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||||
@@ -8,6 +8,7 @@ import { cloneSelectedLineRange, previewSelectedLines } from "@opencode-ai/sessi
|
|||||||
import { createLineCommentController } from "@opencode-ai/session-ui/line-comment-annotations"
|
import { createLineCommentController } from "@opencode-ai/session-ui/line-comment-annotations"
|
||||||
import { createLineCommentControllerV2 } from "@opencode-ai/session-ui/v2/line-comment-annotations-v2"
|
import { createLineCommentControllerV2 } from "@opencode-ai/session-ui/v2/line-comment-annotations-v2"
|
||||||
import { sampledChecksum } from "@opencode-ai/core/util/encode"
|
import { sampledChecksum } from "@opencode-ai/core/util/encode"
|
||||||
|
import { normalize, text } from "@opencode-ai/session-ui/session-diff"
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { LineCommentV2OverflowIcon } from "@opencode-ai/ui/v2/line-comment-v2"
|
import { LineCommentV2OverflowIcon } from "@opencode-ai/ui/v2/line-comment-v2"
|
||||||
@@ -18,11 +19,23 @@ import { showToast } from "@/utils/toast"
|
|||||||
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
import { selectionFromLines, useFile, type FileSelection, type SelectedLineRange } from "@/context/file"
|
||||||
import { useComments } from "@/context/comments"
|
import { useComments } from "@/context/comments"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { useLayout } from "@/context/layout"
|
||||||
import { usePrompt } from "@/context/prompt"
|
import { usePrompt } from "@/context/prompt"
|
||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
import { getSessionHandoff } from "@/pages/session/handoff"
|
import { getSessionHandoff } from "@/pages/session/handoff"
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
import { createSessionTabs } from "@/pages/session/helpers"
|
import { createSessionTabs } from "@/pages/session/helpers"
|
||||||
|
import { reviewDiffNeedsLoad, type RenderDiff } from "@/pages/session/v2/review-diff-kinds"
|
||||||
|
|
||||||
|
type SessionFileViewProps = {
|
||||||
|
tab: string
|
||||||
|
diff?: RenderDiff
|
||||||
|
diffVersion?: number
|
||||||
|
loadDiff?: (path: string, version?: number) => Promise<RenderDiff | undefined>
|
||||||
|
expandUnchanged?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectionSide = (range: SelectedLineRange) => range.endSide ?? range.side ?? "additions"
|
||||||
|
|
||||||
function FileCommentMenu(props: {
|
function FileCommentMenu(props: {
|
||||||
moreLabel: string
|
moreLabel: string
|
||||||
@@ -207,11 +220,27 @@ export function FileTabContent(props: { tab: string }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SessionFileView(props: { tab: string }) {
|
export function SessionFileView(props: SessionFileViewProps) {
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
|
const detailSource = createMemo(() => {
|
||||||
|
if (!props.diff || !props.loadDiff || !reviewDiffNeedsLoad(props.diff)) return
|
||||||
|
return { diff: props.diff, load: props.loadDiff, version: props.diffVersion }
|
||||||
|
})
|
||||||
|
const [loadedDiff] = createResource(detailSource, async ({ diff, load, version }) => {
|
||||||
|
const value = await load(diff.file, version)
|
||||||
|
if (value?.file !== diff.file) return
|
||||||
|
return { source: diff, version, value }
|
||||||
|
})
|
||||||
|
const diff = createMemo(() => {
|
||||||
|
const source = props.diff
|
||||||
|
if (!source) return
|
||||||
|
const loaded = loadedDiff()
|
||||||
|
return normalize(loaded?.source === source && loaded.version === props.diffVersion ? loaded.value : source)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={settings.general.newLayoutDesigns()} fallback={<SessionFileViewV1 tab={props.tab} />}>
|
<Show when={settings.general.newLayoutDesigns()} fallback={<SessionFileViewV1 tab={props.tab} />}>
|
||||||
<SessionFileViewV2 tab={props.tab} />
|
<SessionFileViewV2 tab={props.tab} diff={diff()} expandUnchanged={props.expandUnchanged} />
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -501,11 +530,12 @@ function SessionFileViewV1(props: { tab: string }) {
|
|||||||
return content()
|
return content()
|
||||||
}
|
}
|
||||||
|
|
||||||
function SessionFileViewV2(props: { tab: string }) {
|
function SessionFileViewV2(props: { tab: string; diff?: ReturnType<typeof normalize>; expandUnchanged?: boolean }) {
|
||||||
const file = useFile()
|
const file = useFile()
|
||||||
const comments = useComments()
|
const comments = useComments()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
|
const layout = useLayout()
|
||||||
const fileComponent = useFileComponent()
|
const fileComponent = useFileComponent()
|
||||||
const { sessionKey, tabs, view } = useSessionLayout()
|
const { sessionKey, tabs, view } = useSessionLayout()
|
||||||
const activeFileTab = createSessionTabs({
|
const activeFileTab = createSessionTabs({
|
||||||
@@ -548,10 +578,15 @@ function SessionFileViewV2(props: { tab: string }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildPreview = (filePath: string, selection: FileSelection) => {
|
const buildPreview = (filePath: string, lines: SelectedLineRange) => {
|
||||||
const source = filePath === path() ? contents() : file.get(filePath)?.content?.content
|
const source =
|
||||||
|
filePath === path()
|
||||||
|
? props.diff
|
||||||
|
? text(props.diff, selectionSide(lines))
|
||||||
|
: contents()
|
||||||
|
: file.get(filePath)?.content?.content
|
||||||
if (!source) return undefined
|
if (!source) return undefined
|
||||||
return selectionPreview(source, selection)
|
return selectionPreview(source, selectionFromLines(lines))
|
||||||
}
|
}
|
||||||
|
|
||||||
const addCommentToContext = (input: {
|
const addCommentToContext = (input: {
|
||||||
@@ -562,7 +597,7 @@ function SessionFileViewV2(props: { tab: string }) {
|
|||||||
origin?: "review" | "file"
|
origin?: "review" | "file"
|
||||||
}) => {
|
}) => {
|
||||||
const selection = selectionFromLines(input.selection)
|
const selection = selectionFromLines(input.selection)
|
||||||
const preview = input.preview ?? buildPreview(input.file, selection)
|
const preview = input.preview ?? buildPreview(input.file, input.selection)
|
||||||
|
|
||||||
const saved = comments.add({
|
const saved = comments.add({
|
||||||
file: input.file,
|
file: input.file,
|
||||||
@@ -587,7 +622,7 @@ function SessionFileViewV2(props: { tab: string }) {
|
|||||||
comment: string
|
comment: string
|
||||||
}) => {
|
}) => {
|
||||||
comments.update(input.file, input.id, input.comment)
|
comments.update(input.file, input.id, input.comment)
|
||||||
const preview = input.file === path() ? buildPreview(input.file, selectionFromLines(input.selection)) : undefined
|
const preview = input.file === path() ? buildPreview(input.file, input.selection) : undefined
|
||||||
prompt.context.updateComment(input.file, input.id, {
|
prompt.context.updateComment(input.file, input.id, {
|
||||||
comment: input.comment,
|
comment: input.comment,
|
||||||
...(preview ? { preview } : {}),
|
...(preview ? { preview } : {}),
|
||||||
@@ -628,7 +663,7 @@ function SessionFileViewV2(props: { tab: string }) {
|
|||||||
mention: {
|
mention: {
|
||||||
items: file.searchFilesAndDirectories,
|
items: file.searchFilesAndDirectories,
|
||||||
},
|
},
|
||||||
getSide: (range) => range.endSide ?? range.side ?? "additions",
|
getSide: selectionSide,
|
||||||
state: {
|
state: {
|
||||||
opened: () => note.openedComment,
|
opened: () => note.openedComment,
|
||||||
setOpened: (id) => setNote("openedComment", id),
|
setOpened: (id) => setNote("openedComment", id),
|
||||||
@@ -726,12 +761,21 @@ function SessionFileViewV2(props: { tab: string }) {
|
|||||||
<div class="relative overflow-hidden pb-40">
|
<div class="relative overflow-hidden pb-40">
|
||||||
<Dynamic
|
<Dynamic
|
||||||
component={fileComponent}
|
component={fileComponent}
|
||||||
mode="text"
|
{...(props.diff
|
||||||
file={{
|
? {
|
||||||
name: path() ?? "",
|
mode: "diff" as const,
|
||||||
contents: source,
|
fileDiff: props.diff.fileDiff,
|
||||||
cacheKey: cacheKey(),
|
diffStyle: layout.review.diffStyle(),
|
||||||
}}
|
expandUnchanged: props.expandUnchanged,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
mode: "text" as const,
|
||||||
|
file: {
|
||||||
|
name: path() ?? "",
|
||||||
|
contents: source,
|
||||||
|
cacheKey: cacheKey(),
|
||||||
|
},
|
||||||
|
})}
|
||||||
enableLineSelection
|
enableLineSelection
|
||||||
enableGutterUtility
|
enableGutterUtility
|
||||||
selectedLines={activeSelection()}
|
selectedLines={activeSelection()}
|
||||||
@@ -761,6 +805,7 @@ function SessionFileViewV2(props: { tab: string }) {
|
|||||||
media={{
|
media={{
|
||||||
mode: "auto",
|
mode: "auto",
|
||||||
path: path(),
|
path: path(),
|
||||||
|
deleted: props.diff?.status === "deleted",
|
||||||
current: state()?.content,
|
current: state()?.content,
|
||||||
onLoad: scrollSync.queueRestore,
|
onLoad: scrollSync.queueRestore,
|
||||||
onError: (args: { kind: "image" | "audio" | "svg" }) => {
|
onError: (args: { kind: "image" | "audio" | "svg" }) => {
|
||||||
@@ -779,6 +824,7 @@ function SessionFileViewV2(props: { tab: string }) {
|
|||||||
<div class="mt-3 relative h-full min-h-0">
|
<div class="mt-3 relative h-full min-h-0">
|
||||||
<ScrollView class="h-full" viewportRef={scrollSync.setViewport} onScroll={scrollSync.handleScroll as any}>
|
<ScrollView class="h-full" viewportRef={scrollSync.setViewport} onScroll={scrollSync.handleScroll as any}>
|
||||||
<Switch>
|
<Switch>
|
||||||
|
<Match when={props.diff}>{renderFile(contents())}</Match>
|
||||||
<Match when={state()?.loaded}>{renderFile(contents())}</Match>
|
<Match when={state()?.loaded}>{renderFile(contents())}</Match>
|
||||||
<Match when={state()?.loading}>
|
<Match when={state()?.loading}>
|
||||||
<div class="px-6 py-4 text-text-weak">{language.t("common.loading")}...</div>
|
<div class="px-6 py-4 text-text-weak">{language.t("common.loading")}...</div>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
|
||||||
import { commandPaletteOptions } from "@/context/command"
|
|
||||||
import { sessionHistoryActions } from "./session-history-actions"
|
|
||||||
|
|
||||||
describe("sessionHistoryActions", () => {
|
|
||||||
test("enables session history actions for Git projects", () => {
|
|
||||||
expect(
|
|
||||||
sessionHistoryActions({
|
|
||||||
vcs: "git",
|
|
||||||
sessionID: "session-1",
|
|
||||||
hasVisibleUserMessage: true,
|
|
||||||
hasRevert: true,
|
|
||||||
}),
|
|
||||||
).toEqual({ undo: true, redo: true, revert: true })
|
|
||||||
})
|
|
||||||
|
|
||||||
test("disables session history actions for non-Git projects", () => {
|
|
||||||
const actions = sessionHistoryActions({
|
|
||||||
vcs: undefined,
|
|
||||||
sessionID: "session-1",
|
|
||||||
hasVisibleUserMessage: true,
|
|
||||||
hasRevert: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(actions).toEqual({ undo: false, redo: false, revert: false })
|
|
||||||
expect(commandPaletteOptions([{ id: "session.undo", title: "Undo", disabled: !actions.undo }])).toEqual([])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export function supportsSessionHistory(vcs?: string) {
|
|
||||||
return vcs === "git"
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sessionHistoryActions(input: {
|
|
||||||
vcs?: string
|
|
||||||
sessionID?: string
|
|
||||||
hasVisibleUserMessage: boolean
|
|
||||||
hasRevert: boolean
|
|
||||||
}) {
|
|
||||||
const git = supportsSessionHistory(input.vcs)
|
|
||||||
return {
|
|
||||||
undo: git && !!input.sessionID && input.hasVisibleUserMessage,
|
|
||||||
redo: git && !!input.sessionID && input.hasRevert,
|
|
||||||
revert: git,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -71,6 +71,9 @@ export function SessionSidePanel(props: {
|
|||||||
reviewHasFocusableContent: () => boolean
|
reviewHasFocusableContent: () => boolean
|
||||||
reviewCount: () => number
|
reviewCount: () => number
|
||||||
reviewPanel: () => JSX.Element
|
reviewPanel: () => JSX.Element
|
||||||
|
diffVersion?: number
|
||||||
|
loadDiff?: (path: string, version?: number) => Promise<RenderDiff | undefined>
|
||||||
|
expandUnchanged?: boolean
|
||||||
reviewSidebarToggle?: (disabled: boolean) => JSX.Element
|
reviewSidebarToggle?: (disabled: boolean) => JSX.Element
|
||||||
fileBrowserState?: SessionFileBrowserState
|
fileBrowserState?: SessionFileBrowserState
|
||||||
activeDiff?: string
|
activeDiff?: string
|
||||||
@@ -88,6 +91,11 @@ export function SessionSidePanel(props: {
|
|||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const { sessionKey, tabs, view, params } = useSessionLayout()
|
const { sessionKey, tabs, view, params } = useSessionLayout()
|
||||||
const projectDirectory = createMemo(() => sdk().directory)
|
const projectDirectory = createMemo(() => sdk().directory)
|
||||||
|
const diffForTab = (tab: string) => {
|
||||||
|
const path = file.pathFromTab(tab)
|
||||||
|
if (!path) return
|
||||||
|
return props.diffs().find((diff): diff is RenderDiff => renderDiff(diff) && diff.file === path)
|
||||||
|
}
|
||||||
|
|
||||||
const isDesktop = createMediaQuery("(min-width: 768px)")
|
const isDesktop = createMediaQuery("(min-width: 768px)")
|
||||||
const shown = settings.visibility.fileTree
|
const shown = settings.visibility.fileTree
|
||||||
@@ -739,6 +747,10 @@ export function SessionSidePanel(props: {
|
|||||||
active={file.pathFromTab(browserTab() ?? activeFileTab() ?? "")}
|
active={file.pathFromTab(browserTab() ?? activeFileTab() ?? "")}
|
||||||
kinds={kinds()}
|
kinds={kinds()}
|
||||||
state={props.fileBrowserState!}
|
state={props.fileBrowserState!}
|
||||||
|
diff={diffForTab(browserTab() ?? activeFileTab() ?? "")}
|
||||||
|
diffVersion={props.diffVersion}
|
||||||
|
loadDiff={props.loadDiff}
|
||||||
|
expandUnchanged={props.expandUnchanged}
|
||||||
onSelect={(path) => previewTab(file.tab(path))}
|
onSelect={(path) => previewTab(file.tab(path))}
|
||||||
onSelectPermanent={(path) => openTab(file.tab(path))}
|
onSelectPermanent={(path) => openTab(file.tab(path))}
|
||||||
filterRef={(element) => (fileFilter = element)}
|
filterRef={(element) => (fileFilter = element)}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ 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 { createSessionOwnership } from "./session-ownership"
|
import { createSessionOwnership } from "./session-ownership"
|
||||||
import { sessionHistoryActions } from "./session-history-actions"
|
|
||||||
|
|
||||||
export type SessionCommandContext = {
|
export type SessionCommandContext = {
|
||||||
navigateMessageByOffset: (offset: number) => void
|
navigateMessageByOffset: (offset: number) => void
|
||||||
@@ -102,13 +101,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
if (!revert) return userMessages()
|
if (!revert) return userMessages()
|
||||||
return userMessages().filter((m) => m.id < revert)
|
return userMessages().filter((m) => m.id < revert)
|
||||||
}
|
}
|
||||||
const historyActions = () =>
|
|
||||||
sessionHistoryActions({
|
|
||||||
vcs: sync().project?.vcs,
|
|
||||||
sessionID: params.id,
|
|
||||||
hasVisibleUserMessage: visibleUserMessages().length > 0,
|
|
||||||
hasRevert: !!info()?.revert?.messageID,
|
|
||||||
})
|
|
||||||
|
|
||||||
const showAllFiles = () => {
|
const showAllFiles = () => {
|
||||||
if (layout.fileTree.tab() !== "changes") return
|
if (layout.fileTree.tab() !== "changes") return
|
||||||
@@ -311,7 +303,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const undo = async () => {
|
const undo = async () => {
|
||||||
if (!historyActions().undo) return
|
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
const owner = sessionOwnership.capture()
|
const owner = sessionOwnership.capture()
|
||||||
@@ -340,7 +331,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const redo = async () => {
|
const redo = async () => {
|
||||||
if (!historyActions().redo) return
|
|
||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
const owner = sessionOwnership.capture()
|
const owner = sessionOwnership.capture()
|
||||||
@@ -440,12 +430,9 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
sessionCommand({
|
sessionCommand({
|
||||||
id: "session.undo",
|
id: "session.undo",
|
||||||
title: language.t("command.session.undo"),
|
title: language.t("command.session.undo"),
|
||||||
description:
|
description: language.t("command.session.undo.description"),
|
||||||
sync().project && sync().project?.vcs !== "git"
|
|
||||||
? language.t("command.session.undo.gitRequired")
|
|
||||||
: language.t("command.session.undo.description"),
|
|
||||||
slash: "undo",
|
slash: "undo",
|
||||||
disabled: !historyActions().undo,
|
disabled: !params.id || visibleUserMessages().length === 0,
|
||||||
onSelect: undo,
|
onSelect: undo,
|
||||||
}),
|
}),
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
@@ -453,7 +440,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||||||
title: language.t("command.session.redo"),
|
title: language.t("command.session.redo"),
|
||||||
description: language.t("command.session.redo.description"),
|
description: language.t("command.session.redo.description"),
|
||||||
slash: "redo",
|
slash: "redo",
|
||||||
disabled: !historyActions().redo,
|
disabled: !params.id || !info()?.revert?.messageID,
|
||||||
onSelect: redo,
|
onSelect: redo,
|
||||||
}),
|
}),
|
||||||
sessionCommand({
|
sessionCommand({
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useSDK } from "@/context/sdk"
|
|||||||
import { displayName } from "@/pages/layout/helpers"
|
import { displayName } from "@/pages/layout/helpers"
|
||||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||||
import { SessionFileView } from "@/pages/session/file-tabs"
|
import { SessionFileView } from "@/pages/session/file-tabs"
|
||||||
|
import type { RenderDiff } from "@/pages/session/v2/review-diff-kinds"
|
||||||
import { applyFileListKeyDown, SessionFileListV2 } from "@/pages/session/v2/session-file-list-v2"
|
import { applyFileListKeyDown, SessionFileListV2 } from "@/pages/session/v2/session-file-list-v2"
|
||||||
import { pathKey } from "@/utils/path-key"
|
import { pathKey } from "@/utils/path-key"
|
||||||
|
|
||||||
@@ -30,6 +31,10 @@ export function SessionFileBrowserTab(props: {
|
|||||||
active?: string
|
active?: string
|
||||||
kinds: ReadonlyMap<string, Kind>
|
kinds: ReadonlyMap<string, Kind>
|
||||||
state: SessionFileBrowserState
|
state: SessionFileBrowserState
|
||||||
|
diff?: RenderDiff
|
||||||
|
diffVersion?: number
|
||||||
|
loadDiff?: (path: string, version?: number) => Promise<RenderDiff | undefined>
|
||||||
|
expandUnchanged?: boolean
|
||||||
onSelect: (path: string) => void
|
onSelect: (path: string) => void
|
||||||
onSelectPermanent: (path: string) => void
|
onSelectPermanent: (path: string) => void
|
||||||
filterRef?: (element: HTMLInputElement) => void
|
filterRef?: (element: HTMLInputElement) => void
|
||||||
@@ -172,7 +177,15 @@ export function SessionFileBrowserTab(props: {
|
|||||||
>
|
>
|
||||||
<div class="min-h-0 flex-1">
|
<div class="min-h-0 flex-1">
|
||||||
<Show when={props.tab} keyed>
|
<Show when={props.tab} keyed>
|
||||||
{(tab) => <SessionFileView tab={tab} />}
|
{(tab) => (
|
||||||
|
<SessionFileView
|
||||||
|
tab={tab}
|
||||||
|
diff={props.diff}
|
||||||
|
diffVersion={props.diffVersion}
|
||||||
|
loadDiff={props.loadDiff}
|
||||||
|
expandUnchanged={props.expandUnchanged}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/cli",
|
"name": "@opencode-ai/cli",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import fs from "fs"
|
|
||||||
import { rm } from "fs/promises"
|
import { rm } from "fs/promises"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { Script } from "@opencode-ai/script"
|
import { Script } from "@opencode-ai/script"
|
||||||
@@ -51,10 +50,6 @@ const targets = singleFlag
|
|||||||
|
|
||||||
if (!skipInstall) await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}`
|
if (!skipInstall) await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}`
|
||||||
|
|
||||||
const localParserWorker = path.resolve(dir, "node_modules/@opentui/core/parser.worker.js")
|
|
||||||
const rootParserWorker = path.resolve(dir, "../../node_modules/@opentui/core/parser.worker.js")
|
|
||||||
const parserWorker = fs.realpathSync(fs.existsSync(localParserWorker) ? localParserWorker : rootParserWorker)
|
|
||||||
|
|
||||||
for (const item of targets) {
|
for (const item of targets) {
|
||||||
const target = [
|
const target = [
|
||||||
binary,
|
binary,
|
||||||
@@ -68,7 +63,7 @@ for (const item of targets) {
|
|||||||
const name = target.replace(binary, "cli")
|
const name = target.replace(binary, "cli")
|
||||||
console.log(`building ${name}`)
|
console.log(`building ${name}`)
|
||||||
const result = await Bun.build({
|
const result = await Bun.build({
|
||||||
entrypoints: ["./src/index.ts", parserWorker],
|
entrypoints: ["./src/index.ts"],
|
||||||
tsconfig: "./tsconfig.json",
|
tsconfig: "./tsconfig.json",
|
||||||
plugins: [plugin],
|
plugins: [plugin],
|
||||||
external: ["node-gyp"],
|
external: ["node-gyp"],
|
||||||
@@ -94,10 +89,6 @@ for (const item of targets) {
|
|||||||
OPENCODE_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "undefined",
|
OPENCODE_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "undefined",
|
||||||
// FFF_LIBC selects the fff native lib variant: "musl" or "gnu".
|
// FFF_LIBC selects the fff native lib variant: "musl" or "gnu".
|
||||||
FFF_LIBC: item.os === "linux" ? `'${item.abi ?? "gnu"}'` : "undefined",
|
FFF_LIBC: item.os === "linux" ? `'${item.abi ?? "gnu"}'` : "undefined",
|
||||||
OTUI_TREE_SITTER_WORKER_PATH:
|
|
||||||
(item.os === "win32" ? '"B:/~BUN/root/' : '"/$bunfs/root/') +
|
|
||||||
path.relative(dir, parserWorker).replaceAll("\\", "/") +
|
|
||||||
'"',
|
|
||||||
...(item.os === "linux" ? { "process.env.OPENTUI_LIBC": JSON.stringify(item.abi ?? "glibc") } : {}),
|
...(item.os === "linux" ? { "process.env.OPENTUI_LIBC": JSON.stringify(item.abi ?? "glibc") } : {}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/codemode",
|
"name": "@opencode-ai/codemode",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"description": "Effect-native confined code execution over schema-described tools",
|
"description": "Effect-native confined code execution over schema-described tools",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-app",
|
"name": "@opencode-ai/console-app",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import "./app.css"
|
|||||||
import { LanguageProvider } from "~/context/language"
|
import { LanguageProvider } from "~/context/language"
|
||||||
import { I18nProvider, useI18n } from "~/context/i18n"
|
import { I18nProvider, useI18n } from "~/context/i18n"
|
||||||
import { strip } from "~/lib/language"
|
import { strip } from "~/lib/language"
|
||||||
import { DesktopPromo } from "~/component/desktop-promo"
|
|
||||||
|
|
||||||
function AppMeta() {
|
function AppMeta() {
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
@@ -34,7 +33,6 @@ export default function App() {
|
|||||||
<MetaProvider>
|
<MetaProvider>
|
||||||
<AppMeta />
|
<AppMeta />
|
||||||
<Suspense>{props.children}</Suspense>
|
<Suspense>{props.children}</Suspense>
|
||||||
<DesktopPromo />
|
|
||||||
</MetaProvider>
|
</MetaProvider>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
</LanguageProvider>
|
</LanguageProvider>
|
||||||
|
|||||||
@@ -358,6 +358,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "المزود {{provider}} غير مدعوم",
|
"zen.api.error.providerNotSupported": "المزود {{provider}} غير مدعوم",
|
||||||
"zen.api.error.missingApiKey": "مفتاح API مفقود.",
|
"zen.api.error.missingApiKey": "مفتاح API مفقود.",
|
||||||
"zen.api.error.invalidApiKey": "مفتاح API غير صالح.",
|
"zen.api.error.invalidApiKey": "مفتاح API غير صالح.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "حظر المزود الخارجي الطلب.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "تم تجاوز حصة الاشتراك. أعد المحاولة خلال {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "تم تجاوز حصة الاشتراك. أعد المحاولة خلال {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"تم الوصول إلى حد الاستخدام لمدة 5 ساعات. تتم إعادة التعيين خلال {{retryIn}}. لمواصلة استخدام هذا النموذج الآن، فعّل الاستخدام من رصيدك المتاح: {{consoleGoUrl}}",
|
"تم الوصول إلى حد الاستخدام لمدة 5 ساعات. تتم إعادة التعيين خلال {{retryIn}}. لمواصلة استخدام هذا النموذج الآن، فعّل الاستخدام من رصيدك المتاح: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Provedor {{provider}} não suportado",
|
"zen.api.error.providerNotSupported": "Provedor {{provider}} não suportado",
|
||||||
"zen.api.error.missingApiKey": "Chave de API ausente.",
|
"zen.api.error.missingApiKey": "Chave de API ausente.",
|
||||||
"zen.api.error.invalidApiKey": "Chave de API inválida.",
|
"zen.api.error.invalidApiKey": "Chave de API inválida.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "Solicitação bloqueada pelo provedor upstream.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Cota de assinatura excedida. Tente novamente em {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Cota de assinatura excedida. Tente novamente em {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Limite de uso de 5 horas atingido. Será reiniciado em {{retryIn}}. Para continuar usando este modelo agora, habilite o uso a partir do seu saldo disponível: {{consoleGoUrl}}",
|
"Limite de uso de 5 horas atingido. Será reiniciado em {{retryIn}}. Para continuar usando este modelo agora, habilite o uso a partir do seu saldo disponível: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Udbyder {{provider}} understøttes ikke",
|
"zen.api.error.providerNotSupported": "Udbyder {{provider}} understøttes ikke",
|
||||||
"zen.api.error.missingApiKey": "Manglende API-nøgle.",
|
"zen.api.error.missingApiKey": "Manglende API-nøgle.",
|
||||||
"zen.api.error.invalidApiKey": "Ugyldig API-nøgle.",
|
"zen.api.error.invalidApiKey": "Ugyldig API-nøgle.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "Anmodningen blev blokeret af upstream-udbyderen.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Abonnementskvote overskredet. Prøv igen om {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Abonnementskvote overskredet. Prøv igen om {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Forbrugsgrænsen for 5 timer er nået. Nulstilles om {{retryIn}}. For at fortsætte med at bruge denne model nu, aktivér forbrug fra din tilgængelige saldo: {{consoleGoUrl}}",
|
"Forbrugsgrænsen for 5 timer er nået. Nulstilles om {{retryIn}}. For at fortsætte med at bruge denne model nu, aktivér forbrug fra din tilgængelige saldo: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -365,6 +365,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Anbieter {{provider}} wird nicht unterstützt",
|
"zen.api.error.providerNotSupported": "Anbieter {{provider}} wird nicht unterstützt",
|
||||||
"zen.api.error.missingApiKey": "Fehlender API-Key.",
|
"zen.api.error.missingApiKey": "Fehlender API-Key.",
|
||||||
"zen.api.error.invalidApiKey": "Ungültiger API-Key.",
|
"zen.api.error.invalidApiKey": "Ungültiger API-Key.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "Anfrage vom vorgelagerten Anbieter blockiert.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Abonnement-Quote überschritten. Erneuter Versuch in {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Abonnement-Quote überschritten. Erneuter Versuch in {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"5-Stunden-Nutzungslimit erreicht. Wird in {{retryIn}} zurückgesetzt. Um dieses Modell jetzt weiter zu nutzen, aktiviere die Nutzung über dein verfügbares Guthaben: {{consoleGoUrl}}",
|
"5-Stunden-Nutzungslimit erreicht. Wird in {{retryIn}} zurückgesetzt. Um dieses Modell jetzt weiter zu nutzen, aktiviere die Nutzung über dein verfügbares Guthaben: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -360,6 +360,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Provider {{provider}} not supported",
|
"zen.api.error.providerNotSupported": "Provider {{provider}} not supported",
|
||||||
"zen.api.error.missingApiKey": "Missing API key.",
|
"zen.api.error.missingApiKey": "Missing API key.",
|
||||||
"zen.api.error.invalidApiKey": "Invalid API key.",
|
"zen.api.error.invalidApiKey": "Invalid API key.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "Request blocked by upstream provider.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Subscription quota exceeded. Retry in {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Subscription quota exceeded. Retry in {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"5-hour usage limit reached. Resets in {{retryIn}}. To continue using this model now, enable usage from your available balance: {{consoleGoUrl}}",
|
"5-hour usage limit reached. Resets in {{retryIn}}. To continue using this model now, enable usage from your available balance: {{consoleGoUrl}}",
|
||||||
@@ -777,6 +778,10 @@ export const dict = {
|
|||||||
"enterprise.faq.q4": "Is my data secure with OpenCode Enterprise?",
|
"enterprise.faq.q4": "Is my data secure with OpenCode Enterprise?",
|
||||||
"enterprise.faq.a4":
|
"enterprise.faq.a4":
|
||||||
"Yes. OpenCode does not store your code or context data. All processing happens locally or through direct API calls to your AI provider. With central config and SSO integration, your data remains secure within your organization's infrastructure.",
|
"Yes. OpenCode does not store your code or context data. All processing happens locally or through direct API calls to your AI provider. With central config and SSO integration, your data remains secure within your organization's infrastructure.",
|
||||||
|
"enterprise.faq.q5": "Where can I find your security and compliance documentation?",
|
||||||
|
"enterprise.faq.a5.before":
|
||||||
|
"Our Trust Center has everything: SOC 2 Type 2 report, security policies, subprocessor list, and answers to common security questions. Visit",
|
||||||
|
"enterprise.faq.a5.after": "to review or request documents under NDA.",
|
||||||
|
|
||||||
"brand.title": "OpenCode | Brand",
|
"brand.title": "OpenCode | Brand",
|
||||||
"brand.meta.description": "OpenCode brand guidelines",
|
"brand.meta.description": "OpenCode brand guidelines",
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Proveedor {{provider}} no soportado",
|
"zen.api.error.providerNotSupported": "Proveedor {{provider}} no soportado",
|
||||||
"zen.api.error.missingApiKey": "Falta la clave API.",
|
"zen.api.error.missingApiKey": "Falta la clave API.",
|
||||||
"zen.api.error.invalidApiKey": "Clave API inválida.",
|
"zen.api.error.invalidApiKey": "Clave API inválida.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "El proveedor externo bloqueó la solicitud.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Cuota de suscripción excedida. Reintenta en {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Cuota de suscripción excedida. Reintenta en {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Límite de uso de 5 horas alcanzado. Se restablece en {{retryIn}}. Para seguir usando este modelo ahora, habilita el uso desde tu saldo disponible: {{consoleGoUrl}}",
|
"Límite de uso de 5 horas alcanzado. Se restablece en {{retryIn}}. Para seguir usando este modelo ahora, habilita el uso desde tu saldo disponible: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Fournisseur {{provider}} non pris en charge",
|
"zen.api.error.providerNotSupported": "Fournisseur {{provider}} non pris en charge",
|
||||||
"zen.api.error.missingApiKey": "Clé API manquante.",
|
"zen.api.error.missingApiKey": "Clé API manquante.",
|
||||||
"zen.api.error.invalidApiKey": "Clé API invalide.",
|
"zen.api.error.invalidApiKey": "Clé API invalide.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "Requête bloquée par le fournisseur en amont.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Quota d'abonnement dépassé. Réessayez dans {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Quota d'abonnement dépassé. Réessayez dans {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Limite d'utilisation sur 5 heures atteinte. Réinitialisation dans {{retryIn}}. Pour continuer à utiliser ce modèle dès maintenant, activez l'utilisation depuis votre solde disponible : {{consoleGoUrl}}",
|
"Limite d'utilisation sur 5 heures atteinte. Réinitialisation dans {{retryIn}}. Pour continuer à utiliser ce modèle dès maintenant, activez l'utilisation depuis votre solde disponible : {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Provider {{provider}} non supportato",
|
"zen.api.error.providerNotSupported": "Provider {{provider}} non supportato",
|
||||||
"zen.api.error.missingApiKey": "Chiave API mancante.",
|
"zen.api.error.missingApiKey": "Chiave API mancante.",
|
||||||
"zen.api.error.invalidApiKey": "Chiave API non valida.",
|
"zen.api.error.invalidApiKey": "Chiave API non valida.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "Richiesta bloccata dal provider upstream.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Quota dell'abbonamento superata. Riprova tra {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Quota dell'abbonamento superata. Riprova tra {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Limite di utilizzo di 5 ore raggiunto. Si reimposta tra {{retryIn}}. Per continuare a usare questo modello ora, abilita l'utilizzo dal tuo saldo disponibile: {{consoleGoUrl}}",
|
"Limite di utilizzo di 5 ore raggiunto. Si reimposta tra {{retryIn}}. Per continuare a usare questo modello ora, abilita l'utilizzo dal tuo saldo disponibile: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "プロバイダー {{provider}} はサポートされていません",
|
"zen.api.error.providerNotSupported": "プロバイダー {{provider}} はサポートされていません",
|
||||||
"zen.api.error.missingApiKey": "APIキーがありません。",
|
"zen.api.error.missingApiKey": "APIキーがありません。",
|
||||||
"zen.api.error.invalidApiKey": "無効なAPIキーです。",
|
"zen.api.error.invalidApiKey": "無効なAPIキーです。",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "上流プロバイダーによりリクエストがブロックされました。",
|
||||||
"zen.api.error.subscriptionQuotaExceeded":
|
"zen.api.error.subscriptionQuotaExceeded":
|
||||||
"サブスクリプションの制限を超えました。{{retryIn}} 後に再試行してください。",
|
"サブスクリプションの制限を超えました。{{retryIn}} 後に再試行してください。",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
|
|||||||
@@ -357,6 +357,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "{{provider}} 제공자는 지원되지 않습니다",
|
"zen.api.error.providerNotSupported": "{{provider}} 제공자는 지원되지 않습니다",
|
||||||
"zen.api.error.missingApiKey": "API 키가 누락되었습니다.",
|
"zen.api.error.missingApiKey": "API 키가 누락되었습니다.",
|
||||||
"zen.api.error.invalidApiKey": "유효하지 않은 API 키입니다.",
|
"zen.api.error.invalidApiKey": "유효하지 않은 API 키입니다.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "상위 제공자가 요청을 차단했습니다.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "구독 할당량을 초과했습니다. {{retryIn}} 후 다시 시도해 주세요.",
|
"zen.api.error.subscriptionQuotaExceeded": "구독 할당량을 초과했습니다. {{retryIn}} 후 다시 시도해 주세요.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"5시간 사용 한도에 도달했습니다. {{retryIn}} 후 초기화됩니다. 이 모델을 지금 계속 사용하려면 사용 가능한 잔액에서 사용을 활성화하세요: {{consoleGoUrl}}",
|
"5시간 사용 한도에 도달했습니다. {{retryIn}} 후 초기화됩니다. 이 모델을 지금 계속 사용하려면 사용 가능한 잔액에서 사용을 활성화하세요: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -363,6 +363,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Leverandør {{provider}} støttes ikke",
|
"zen.api.error.providerNotSupported": "Leverandør {{provider}} støttes ikke",
|
||||||
"zen.api.error.missingApiKey": "Mangler API-nøkkel.",
|
"zen.api.error.missingApiKey": "Mangler API-nøkkel.",
|
||||||
"zen.api.error.invalidApiKey": "Ugyldig API-nøkkel.",
|
"zen.api.error.invalidApiKey": "Ugyldig API-nøkkel.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "Forespørselen ble blokkert av leverandøren.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Abonnementskvote overskredet. Prøv igjen om {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Abonnementskvote overskredet. Prøv igjen om {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"5-timers bruksgrense nådd. Tilbakestilles om {{retryIn}}. For å fortsette å bruke denne modellen nå, aktiver bruk fra din tilgjengelige saldo: {{consoleGoUrl}}",
|
"5-timers bruksgrense nådd. Tilbakestilles om {{retryIn}}. For å fortsette å bruke denne modellen nå, aktiver bruk fra din tilgjengelige saldo: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -364,6 +364,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Dostawca {{provider}} nie jest obsługiwany",
|
"zen.api.error.providerNotSupported": "Dostawca {{provider}} nie jest obsługiwany",
|
||||||
"zen.api.error.missingApiKey": "Brak klucza API.",
|
"zen.api.error.missingApiKey": "Brak klucza API.",
|
||||||
"zen.api.error.invalidApiKey": "Nieprawidłowy klucz API.",
|
"zen.api.error.invalidApiKey": "Nieprawidłowy klucz API.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "Żądanie zablokowane przez dostawcę zewnętrznego.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Przekroczono limit subskrypcji. Spróbuj ponownie za {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Przekroczono limit subskrypcji. Spróbuj ponownie za {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Osiągnięto 5-godzinny limit użycia. Resetuje się za {{retryIn}}. Aby nadal korzystać z tego modelu, włącz użycie z dostępnego salda: {{consoleGoUrl}}",
|
"Osiągnięto 5-godzinny limit użycia. Resetuje się za {{retryIn}}. Aby nadal korzystać z tego modelu, włącz użycie z dostępnego salda: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -368,6 +368,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Провайдер {{provider}} не поддерживается",
|
"zen.api.error.providerNotSupported": "Провайдер {{provider}} не поддерживается",
|
||||||
"zen.api.error.missingApiKey": "Отсутствует API ключ.",
|
"zen.api.error.missingApiKey": "Отсутствует API ключ.",
|
||||||
"zen.api.error.invalidApiKey": "Неверный API ключ.",
|
"zen.api.error.invalidApiKey": "Неверный API ключ.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "Запрос заблокирован вышестоящим провайдером.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Квота подписки превышена. Повторите попытку через {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Квота подписки превышена. Повторите попытку через {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Достигнут лимит использования за 5 часов. Сбросится через {{retryIn}}. Чтобы продолжить использовать эту модель сейчас, включите оплату с доступного баланса: {{consoleGoUrl}}",
|
"Достигнут лимит использования за 5 часов. Сбросится через {{retryIn}}. Чтобы продолжить использовать эту модель сейчас, включите оплату с доступного баланса: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -359,6 +359,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "ไม่รองรับผู้ให้บริการ {{provider}}",
|
"zen.api.error.providerNotSupported": "ไม่รองรับผู้ให้บริการ {{provider}}",
|
||||||
"zen.api.error.missingApiKey": "ไม่มี API key",
|
"zen.api.error.missingApiKey": "ไม่มี API key",
|
||||||
"zen.api.error.invalidApiKey": "API key ไม่ถูกต้อง",
|
"zen.api.error.invalidApiKey": "API key ไม่ถูกต้อง",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "คำขอถูกบล็อกโดยผู้ให้บริการต้นทาง",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "โควต้าการสมัครสมาชิกเกินขีดจำกัด ลองใหม่ในอีก {{retryIn}}",
|
"zen.api.error.subscriptionQuotaExceeded": "โควต้าการสมัครสมาชิกเกินขีดจำกัด ลองใหม่ในอีก {{retryIn}}",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"ถึงขีดจำกัดการใช้งานในรอบ 5 ชั่วโมงแล้ว จะรีเซ็ตในอีก {{retryIn}} หากต้องการใช้โมเดลนี้ต่อทันที ให้เปิดใช้งานจากยอดเงินคงเหลือของคุณ: {{consoleGoUrl}}",
|
"ถึงขีดจำกัดการใช้งานในรอบ 5 ชั่วโมงแล้ว จะรีเซ็ตในอีก {{retryIn}} หากต้องการใช้โมเดลนี้ต่อทันที ให้เปิดใช้งานจากยอดเงินคงเหลือของคุณ: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "{{provider}} sağlayıcısı desteklenmiyor",
|
"zen.api.error.providerNotSupported": "{{provider}} sağlayıcısı desteklenmiyor",
|
||||||
"zen.api.error.missingApiKey": "API anahtarı eksik.",
|
"zen.api.error.missingApiKey": "API anahtarı eksik.",
|
||||||
"zen.api.error.invalidApiKey": "Geçersiz API anahtarı.",
|
"zen.api.error.invalidApiKey": "Geçersiz API anahtarı.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "İstek üst sağlayıcı tarafından engellendi.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Abonelik kotası aşıldı. {{retryIn}} içinde tekrar deneyin.",
|
"zen.api.error.subscriptionQuotaExceeded": "Abonelik kotası aşıldı. {{retryIn}} içinde tekrar deneyin.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"5 saatlik kullanım limitine ulaşıldı. {{retryIn}} içinde sıfırlanır. Bu modeli şimdi kullanmaya devam etmek için kullanılabilir bakiyenizden kullanımı etkinleştirin: {{consoleGoUrl}}",
|
"5 saatlik kullanım limitine ulaşıldı. {{retryIn}} içinde sıfırlanır. Bu modeli şimdi kullanmaya devam etmek için kullanılabilir bakiyenizden kullanımı etkinleştirin: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -363,6 +363,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "Провайдер {{provider}} не підтримується",
|
"zen.api.error.providerNotSupported": "Провайдер {{provider}} не підтримується",
|
||||||
"zen.api.error.missingApiKey": "Відсутній ключ API.",
|
"zen.api.error.missingApiKey": "Відсутній ключ API.",
|
||||||
"zen.api.error.invalidApiKey": "Недійсний ключ API.",
|
"zen.api.error.invalidApiKey": "Недійсний ключ API.",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "Запит заблоковано зовнішнім провайдером.",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "Перевищено квоту підписки. Повторіть через {{retryIn}}.",
|
"zen.api.error.subscriptionQuotaExceeded": "Перевищено квоту підписки. Повторіть через {{retryIn}}.",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"Досягнуто 5-годинного ліміту використання. Скидається через {{retryIn}}. Щоб продовжити, увімкніть використання з доступного балансу: {{consoleGoUrl}}",
|
"Досягнуто 5-годинного ліміту використання. Скидається через {{retryIn}}. Щоб продовжити, увімкніть використання з доступного балансу: {{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -346,6 +346,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "不支持提供商 {{provider}}",
|
"zen.api.error.providerNotSupported": "不支持提供商 {{provider}}",
|
||||||
"zen.api.error.missingApiKey": "缺少 API 密钥。",
|
"zen.api.error.missingApiKey": "缺少 API 密钥。",
|
||||||
"zen.api.error.invalidApiKey": "无效的 API 密钥。",
|
"zen.api.error.invalidApiKey": "无效的 API 密钥。",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "请求被上游提供商阻止。",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "超出订阅配额。请在 {{retryIn}} 后重试。",
|
"zen.api.error.subscriptionQuotaExceeded": "超出订阅配额。请在 {{retryIn}} 后重试。",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"已达到 5 小时使用限额。将在 {{retryIn}} 后重置。如需立即继续使用该模型,请启用从可用余额扣费:{{consoleGoUrl}}",
|
"已达到 5 小时使用限额。将在 {{retryIn}} 后重置。如需立即继续使用该模型,请启用从可用余额扣费:{{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -346,6 +346,7 @@ export const dict = {
|
|||||||
"zen.api.error.providerNotSupported": "不支援供應商 {{provider}}",
|
"zen.api.error.providerNotSupported": "不支援供應商 {{provider}}",
|
||||||
"zen.api.error.missingApiKey": "缺少 API 金鑰。",
|
"zen.api.error.missingApiKey": "缺少 API 金鑰。",
|
||||||
"zen.api.error.invalidApiKey": "無效的 API 金鑰。",
|
"zen.api.error.invalidApiKey": "無效的 API 金鑰。",
|
||||||
|
"zen.api.error.requestBlockedByUpstreamProvider": "請求遭上游供應商封鎖。",
|
||||||
"zen.api.error.subscriptionQuotaExceeded": "超出訂閱配額。請在 {{retryIn}} 後重試。",
|
"zen.api.error.subscriptionQuotaExceeded": "超出訂閱配額。請在 {{retryIn}} 後重試。",
|
||||||
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
"zen.api.error.goSubscriptionRollingLimitExceeded":
|
||||||
"已達 5 小時使用上限,將在 {{retryIn}} 後重置。若要立即繼續使用此模型,請從可用餘額啟用使用量:{{consoleGoUrl}}",
|
"已達 5 小時使用上限,將在 {{retryIn}} 後重置。若要立即繼續使用此模型,請從可用餘額啟用使用量:{{consoleGoUrl}}",
|
||||||
|
|||||||
@@ -273,6 +273,12 @@ export default function Enterprise() {
|
|||||||
<li>
|
<li>
|
||||||
<Faq question={i18n.t("enterprise.faq.q4")}>{i18n.t("enterprise.faq.a4")}</Faq>
|
<Faq question={i18n.t("enterprise.faq.q4")}>{i18n.t("enterprise.faq.a4")}</Faq>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<Faq question={i18n.t("enterprise.faq.q5")}>
|
||||||
|
{i18n.t("enterprise.faq.a5.before")} <a href="https://trust.opencode.ai">trust.opencode.ai</a>{" "}
|
||||||
|
{i18n.t("enterprise.faq.a5.after")}
|
||||||
|
</Faq>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ function LimitsGraph(props: { href: string }) {
|
|||||||
|
|
||||||
const baseline = 100
|
const baseline = 100
|
||||||
const graph = [
|
const graph = [
|
||||||
{ id: "grok-4.5", name: "Grok 4.5", req: 80, d: "50ms" },
|
{ id: "grok-4.5", name: "Grok 4.5", req: 120, d: "50ms" },
|
||||||
{ id: "kimi-k3", name: "Kimi K3 (2x usage)", req: 280, baseReq: 140, d: "75ms" },
|
{ id: "kimi-k3", name: "Kimi K3 (2x usage)", req: 220, baseReq: 110, d: "75ms" },
|
||||||
{ id: "glm-5.2", name: "GLM-5.2", req: 880, d: "100ms" },
|
{ id: "glm-5.2", name: "GLM-5.2", req: 880, d: "100ms" },
|
||||||
{ id: "qwen3.7-max", name: "Qwen3.7 Max", req: 950, d: "110ms" },
|
{ id: "qwen3.7-max", name: "Qwen3.7 Max", req: 950, d: "110ms" },
|
||||||
{ id: "kimi-k2.7-code", name: "Kimi K2.7 Code", req: 1150, d: "150ms" },
|
{ id: "kimi-k2.7-code", name: "Kimi K2.7 Code", req: 1150, d: "150ms" },
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import type { APIEvent } from "@solidjs/start/server"
|
||||||
|
import { handler } from "~/routes/zen/util/handler"
|
||||||
|
import { parseOpenAiVariant } from "~/routes/zen/util/variant"
|
||||||
|
|
||||||
|
export function POST(input: APIEvent) {
|
||||||
|
return handler(input, {
|
||||||
|
format: "openai",
|
||||||
|
modelList: "lite",
|
||||||
|
parseApiKey: (headers: Headers) => headers.get("authorization")?.split(" ")[1],
|
||||||
|
parseModel: (url: string, body: any) => body.model,
|
||||||
|
parseVariant: (url: string, body: any) => parseOpenAiVariant(body),
|
||||||
|
parseIsStream: (url: string, body: any) => !!body.stream,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -697,6 +697,9 @@ export async function handler(
|
|||||||
workspace: {
|
workspace: {
|
||||||
id: WorkspaceTable.id,
|
id: WorkspaceTable.id,
|
||||||
region: WorkspaceTable.region,
|
region: WorkspaceTable.region,
|
||||||
|
isBlocked: WorkspaceTable.is_blocked,
|
||||||
|
isFlaggedByAnthropic: WorkspaceTable.is_flagged_by_anthropic,
|
||||||
|
isFlaggedByOpenAI: WorkspaceTable.is_flagged_by_openai,
|
||||||
},
|
},
|
||||||
billing: {
|
billing: {
|
||||||
balance: BillingTable.balance,
|
balance: BillingTable.balance,
|
||||||
@@ -772,6 +775,12 @@ export async function handler(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!data) throw new AuthError(t("zen.api.error.invalidApiKey"))
|
if (!data) throw new AuthError(t("zen.api.error.invalidApiKey"))
|
||||||
|
if (
|
||||||
|
data.workspace.isBlocked ||
|
||||||
|
(data.workspace.isFlaggedByAnthropic && modelInfo.id.startsWith("claude-")) ||
|
||||||
|
(data.workspace.isFlaggedByOpenAI && modelInfo.id.startsWith("gpt-"))
|
||||||
|
)
|
||||||
|
throw new AuthError(t("zen.api.error.requestBlockedByUpstreamProvider"))
|
||||||
if (
|
if (
|
||||||
modelInfo.id.startsWith("alpha-") &&
|
modelInfo.id.startsWith("alpha-") &&
|
||||||
Resource.App.stage === "production" &&
|
Resource.App.stage === "production" &&
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE `workspace` ADD `is_blocked` boolean;--> statement-breakpoint
|
||||||
|
ALTER TABLE `workspace` ADD `is_flagged_by_anthropic` boolean;--> statement-breakpoint
|
||||||
|
ALTER TABLE `workspace` ADD `is_flagged_by_openai` boolean;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"name": "@opencode-ai/console-core",
|
"name": "@opencode-ai/console-core",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { json, primaryKey, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
|
import { boolean, json, primaryKey, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
|
||||||
import { timestamps, ulid } from "../drizzle/types"
|
import { timestamps, ulid } from "../drizzle/types"
|
||||||
|
|
||||||
export const WorkspaceTable = mysqlTable(
|
export const WorkspaceTable = mysqlTable(
|
||||||
@@ -8,6 +8,9 @@ export const WorkspaceTable = mysqlTable(
|
|||||||
slug: varchar("slug", { length: 255 }),
|
slug: varchar("slug", { length: 255 }),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
region: json("region").$type<("us" | "eu" | "sg" | "cn")[]>(),
|
region: json("region").$type<("us" | "eu" | "sg" | "cn")[]>(),
|
||||||
|
is_blocked: boolean(),
|
||||||
|
is_flagged_by_anthropic: boolean(),
|
||||||
|
is_flagged_by_openai: boolean(),
|
||||||
...timestamps,
|
...timestamps,
|
||||||
},
|
},
|
||||||
(table) => [uniqueIndex("slug").on(table.slug)],
|
(table) => [uniqueIndex("slug").on(table.slug)],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-function",
|
"name": "@opencode-ai/console-function",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-mail",
|
"name": "@opencode-ai/console-mail",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jsx-email/all": "2.2.3",
|
"@jsx-email/all": "2.2.3",
|
||||||
"@jsx-email/cli": "1.4.3",
|
"@jsx-email/cli": "1.4.3",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/console-support",
|
"name": "@opencode-ai/console-support",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"name": "@opencode-ai/core",
|
"name": "@opencode-ai/core",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import type { Opts, Proc } from "./pty"
|
|||||||
export type { Disp, Exit, Opts, Proc } from "./pty"
|
export type { Disp, Exit, Opts, Proc } from "./pty"
|
||||||
|
|
||||||
export function spawn(file: string, args: string[], opts: Opts): Proc {
|
export function spawn(file: string, args: string[], opts: Opts): Proc {
|
||||||
const proc = pty.spawn(file, args, opts)
|
const proc = pty.spawn(file, args, {
|
||||||
|
...opts,
|
||||||
|
...(process.platform === "win32" ? { useConptyDll: true } : {}),
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
pid: proc.pid,
|
pid: proc.pid,
|
||||||
onData(listener) {
|
onData(listener) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/desktop",
|
"name": "@opencode-ai/desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://opencode.ai",
|
"homepage": "https://opencode.ai",
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { expect, test } from "bun:test"
|
||||||
|
import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"
|
||||||
|
import { tmpdir } from "node:os"
|
||||||
|
import { join } from "node:path"
|
||||||
|
import { wslSidecarShell } from "./sidecar-shell"
|
||||||
|
|
||||||
|
test("loads interactive WSL environment variables for the desktop server", async () => {
|
||||||
|
const home = await mkdtemp(join(tmpdir(), "opencode-wsl-env-"))
|
||||||
|
const opencode = join(home, "opencode")
|
||||||
|
await writeFile(join(home, ".profile"), 'source "$HOME/.bashrc"\n')
|
||||||
|
await writeFile(join(home, ".bashrc"), "export COMPANY_BASEURL=https://company.example\n")
|
||||||
|
await writeFile(opencode, '#!/usr/bin/env bash\nprintf "%s" "$COMPANY_BASEURL"\n')
|
||||||
|
await chmod(opencode, 0o755)
|
||||||
|
|
||||||
|
const shell = wslSidecarShell(opencode, 4096, "opencode", "secret", false)
|
||||||
|
const proc = Bun.spawn(shell.args, {
|
||||||
|
cwd: home,
|
||||||
|
env: { ...process.env, HOME: home },
|
||||||
|
stdin: new Blob([shell.script]),
|
||||||
|
stdout: "pipe",
|
||||||
|
stderr: "pipe",
|
||||||
|
})
|
||||||
|
const output = await new Response(proc.stdout).text()
|
||||||
|
await new Response(proc.stderr).text()
|
||||||
|
const code = await proc.exited
|
||||||
|
await rm(home, { recursive: true, force: true })
|
||||||
|
|
||||||
|
expect({ code, output }).toEqual({ code: 0, output: "https://company.example" })
|
||||||
|
})
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { shellEscape } from "./runtime"
|
||||||
|
|
||||||
|
export function wslSidecarShell(opencode: string, port: number, username: string, password: string, packaged: boolean) {
|
||||||
|
return {
|
||||||
|
// Explicit WSL commands skip login startup files. Load the same user environment
|
||||||
|
// as a terminal launch, then replace the interactive shell before reading stdin.
|
||||||
|
args: [
|
||||||
|
"bash",
|
||||||
|
"--noprofile",
|
||||||
|
"--norc",
|
||||||
|
"-ic",
|
||||||
|
'for file in "$HOME/.bash_profile" "$HOME/.bash_login" "$HOME/.profile"; do [[ ! -r "$file" ]] || { source "$file"; break; }; done; exec bash -se',
|
||||||
|
],
|
||||||
|
script: [
|
||||||
|
"set -euo pipefail",
|
||||||
|
'cd "$HOME" || cd /',
|
||||||
|
'PATH=$(awk -v RS=: -v ORS=: \'$0 !~ /^\\/mnt\\//\' <<<"$PATH" | sed "s/:$//")',
|
||||||
|
"export PATH",
|
||||||
|
"export WSLENV=",
|
||||||
|
"export OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER=true",
|
||||||
|
"export OPENCODE_CLIENT=desktop",
|
||||||
|
`export OPENCODE_SERVER_USERNAME=${shellEscape(username)}`,
|
||||||
|
`export OPENCODE_SERVER_PASSWORD=${shellEscape(password)}`,
|
||||||
|
'export XDG_STATE_HOME="$HOME/.local/state"',
|
||||||
|
`exec ${shellEscape(opencode)} --print-logs --log-level ${packaged ? "WARN" : "INFO"} serve --hostname 0.0.0.0 --port ${port}`,
|
||||||
|
].join("\n"),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,8 @@ import { randomUUID } from "node:crypto"
|
|||||||
import { createServer } from "node:net"
|
import { createServer } from "node:net"
|
||||||
import { app } from "electron"
|
import { app } from "electron"
|
||||||
import { checkHealth } from "../server"
|
import { checkHealth } from "../server"
|
||||||
import { type WslCommandLine, resolveWslOpencode, shellEscape, wslArgs } from "./runtime"
|
import { type WslCommandLine, resolveWslOpencode, wslArgs } from "./runtime"
|
||||||
|
import { wslSidecarShell } from "./sidecar-shell"
|
||||||
import { pollWslHealth } from "./startup"
|
import { pollWslHealth } from "./startup"
|
||||||
|
|
||||||
export type WslSidecar = {
|
export type WslSidecar = {
|
||||||
@@ -23,24 +24,12 @@ export async function spawnWslSidecar(
|
|||||||
const port = await allocatePort()
|
const port = await allocatePort()
|
||||||
const password = randomUUID()
|
const password = randomUUID()
|
||||||
const username = "opencode"
|
const username = "opencode"
|
||||||
const script = [
|
const shell = wslSidecarShell(opencode, port, username, password, app.isPackaged)
|
||||||
"set -euo pipefail",
|
const child = spawn("wsl", wslArgs(shell.args, distro), {
|
||||||
'cd "$HOME" || cd /',
|
|
||||||
'PATH=$(awk -v RS=: -v ORS=: \'$0 !~ /^\\/mnt\\//\' <<<"$PATH" | sed "s/:$//")',
|
|
||||||
"export PATH",
|
|
||||||
"export WSLENV=",
|
|
||||||
"export OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER=true",
|
|
||||||
"export OPENCODE_CLIENT=desktop",
|
|
||||||
`export OPENCODE_SERVER_USERNAME=${shellEscape(username)}`,
|
|
||||||
`export OPENCODE_SERVER_PASSWORD=${shellEscape(password)}`,
|
|
||||||
'export XDG_STATE_HOME="$HOME/.local/state"',
|
|
||||||
`exec ${shellEscape(opencode)} --print-logs --log-level ${app.isPackaged ? "WARN" : "INFO"} serve --hostname 0.0.0.0 --port ${port}`,
|
|
||||||
].join("\n")
|
|
||||||
const child = spawn("wsl", wslArgs(["bash", "-se"], distro), {
|
|
||||||
stdio: ["pipe", "pipe", "pipe"],
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
windowsHide: true,
|
windowsHide: true,
|
||||||
})
|
})
|
||||||
child.stdin.end(script)
|
child.stdin.end(shell.script)
|
||||||
|
|
||||||
const recentOutput: string[] = []
|
const recentOutput: string[] = []
|
||||||
const emit = (line: WslCommandLine) => {
|
const emit = (line: WslCommandLine) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"name": "@opencode-ai/effect-sqlite-node",
|
"name": "@opencode-ai/effect-sqlite-node",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/enterprise",
|
"name": "@opencode-ai/enterprise",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/function",
|
"name": "@opencode-ai/function",
|
||||||
"version": "1.18.3",
|
"version": "1.18.4",
|
||||||
"$schema": "https://json.schemastore.org/package.json",
|
"$schema": "https://json.schemastore.org/package.json",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user