Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6177759ca8 |
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -83,7 +83,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -117,7 +117,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -144,7 +144,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.64",
|
||||
"@ai-sdk/openai": "3.0.48",
|
||||
@@ -168,7 +168,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -192,7 +192,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -225,7 +225,7 @@
|
||||
},
|
||||
"packages/desktop-electron": {
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -269,7 +269,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@opencode-ai/shared": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -298,7 +298,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -314,7 +314,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -367,8 +367,8 @@
|
||||
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.6.1",
|
||||
"@opentelemetry/sdk-trace-node": "2.6.1",
|
||||
"@opentui/core": "catalog:",
|
||||
"@opentui/solid": "catalog:",
|
||||
"@opentui/core": "0.1.103",
|
||||
"@opentui/solid": "0.1.103",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -403,7 +403,7 @@
|
||||
"open": "10.1.2",
|
||||
"opencode-gitlab-auth": "2.0.1",
|
||||
"opencode-poe-auth": "0.0.1",
|
||||
"opentui-spinner": "catalog:",
|
||||
"opentui-spinner": "0.0.6",
|
||||
"partial-json": "0.1.7",
|
||||
"remeda": "catalog:",
|
||||
"semver": "^7.6.3",
|
||||
@@ -460,15 +460,15 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
"zod": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opentui/core": "catalog:",
|
||||
"@opentui/solid": "catalog:",
|
||||
"@opentui/core": "0.1.103",
|
||||
"@opentui/solid": "0.1.103",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
@@ -495,7 +495,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -510,7 +510,7 @@
|
||||
},
|
||||
"packages/shared": {
|
||||
"name": "@opencode-ai/shared",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -534,7 +534,7 @@
|
||||
},
|
||||
"packages/slack": {
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1",
|
||||
@@ -569,7 +569,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
@@ -618,7 +618,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode-ai/web",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -678,8 +678,8 @@
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@opentui/core": "0.1.103",
|
||||
"@opentui/solid": "0.1.103",
|
||||
"@opentui/core": "0.1.99",
|
||||
"@opentui/solid": "0.1.99",
|
||||
"@pierre/diffs": "1.1.0-beta.18",
|
||||
"@playwright/test": "1.59.1",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
@@ -708,7 +708,6 @@
|
||||
"luxon": "3.6.1",
|
||||
"marked": "17.0.1",
|
||||
"marked-shiki": "1.2.1",
|
||||
"opentui-spinner": "0.0.6",
|
||||
"remeda": "2.26.0",
|
||||
"remend": "1.3.0",
|
||||
"semver": "7.7.4",
|
||||
|
||||
+2
-3
@@ -34,8 +34,8 @@
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@opentui/core": "0.1.103",
|
||||
"@opentui/solid": "0.1.103",
|
||||
"@opentui/core": "0.1.99",
|
||||
"@opentui/solid": "0.1.99",
|
||||
"ulid": "3.0.1",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@types/luxon": "3.7.1",
|
||||
@@ -46,7 +46,6 @@
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/diffs": "1.1.0-beta.18",
|
||||
"opentui-spinner": "0.0.6",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -27,12 +27,16 @@ Chart.register(BarController, BarElement, CategoryScale, LinearScale, Tooltip, L
|
||||
async function getCosts(workspaceID: string, year: number, month: number) {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
const startDate = new Date(year, month, 1)
|
||||
const endDate = new Date(year, month + 1, 1)
|
||||
// Fetch a UTC buffer so client-side local dates at month boundaries are not dropped.
|
||||
const startDate = new Date(Date.UTC(year, month, 1))
|
||||
startDate.setUTCDate(startDate.getUTCDate() - 1)
|
||||
const endDate = new Date(Date.UTC(year, month + 1, 1))
|
||||
endDate.setUTCDate(endDate.getUTCDate() + 1)
|
||||
const hourBucket = sql<number>`FLOOR(UNIX_TIMESTAMP(${UsageTable.timeCreated}) / 3600)`
|
||||
const usageData = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
date: sql<string>`DATE(${UsageTable.timeCreated})`,
|
||||
hourBucket: hourBucket.as("hour_bucket"),
|
||||
model: UsageTable.model,
|
||||
totalCost: sum(UsageTable.cost),
|
||||
keyId: UsageTable.keyID,
|
||||
@@ -47,7 +51,7 @@ async function getCosts(workspaceID: string, year: number, month: number) {
|
||||
),
|
||||
)
|
||||
.groupBy(
|
||||
sql`DATE(${UsageTable.timeCreated})`,
|
||||
hourBucket,
|
||||
UsageTable.model,
|
||||
UsageTable.keyID,
|
||||
sql`JSON_EXTRACT(${UsageTable.enrichment}, '$.plan')`,
|
||||
@@ -55,6 +59,7 @@ async function getCosts(workspaceID: string, year: number, month: number) {
|
||||
.then((x) =>
|
||||
x.map((r) => ({
|
||||
...r,
|
||||
hourBucket: Number(r.hourBucket),
|
||||
totalCost: r.totalCost ? parseInt(r.totalCost) : 0,
|
||||
plan: r.plan as "sub" | "lite" | "byok" | null,
|
||||
})),
|
||||
@@ -125,15 +130,13 @@ function getModelColor(model: string): string {
|
||||
}
|
||||
|
||||
function formatDateLabel(dateStr: string): string {
|
||||
const date = new Date()
|
||||
const [y, m, d] = dateStr.split("-").map(Number)
|
||||
date.setFullYear(y)
|
||||
date.setMonth(m - 1)
|
||||
date.setDate(d)
|
||||
date.setHours(0, 0, 0, 0)
|
||||
const month = date.toLocaleDateString(undefined, { month: "short" })
|
||||
const day = date.getUTCDate().toString().padStart(2, "0")
|
||||
return `${month} ${day}`
|
||||
const [year, month, day] = dateStr.split("-").map(Number)
|
||||
const date = new Date(year, month - 1, day)
|
||||
return `${date.toLocaleDateString(undefined, { month: "short" })} ${day.toString().padStart(2, "0")}`
|
||||
}
|
||||
|
||||
function formatDateKey(date: Date) {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`
|
||||
}
|
||||
|
||||
function addOpacityToColor(color: string, opacity: number): string {
|
||||
@@ -179,17 +182,24 @@ export function GraphSection() {
|
||||
|
||||
const onSelectKey = (keyID: string | null) => setStore({ key: keyID, keyDropdownOpen: false })
|
||||
|
||||
const getModels = createMemo(() => {
|
||||
if (!store.data?.usage) return []
|
||||
return Array.from(new Set(store.data.usage.map((row) => row.model))).sort()
|
||||
})
|
||||
|
||||
const getDates = createMemo(() => {
|
||||
const daysInMonth = new Date(store.year, store.month + 1, 0).getDate()
|
||||
return Array.from({ length: daysInMonth }, (_, i) => {
|
||||
const date = new Date(store.year, store.month, i + 1)
|
||||
return date.toISOString().split("T")[0]
|
||||
})
|
||||
const dates = Array.from({ length: daysInMonth }, (_, i) => formatDateKey(new Date(store.year, store.month, i + 1)))
|
||||
const dateSet = new Set(dates)
|
||||
return {
|
||||
dates,
|
||||
dateSet,
|
||||
}
|
||||
})
|
||||
|
||||
const getUsageForMonth = createMemo(() => {
|
||||
if (!store.data?.usage) return []
|
||||
const dateSet = getDates().dateSet
|
||||
return store.data.usage.filter((row) => dateSet.has(formatDateKey(new Date(row.hourBucket * 3600 * 1000))))
|
||||
})
|
||||
|
||||
const getModels = createMemo(() => {
|
||||
return Array.from(new Set(getUsageForMonth().map((row) => row.model))).sort()
|
||||
})
|
||||
|
||||
const getKeyName = (keyID: string | null): string => {
|
||||
@@ -205,9 +215,9 @@ export function GraphSection() {
|
||||
const isCurrentMonth = () => store.year === now.getFullYear() && store.month === now.getMonth()
|
||||
|
||||
const chartConfig = createMemo((): ChartConfiguration | null => {
|
||||
const data = store.data
|
||||
const dates = getDates()
|
||||
if (!data?.usage?.length) return null
|
||||
const dates = getDates().dates
|
||||
const usage = getUsageForMonth()
|
||||
if (usage.length === 0) return null
|
||||
|
||||
store.colorScheme
|
||||
const styles = getComputedStyle(document.documentElement)
|
||||
@@ -229,11 +239,12 @@ export function GraphSection() {
|
||||
dailyDataLite.set(dateKey, new Map())
|
||||
}
|
||||
|
||||
data.usage
|
||||
usage
|
||||
.filter((row) => (store.key ? row.keyId === store.key : true))
|
||||
.forEach((row) => {
|
||||
const dateKey = formatDateKey(new Date(row.hourBucket * 3600 * 1000))
|
||||
const targetMap = row.plan === "sub" ? dailyDataSub : row.plan === "lite" ? dailyDataLite : dailyDataRegular
|
||||
const dayMap = targetMap.get(row.date)
|
||||
const dayMap = targetMap.get(dateKey)
|
||||
if (!dayMap) return
|
||||
dayMap.set(row.model, (dayMap.get(row.model) ?? 0) + row.totalCost)
|
||||
})
|
||||
@@ -286,6 +297,8 @@ export function GraphSection() {
|
||||
}),
|
||||
]
|
||||
|
||||
if (datasets.length === 0) return null
|
||||
|
||||
return {
|
||||
type: "bar",
|
||||
data: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"private": true,
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.14.25"
|
||||
version = "1.14.24"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/anomalyco/opencode"
|
||||
@@ -11,26 +11,26 @@ name = "OpenCode"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.25/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.24/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -124,8 +124,8 @@
|
||||
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
|
||||
"@opentelemetry/sdk-trace-base": "2.6.1",
|
||||
"@opentelemetry/sdk-trace-node": "2.6.1",
|
||||
"@opentui/core": "catalog:",
|
||||
"@opentui/solid": "catalog:",
|
||||
"@opentui/core": "0.1.103",
|
||||
"@opentui/solid": "0.1.103",
|
||||
"@parcel/watcher": "2.5.1",
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@solid-primitives/event-bus": "1.1.2",
|
||||
@@ -160,7 +160,7 @@
|
||||
"open": "10.1.2",
|
||||
"opencode-gitlab-auth": "2.0.1",
|
||||
"opencode-poe-auth": "0.0.1",
|
||||
"opentui-spinner": "catalog:",
|
||||
"opentui-spinner": "0.0.6",
|
||||
"partial-json": "0.1.7",
|
||||
"remeda": "catalog:",
|
||||
"semver": "^7.6.3",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
export * as ConfigPermission from "./permission"
|
||||
import { Schema, SchemaGetter } from "effect"
|
||||
import z from "zod"
|
||||
import { ZodOverride, zod } from "@/util/effect-zod"
|
||||
import { zod } from "@/util/effect-zod"
|
||||
import { withStatics } from "@/util/schema"
|
||||
|
||||
export const Action = Schema.Literals(["ask", "allow", "deny"])
|
||||
@@ -19,9 +18,17 @@ export const Rule = Schema.Union([Action, Object])
|
||||
.pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type Rule = Schema.Schema.Type<typeof Rule>
|
||||
|
||||
// Known permission keys get explicit types in the Effect schema for generated
|
||||
// docs/types. Runtime config parsing uses `InfoZod` below so user key order is
|
||||
// preserved for permission precedence.
|
||||
// Known permission keys get explicit types — most are full Rule (either a
|
||||
// single Action or a per-pattern object), but a handful of tools take no
|
||||
// sub-target patterns and are Action-only. Unknown keys fall through the
|
||||
// Record rest signature as Rule.
|
||||
//
|
||||
// StructWithRest canonicalises key order on decode (known first, then rest),
|
||||
// which used to require the `__originalKeys` preprocess hack because
|
||||
// `Permission.fromConfig` depended on the user's insertion order. That
|
||||
// dependency is gone — `fromConfig` now sorts top-level keys so wildcard
|
||||
// permissions come before specifics, making the final precedence
|
||||
// order-independent.
|
||||
const InputObject = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
read: Schema.optional(Rule),
|
||||
@@ -53,35 +60,6 @@ const InputSchema = Schema.Union([Action, InputObject])
|
||||
const normalizeInput = (input: Schema.Schema.Type<typeof InputSchema>): Schema.Schema.Type<typeof InputObject> =>
|
||||
typeof input === "string" ? { "*": input } : input
|
||||
|
||||
const InfoZod = z
|
||||
.union([
|
||||
zod(Action),
|
||||
z.intersection(
|
||||
z.record(z.string(), zod(Rule)),
|
||||
z
|
||||
.object({
|
||||
read: zod(Rule).optional(),
|
||||
edit: zod(Rule).optional(),
|
||||
glob: zod(Rule).optional(),
|
||||
grep: zod(Rule).optional(),
|
||||
list: zod(Rule).optional(),
|
||||
bash: zod(Rule).optional(),
|
||||
task: zod(Rule).optional(),
|
||||
external_directory: zod(Rule).optional(),
|
||||
todowrite: zod(Action).optional(),
|
||||
question: zod(Action).optional(),
|
||||
webfetch: zod(Action).optional(),
|
||||
websearch: zod(Action).optional(),
|
||||
codesearch: zod(Action).optional(),
|
||||
lsp: zod(Rule).optional(),
|
||||
doom_loop: zod(Action).optional(),
|
||||
skill: zod(Rule).optional(),
|
||||
})
|
||||
.catchall(zod(Rule)),
|
||||
),
|
||||
])
|
||||
.transform(normalizeInput)
|
||||
|
||||
export const Info = InputSchema.pipe(
|
||||
Schema.decodeTo(InputObject, {
|
||||
decode: SchemaGetter.transform(normalizeInput),
|
||||
@@ -92,7 +70,6 @@ export const Info = InputSchema.pipe(
|
||||
}),
|
||||
)
|
||||
.annotate({ identifier: "PermissionConfig" })
|
||||
.annotate({ [ZodOverride]: InfoZod })
|
||||
.pipe(
|
||||
// Walker already emits the decodeTo transform into the derived zod (see
|
||||
// `encoded()` in effect-zod.ts), so just expose that directly.
|
||||
|
||||
@@ -288,8 +288,18 @@ function expand(pattern: string): string {
|
||||
}
|
||||
|
||||
export function fromConfig(permission: ConfigPermission.Info) {
|
||||
// Sort top-level keys so wildcard permissions (`*`, `mcp_*`) come before
|
||||
// specific ones. Combined with `findLast` in evaluate(), this gives the
|
||||
// intuitive semantic "specific tool rules override the `*` fallback"
|
||||
// regardless of the user's JSON key order. Sub-pattern order inside a
|
||||
// single permission key is preserved — only top-level keys are sorted.
|
||||
const entries = Object.entries(permission).sort(([a], [b]) => {
|
||||
const aWild = a.includes("*")
|
||||
const bWild = b.includes("*")
|
||||
return aWild === bWild ? 0 : aWild ? -1 : 1
|
||||
})
|
||||
const ruleset: Ruleset = []
|
||||
for (const [key, value] of Object.entries(permission)) {
|
||||
for (const [key, value] of entries) {
|
||||
if (typeof value === "string") {
|
||||
ruleset.push({ permission: key, action: value, pattern: "*" })
|
||||
continue
|
||||
|
||||
@@ -36,6 +36,7 @@ export const LspTool = Tool.define(
|
||||
Effect.gen(function* () {
|
||||
const lsp = yield* LSP.Service
|
||||
const fs = yield* AppFileSystem.Service
|
||||
|
||||
return {
|
||||
description: DESCRIPTION,
|
||||
parameters: Parameters,
|
||||
@@ -46,29 +47,12 @@ export const LspTool = Tool.define(
|
||||
Effect.gen(function* () {
|
||||
const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath)
|
||||
yield* assertExternalDirectoryEffect(ctx, file)
|
||||
const meta =
|
||||
args.operation === "workspaceSymbol"
|
||||
? { operation: args.operation }
|
||||
: args.operation === "documentSymbol"
|
||||
? { operation: args.operation, filePath: file }
|
||||
: { operation: args.operation, filePath: file, line: args.line, character: args.character }
|
||||
yield* ctx.ask({
|
||||
permission: "lsp",
|
||||
patterns: ["*"],
|
||||
always: ["*"],
|
||||
metadata: meta,
|
||||
})
|
||||
yield* ctx.ask({ permission: "lsp", patterns: ["*"], always: ["*"], metadata: {} })
|
||||
|
||||
const uri = pathToFileURL(file).href
|
||||
const position = { file, line: args.line - 1, character: args.character - 1 }
|
||||
const relPath = path.relative(Instance.worktree, file)
|
||||
const detail =
|
||||
args.operation === "workspaceSymbol"
|
||||
? ""
|
||||
: args.operation === "documentSymbol"
|
||||
? relPath
|
||||
: `${relPath}:${args.line}:${args.character}`
|
||||
const title = detail ? `${args.operation} ${detail}` : args.operation
|
||||
const title = `${args.operation} ${relPath}:${args.line}:${args.character}`
|
||||
|
||||
const exists = yield* fs.existsSafe(file)
|
||||
if (!exists) throw new Error(`File not found: ${file}`)
|
||||
@@ -106,7 +90,7 @@ export const LspTool = Tool.define(
|
||||
metadata: { result },
|
||||
output: result.length === 0 ? `No results found for ${args.operation}` : JSON.stringify(result, null, 2),
|
||||
}
|
||||
}).pipe(Effect.orDie),
|
||||
}),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1495,9 +1495,16 @@ test("merges legacy tools with existing permission config", async () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("permission config preserves user key order", async () => {
|
||||
// Permission precedence follows the order users write in config, so parsing
|
||||
// must not canonicalise known keys ahead of wildcard or custom keys.
|
||||
test("permission config canonicalises known keys first, preserves rest-key insertion order", async () => {
|
||||
// ConfigPermission.Info is a StructWithRest schema — the decoder reorders
|
||||
// keys into declaration-order for known permission names (edit, read,
|
||||
// todowrite, external_directory are declared in `config/permission.ts`),
|
||||
// followed by rest keys in the user's insertion order.
|
||||
//
|
||||
// Rule precedence is NOT affected by this reordering: `Permission.fromConfig`
|
||||
// sorts wildcards before specifics before iterating. See the
|
||||
// "fromConfig - specific key beats wildcard regardless of JSON key order"
|
||||
// test in test/permission/next.test.ts for the behavioural guarantee.
|
||||
await using tmp = await tmpdir({
|
||||
init: async (dir) => {
|
||||
await Filesystem.write(
|
||||
@@ -1525,12 +1532,15 @@ test("permission config preserves user key order", async () => {
|
||||
fn: async () => {
|
||||
const config = await load()
|
||||
expect(Object.keys(config.permission!)).toEqual([
|
||||
"*",
|
||||
"edit",
|
||||
"write",
|
||||
"external_directory",
|
||||
// known fields that the user provided, in declaration order from
|
||||
// config/permission.ts (read, edit, ..., external_directory, todowrite)
|
||||
"read",
|
||||
"edit",
|
||||
"external_directory",
|
||||
"todowrite",
|
||||
// rest keys (not in the known list), in user's insertion order
|
||||
"*",
|
||||
"write",
|
||||
"thoughts_*",
|
||||
"reasoning_model_*",
|
||||
"tools_*",
|
||||
|
||||
@@ -128,45 +128,61 @@ test("fromConfig - does not expand tilde in middle of path", () => {
|
||||
expect(result).toEqual([{ permission: "external_directory", pattern: "/some/~/path", action: "allow" }])
|
||||
})
|
||||
|
||||
// Permission precedence follows config insertion order. `evaluate()` uses the
|
||||
// last matching rule, so later config entries intentionally override earlier
|
||||
// entries even when a wildcard appears after a specific permission.
|
||||
// Top-level wildcard-vs-specific precedence semantics.
|
||||
//
|
||||
// fromConfig sorts top-level keys so wildcard permissions (containing "*")
|
||||
// come before specific permissions. Combined with `findLast` in evaluate(),
|
||||
// this gives the intuitive semantic "specific tool rules override the `*`
|
||||
// fallback", regardless of the order the user wrote the keys in their JSON.
|
||||
//
|
||||
// Sub-pattern order inside a single permission key (e.g. `bash: { "*": "allow", "rm": "deny" }`)
|
||||
// still depends on insertion order — only top-level keys are sorted.
|
||||
|
||||
test("fromConfig - preserves top-level config key order", () => {
|
||||
test("fromConfig - specific key beats wildcard regardless of JSON key order", () => {
|
||||
const wildcardFirst = Permission.fromConfig({ "*": "deny", bash: "allow" })
|
||||
const specificFirst = Permission.fromConfig({ bash: "allow", "*": "deny" })
|
||||
|
||||
expect(wildcardFirst.map((r) => r.permission)).toEqual(["*", "bash"])
|
||||
expect(specificFirst.map((r) => r.permission)).toEqual(["bash", "*"])
|
||||
// Both orderings produce the same ruleset
|
||||
expect(wildcardFirst).toEqual(specificFirst)
|
||||
|
||||
// And both evaluate bash → allow (bash rule wins over * fallback)
|
||||
expect(Permission.evaluate("bash", "ls", wildcardFirst).action).toBe("allow")
|
||||
expect(Permission.evaluate("bash", "ls", specificFirst).action).toBe("deny")
|
||||
expect(Permission.evaluate("bash", "ls", specificFirst).action).toBe("allow")
|
||||
})
|
||||
|
||||
test("fromConfig - wildcard acts as fallback when it appears before specifics", () => {
|
||||
const ruleset = Permission.fromConfig({ "*": "ask", bash: "allow" })
|
||||
test("fromConfig - wildcard acts as fallback for permissions with no specific rule", () => {
|
||||
const ruleset = Permission.fromConfig({ bash: "allow", "*": "ask" })
|
||||
expect(Permission.evaluate("edit", "foo.ts", ruleset).action).toBe("ask")
|
||||
expect(Permission.evaluate("bash", "ls", ruleset).action).toBe("allow")
|
||||
})
|
||||
|
||||
test("fromConfig - top-level ordering is not sorted by wildcard specificity", () => {
|
||||
test("fromConfig - top-level ordering: wildcards first, specifics after", () => {
|
||||
const ruleset = Permission.fromConfig({
|
||||
bash: "allow",
|
||||
"*": "ask",
|
||||
edit: "deny",
|
||||
"mcp_*": "allow",
|
||||
})
|
||||
expect(ruleset.map((r) => r.permission)).toEqual(["bash", "*", "edit", "mcp_*"])
|
||||
// wildcards (* and mcp_*) come before specifics (bash, edit)
|
||||
const permissions = ruleset.map((r) => r.permission)
|
||||
expect(permissions.slice(0, 2).sort()).toEqual(["*", "mcp_*"])
|
||||
expect(permissions.slice(2)).toEqual(["bash", "edit"])
|
||||
})
|
||||
|
||||
test("fromConfig - sub-pattern insertion order inside a tool key is preserved", () => {
|
||||
test("fromConfig - sub-pattern insertion order inside a tool key is preserved (only top-level sorts)", () => {
|
||||
// Sub-patterns within a single tool key use the documented "`*` first,
|
||||
// specific patterns after" convention (findLast picks specifics). The
|
||||
// top-level sort must not touch sub-pattern ordering.
|
||||
const ruleset = Permission.fromConfig({ bash: { "*": "deny", "git *": "allow" } })
|
||||
expect(ruleset.map((r) => r.pattern)).toEqual(["*", "git *"])
|
||||
// * fallback for unknown commands
|
||||
expect(Permission.evaluate("bash", "rm foo", ruleset).action).toBe("deny")
|
||||
// specific pattern wins for git commands (it's last, findLast picks it)
|
||||
expect(Permission.evaluate("bash", "git status", ruleset).action).toBe("allow")
|
||||
})
|
||||
|
||||
test("fromConfig - documented fallback-first example", () => {
|
||||
test("fromConfig - canonical documented example unchanged", () => {
|
||||
// Regression guard for the example in docs/permissions.mdx
|
||||
const ruleset = Permission.fromConfig({ "*": "ask", bash: "allow", edit: "deny" })
|
||||
expect(Permission.evaluate("bash", "ls", ruleset).action).toBe("allow")
|
||||
expect(Permission.evaluate("edit", "foo.ts", ruleset).action).toBe("deny")
|
||||
@@ -432,7 +448,7 @@ test("evaluate - wildcard permission fallback for unknown tool", () => {
|
||||
expect(result.action).toBe("ask")
|
||||
})
|
||||
|
||||
test("evaluate - later wildcard permission can override earlier specific permission", () => {
|
||||
test("evaluate - permission patterns sorted by length regardless of object order", () => {
|
||||
const result = Permission.evaluate("bash", "rm", [
|
||||
{ permission: "bash", pattern: "*", action: "allow" },
|
||||
{ permission: "*", pattern: "*", action: "deny" },
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
import { afterEach, describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import path from "path"
|
||||
import { Agent } from "../../src/agent/agent"
|
||||
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
|
||||
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
||||
import { LSP } from "../../src/lsp"
|
||||
import { Permission } from "../../src/permission"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { MessageID, SessionID } from "../../src/session/schema"
|
||||
import { Tool, Truncate } from "../../src/tool"
|
||||
import { LspTool } from "../../src/tool/lsp"
|
||||
import { provideTmpdirInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
afterEach(async () => {
|
||||
await Instance.disposeAll()
|
||||
})
|
||||
|
||||
const ctx = {
|
||||
sessionID: SessionID.make("ses_test"),
|
||||
messageID: MessageID.make(""),
|
||||
callID: "",
|
||||
agent: "build",
|
||||
abort: AbortSignal.any([]),
|
||||
messages: [],
|
||||
metadata: () => Effect.void,
|
||||
ask: () => Effect.void,
|
||||
}
|
||||
|
||||
const lsp = Layer.succeed(
|
||||
LSP.Service,
|
||||
LSP.Service.of({
|
||||
init: () => Effect.void,
|
||||
status: () => Effect.succeed([]),
|
||||
hasClients: () => Effect.succeed(true),
|
||||
touchFile: () => Effect.void,
|
||||
diagnostics: () => Effect.succeed({}),
|
||||
hover: () => Effect.succeed([]),
|
||||
definition: () => Effect.succeed([]),
|
||||
references: () => Effect.succeed([]),
|
||||
implementation: () => Effect.succeed([]),
|
||||
documentSymbol: () => Effect.succeed([]),
|
||||
workspaceSymbol: () => Effect.succeed([]),
|
||||
prepareCallHierarchy: () => Effect.succeed([]),
|
||||
incomingCalls: () => Effect.succeed([]),
|
||||
outgoingCalls: () => Effect.succeed([]),
|
||||
}),
|
||||
)
|
||||
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(
|
||||
Agent.defaultLayer,
|
||||
AppFileSystem.defaultLayer,
|
||||
CrossSpawnSpawner.defaultLayer,
|
||||
Truncate.defaultLayer,
|
||||
lsp,
|
||||
),
|
||||
)
|
||||
|
||||
const init = Effect.fn("LspToolTest.init")(function* () {
|
||||
const info = yield* LspTool
|
||||
return yield* info.init()
|
||||
})
|
||||
|
||||
const run = Effect.fn("LspToolTest.run")(function* (
|
||||
args: Tool.InferParameters<typeof LspTool>,
|
||||
next: Tool.Context = ctx,
|
||||
) {
|
||||
const tool = yield* init()
|
||||
return yield* tool.execute(args, next)
|
||||
})
|
||||
|
||||
const put = Effect.fn("LspToolTest.put")(function* (file: string) {
|
||||
const fs = yield* AppFileSystem.Service
|
||||
yield* fs.writeWithDirs(file, "export const x = 1\n")
|
||||
})
|
||||
|
||||
const asks = () => {
|
||||
const items: Array<Omit<Permission.Request, "id" | "sessionID" | "tool">> = []
|
||||
return {
|
||||
items,
|
||||
next: {
|
||||
...ctx,
|
||||
ask: (req: Omit<Permission.Request, "id" | "sessionID" | "tool">) =>
|
||||
Effect.sync(() => {
|
||||
items.push(req)
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe("tool.lsp", () => {
|
||||
describe("permission metadata", () => {
|
||||
it.live("keeps cursor details for position-based operations", () =>
|
||||
provideTmpdirInstance(
|
||||
(dir) =>
|
||||
Effect.gen(function* () {
|
||||
const file = path.join(dir, "test.ts")
|
||||
yield* put(file)
|
||||
|
||||
const { items, next } = asks()
|
||||
const result = yield* run({ operation: "goToDefinition", filePath: file, line: 3, character: 7 }, next)
|
||||
const req = items.find((item) => item.permission === "lsp")
|
||||
|
||||
expect(req).toBeDefined()
|
||||
expect(req!.metadata).toEqual({
|
||||
operation: "goToDefinition",
|
||||
filePath: file,
|
||||
line: 3,
|
||||
character: 7,
|
||||
})
|
||||
expect(result.title).toBe("goToDefinition test.ts:3:7")
|
||||
}),
|
||||
{ git: true },
|
||||
),
|
||||
)
|
||||
|
||||
it.live("omits cursor details for documentSymbol", () =>
|
||||
provideTmpdirInstance(
|
||||
(dir) =>
|
||||
Effect.gen(function* () {
|
||||
const file = path.join(dir, "test.ts")
|
||||
yield* put(file)
|
||||
|
||||
const { items, next } = asks()
|
||||
const result = yield* run({ operation: "documentSymbol", filePath: file, line: 3, character: 7 }, next)
|
||||
const req = items.find((item) => item.permission === "lsp")
|
||||
|
||||
expect(req).toBeDefined()
|
||||
expect(req!.metadata).toEqual({
|
||||
operation: "documentSymbol",
|
||||
filePath: file,
|
||||
})
|
||||
expect(result.title).toBe("documentSymbol test.ts")
|
||||
}),
|
||||
{ git: true },
|
||||
),
|
||||
)
|
||||
|
||||
it.live("omits file and cursor details for workspaceSymbol", () =>
|
||||
provideTmpdirInstance(
|
||||
(dir) =>
|
||||
Effect.gen(function* () {
|
||||
const file = path.join(dir, "test.ts")
|
||||
yield* put(file)
|
||||
|
||||
const { items, next } = asks()
|
||||
const result = yield* run({ operation: "workspaceSymbol", filePath: file, line: 3, character: 7 }, next)
|
||||
const req = items.find((item) => item.permission === "lsp")
|
||||
|
||||
expect(req).toBeDefined()
|
||||
expect(req!.metadata).toEqual({
|
||||
operation: "workspaceSymbol",
|
||||
})
|
||||
expect(result.title).toBe("workspaceSymbol")
|
||||
}),
|
||||
{ git: true },
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -34,8 +34,8 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opentui/core": "catalog:",
|
||||
"@opentui/solid": "catalog:",
|
||||
"@opentui/core": "0.1.103",
|
||||
"@opentui/solid": "0.1.103",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1206,9 +1206,7 @@ export type PermissionRuleConfig = PermissionActionConfig | PermissionObjectConf
|
||||
|
||||
export type PermissionConfig =
|
||||
| PermissionActionConfig
|
||||
| ({
|
||||
[key: string]: PermissionRuleConfig
|
||||
} & {
|
||||
| {
|
||||
read?: PermissionRuleConfig
|
||||
edit?: PermissionRuleConfig
|
||||
glob?: PermissionRuleConfig
|
||||
@@ -1226,7 +1224,7 @@ export type PermissionConfig =
|
||||
doom_loop?: PermissionActionConfig
|
||||
skill?: PermissionRuleConfig
|
||||
[key: string]: PermissionRuleConfig | PermissionActionConfig | undefined
|
||||
})
|
||||
}
|
||||
|
||||
export type AgentConfig = {
|
||||
model?: string
|
||||
|
||||
+52
-65
@@ -10951,73 +10951,60 @@
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
}
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"read": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"read": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"edit": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"glob": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"grep": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"list": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"bash": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"task": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"external_directory": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"todowrite": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"question": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"webfetch": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"websearch": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"codesearch": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"lsp": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"doom_loop": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"skill": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
}
|
||||
"edit": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"glob": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"grep": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"list": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"bash": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"task": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"external_directory": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"todowrite": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"question": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"webfetch": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"websearch": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"codesearch": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"lsp": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
},
|
||||
"doom_loop": {
|
||||
"$ref": "#/components/schemas/PermissionActionConfig"
|
||||
},
|
||||
"skill": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/PermissionRuleConfig"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"name": "@opencode-ai/shared",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "1.14.25",
|
||||
"version": "1.14.24",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user