Merge branch 'dev' into feat/canceled-prompts-in-history
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -41,6 +41,7 @@ export function trimSessions(
|
||||
.filter((s) => !s.time?.archived)
|
||||
.sort((a, b) => cmp(a.id, b.id))
|
||||
const roots = all.filter((s) => !s.parentID)
|
||||
roots.sort(compareSessionRecent)
|
||||
const children = all.filter((s) => !!s.parentID)
|
||||
const base = roots.slice(0, limit)
|
||||
const recent = takeRecentSessions(roots.slice(limit), SESSION_RECENT_LIMIT, cutoff)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-app",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -161,7 +161,9 @@ export async function POST(input: APIEvent) {
|
||||
})
|
||||
|
||||
if (userEmail) {
|
||||
if (coupon === LiteData.firstMonth100Coupon) {
|
||||
if (coupon === LiteData.firstMonth50Coupon) {
|
||||
await Billing.redeemCoupon(userEmail, "GO1MONTH50")
|
||||
} else if (coupon === LiteData.firstMonth100Coupon) {
|
||||
await Billing.redeemCoupon(userEmail, "GOFREEMONTH")
|
||||
} else if (coupon === LiteData.threeMonths100Coupon) {
|
||||
await Billing.redeemCoupon(userEmail, "GO3MONTHS100")
|
||||
|
||||
@@ -10,8 +10,11 @@ export function createRateLimiter(modelId: string, rateLimit: number | undefined
|
||||
const dict = i18n(localeFromRequest(request))
|
||||
|
||||
const limits = Subscription.getFreeLimits()
|
||||
const dailyLimit = rateLimit ?? limits.dailyRequests
|
||||
const isDefaultModel = !rateLimit
|
||||
const headersExist = Object.entries(limits.checkHeaders).every(
|
||||
([name, value]) => request.headers.get(name)?.toLowerCase().includes(value) ?? false,
|
||||
)
|
||||
const dailyLimit = !headersExist ? limits.dailyRequestsFallback : (rateLimit ?? limits.dailyRequests)
|
||||
const isDefaultModel = headersExist && !rateLimit
|
||||
|
||||
const ip = !rawIp.length ? "unknown" : rawIp
|
||||
const now = Date.now()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `coupon` MODIFY COLUMN `type` enum('BUILDATHON','GO1MONTH50','GOFREEMONTH','GO3MONTHS100','GO6MONTHS100','GO12MONTHS100') NOT NULL;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/console-core",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -10,7 +10,7 @@ if (!stage) throw new Error("Stage is required")
|
||||
const root = path.resolve(process.cwd(), "..", "..", "..")
|
||||
|
||||
// read the secret
|
||||
const ret = await $`bun sst secret list --stage frank`.cwd(root).text()
|
||||
const ret = await $`bun sst secret list --fallback`.cwd(root).text()
|
||||
const lines = ret.split("\n")
|
||||
const value = lines.find((line) => line.startsWith("ZEN_LIMITS"))?.split("=")[1]
|
||||
if (!value) throw new Error("ZEN_LIMITS not found")
|
||||
|
||||
@@ -6,7 +6,7 @@ import os from "os"
|
||||
import { Subscription } from "../src/subscription"
|
||||
|
||||
const root = path.resolve(process.cwd(), "..", "..", "..")
|
||||
const secrets = await $`bun sst secret list --stage frank`.cwd(root).text()
|
||||
const secrets = await $`bun sst secret list --fallback`.cwd(root).text()
|
||||
|
||||
// read value
|
||||
const lines = secrets.split("\n")
|
||||
@@ -25,4 +25,6 @@ const newValue = JSON.stringify(JSON.parse(await tempFile.text()))
|
||||
Subscription.validate(JSON.parse(newValue))
|
||||
|
||||
// update the secret
|
||||
await $`bun sst secret set ZEN_LIMITS ${newValue} --stage frank`.cwd(root)
|
||||
const envFile = Bun.file(path.join(os.tmpdir(), `limits-${Date.now()}.env`))
|
||||
await envFile.write(`ZEN_LIMITS="${newValue.replace(/"/g, '\\"')}"`)
|
||||
await $`bun sst secret load ${envFile.name} --fallback`.cwd(root)
|
||||
|
||||
@@ -156,33 +156,32 @@ export namespace Billing {
|
||||
}
|
||||
|
||||
export const redeemCoupon = async (email: string, type: (typeof CouponType)[number]) => {
|
||||
const coupon = await Database.use((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(CouponTable)
|
||||
.where(and(eq(CouponTable.email, email), eq(CouponTable.type, type)))
|
||||
.then((rows) => rows[0]),
|
||||
)
|
||||
if (!coupon) throw new Error("Invalid coupon code")
|
||||
if (coupon.timeRedeemed) throw new Error("Coupon already redeemed")
|
||||
// validate coupon type
|
||||
await (async () => {
|
||||
if (type === "GO1MONTH50") return
|
||||
const coupon = await Database.use((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(CouponTable)
|
||||
.where(and(eq(CouponTable.email, email), eq(CouponTable.type, type)))
|
||||
.then((rows) => rows[0]),
|
||||
)
|
||||
if (!coupon) throw new Error("Invalid coupon code")
|
||||
if (coupon.timeRedeemed) throw new Error("Coupon already redeemed")
|
||||
})()
|
||||
|
||||
// handle coupon type
|
||||
if (type === "BUILDATHON") await grantCredit(Actor.workspace(), 500)
|
||||
|
||||
await Database.use((tx) =>
|
||||
tx
|
||||
.update(CouponTable)
|
||||
.set({ timeRedeemed: sql`now()` })
|
||||
.where(and(eq(CouponTable.email, email), eq(CouponTable.type, type))),
|
||||
)
|
||||
}
|
||||
|
||||
export const getCoupons = async (email: string) => {
|
||||
return await Database.use((tx) =>
|
||||
tx
|
||||
.select({ type: CouponTable.type, timeRedeemed: CouponTable.timeRedeemed })
|
||||
.from(CouponTable)
|
||||
.where(and(eq(CouponTable.email, email), isNull(CouponTable.timeRedeemed)))
|
||||
.then((rows) => rows.map((row) => row.type)),
|
||||
.insert(CouponTable)
|
||||
.values({ email, type, timeRedeemed: sql`now()` })
|
||||
.onDuplicateKeyUpdate({
|
||||
set: {
|
||||
timeRedeemed: sql`now()`,
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -290,20 +289,29 @@ export namespace Billing {
|
||||
if (billing.subscriptionID) throw new Error("Already subscribed to Black")
|
||||
if (billing.liteSubscriptionID) throw new Error("Already subscribed to Lite")
|
||||
|
||||
const coupons = await Billing.getCoupons(email)
|
||||
const coupon = coupons.includes("GO12MONTHS100")
|
||||
? LiteData.twelveMonths100Coupon
|
||||
: coupons.includes("GO6MONTHS100")
|
||||
? LiteData.sixMonths100Coupon
|
||||
: coupons.includes("GO3MONTHS100")
|
||||
? LiteData.threeMonths100Coupon
|
||||
: coupons.includes("GOFREEMONTH")
|
||||
? LiteData.firstMonth100Coupon
|
||||
: LiteData.firstMonth50Coupon
|
||||
const coupons = await Database.use((tx) =>
|
||||
tx
|
||||
.select({ type: CouponTable.type, timeRedeemed: CouponTable.timeRedeemed })
|
||||
.from(CouponTable)
|
||||
.where(eq(CouponTable.email, email)),
|
||||
)
|
||||
|
||||
const coupon = (() => {
|
||||
if (coupons.some((coupon) => coupon.type === "GO12MONTHS100" && !coupon.timeRedeemed))
|
||||
return LiteData.twelveMonths100Coupon
|
||||
if (coupons.some((coupon) => coupon.type === "GO6MONTHS100" && !coupon.timeRedeemed))
|
||||
return LiteData.sixMonths100Coupon
|
||||
if (coupons.some((coupon) => coupon.type === "GO3MONTHS100" && !coupon.timeRedeemed))
|
||||
return LiteData.threeMonths100Coupon
|
||||
if (coupons.some((coupon) => coupon.type === "GOFREEMONTH" && !coupon.timeRedeemed))
|
||||
return LiteData.firstMonth100Coupon
|
||||
if (!coupons.some((coupon) => coupon.type === "GO1MONTH50")) return LiteData.firstMonth50Coupon
|
||||
return undefined
|
||||
})()
|
||||
const createSession = () =>
|
||||
Billing.stripe().checkout.sessions.create({
|
||||
mode: "subscription",
|
||||
discounts: [{ coupon }],
|
||||
discounts: coupon ? [{ coupon }] : undefined,
|
||||
...(billing.customerID
|
||||
? {
|
||||
customer: billing.customerID,
|
||||
|
||||
@@ -133,7 +133,14 @@ export const UsageTable = mysqlTable(
|
||||
(table) => [...workspaceIndexes(table), index("usage_time_created").on(table.workspaceID, table.timeCreated)],
|
||||
)
|
||||
|
||||
export const CouponType = ["BUILDATHON", "GOFREEMONTH", "GO3MONTHS100", "GO6MONTHS100", "GO12MONTHS100"] as const
|
||||
export const CouponType = [
|
||||
"BUILDATHON",
|
||||
"GO1MONTH50",
|
||||
"GOFREEMONTH",
|
||||
"GO3MONTHS100",
|
||||
"GO6MONTHS100",
|
||||
"GO12MONTHS100",
|
||||
] as const
|
||||
export const CouponTable = mysqlTable(
|
||||
"coupon",
|
||||
{
|
||||
|
||||
@@ -9,6 +9,8 @@ export namespace Subscription {
|
||||
free: z.object({
|
||||
promoTokens: z.number().int(),
|
||||
dailyRequests: z.number().int(),
|
||||
dailyRequestsFallback: z.number().int(),
|
||||
checkHeaders: z.record(z.string(), z.string()),
|
||||
}),
|
||||
lite: z.object({
|
||||
rollingLimit: z.number().int(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-function",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/console-mail",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -4,7 +4,7 @@ FROM ${REGISTRY}/build/base:24.04
|
||||
SHELL ["/bin/bash", "-lc"]
|
||||
|
||||
ARG NODE_VERSION=24.4.0
|
||||
ARG BUN_VERSION=1.3.13
|
||||
ARG BUN_VERSION=1.3.14
|
||||
|
||||
ENV BUN_INSTALL=/opt/bun
|
||||
ENV PATH=/opt/bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -112,9 +112,7 @@ export interface Interface {
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ModelsDev") {}
|
||||
|
||||
type Requirements = AppFileSystem.Service | HttpClient.HttpClient
|
||||
|
||||
export const layer: Layer.Layer<Service, never, Requirements> = Layer.effect(
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* AppFileSystem.Service
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -6,8 +6,6 @@ import { initLogging } from "./logging"
|
||||
const logger = initLogging()
|
||||
const { autoUpdater } = pkg
|
||||
|
||||
let downloadedUpdateVersion: string | undefined
|
||||
|
||||
export function setupAutoUpdater() {
|
||||
if (!UPDATER_ENABLED) return
|
||||
autoUpdater.logger = logger
|
||||
@@ -26,12 +24,6 @@ export function setupAutoUpdater() {
|
||||
|
||||
export async function checkUpdate() {
|
||||
if (!UPDATER_ENABLED) return { updateAvailable: false }
|
||||
if (downloadedUpdateVersion) {
|
||||
logger.log("returning cached downloaded update", {
|
||||
version: downloadedUpdateVersion,
|
||||
})
|
||||
return { updateAvailable: true, version: downloadedUpdateVersion }
|
||||
}
|
||||
logger.log("checking for updates", {
|
||||
currentVersion: app.getVersion(),
|
||||
channel: autoUpdater.channel,
|
||||
@@ -57,7 +49,6 @@ export async function checkUpdate() {
|
||||
logger.log("update available", { version })
|
||||
await autoUpdater.downloadUpdate()
|
||||
logger.log("update download completed", { version })
|
||||
downloadedUpdateVersion = version
|
||||
return { updateAvailable: true, version }
|
||||
} catch (error) {
|
||||
logger.error("update check failed", error)
|
||||
@@ -66,14 +57,15 @@ export async function checkUpdate() {
|
||||
}
|
||||
|
||||
export async function installUpdate(killSidecar: () => Promise<void>) {
|
||||
if (!downloadedUpdateVersion) {
|
||||
const result = await checkUpdate()
|
||||
if (!result.updateAvailable) {
|
||||
logger.log("install update skipped", {
|
||||
reason: "no downloaded update ready",
|
||||
reason: result.failed ? "update check failed" : "no update available",
|
||||
})
|
||||
return
|
||||
}
|
||||
logger.log("installing downloaded update", {
|
||||
version: downloadedUpdateVersion,
|
||||
version: result.version ?? null,
|
||||
})
|
||||
await killSidecar()
|
||||
autoUpdater.quitAndInstall()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/enterprise",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "opencode"
|
||||
name = "OpenCode"
|
||||
description = "The open source coding agent."
|
||||
version = "1.15.3"
|
||||
version = "1.15.4"
|
||||
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.15.3/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.4/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.3/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.4/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.3/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.4/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.15.3/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.4/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.15.3/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/anomalyco/opencode/releases/download/v1.15.4/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/function",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"name": "@opencode-ai/llm",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -6,6 +6,8 @@ import { GlobalBus } from "./global"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { Identifier } from "@/id/id"
|
||||
import type { InstanceContext } from "@/project/instance-context"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
|
||||
const log = Log.create({ service: "bus" })
|
||||
|
||||
@@ -185,11 +187,12 @@ export function createID() {
|
||||
}
|
||||
|
||||
export async function publish<D extends BusEvent.Definition>(
|
||||
ctx: InstanceContext,
|
||||
def: D,
|
||||
properties: BusProperties<D>,
|
||||
options?: { id?: string },
|
||||
) {
|
||||
return runPromise((svc) => svc.publish(def, properties, options))
|
||||
return runPromise((svc) => svc.publish(def, properties, options).pipe(Effect.provideService(InstanceRef, ctx)))
|
||||
}
|
||||
|
||||
export function subscribe<D extends BusEvent.Definition>(def: D, callback: (event: Payload<D>) => unknown) {
|
||||
|
||||
@@ -870,6 +870,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
})
|
||||
|
||||
event.on("installation.update-available", async (evt) => {
|
||||
console.log("installation.update-available", evt)
|
||||
const version = evt.properties.version
|
||||
|
||||
const skipped = kv.get("skipped_version")
|
||||
|
||||
@@ -188,6 +188,7 @@ export const Definitions = {
|
||||
"dialog.select.home": keybind("home", "Move to first dialog item"),
|
||||
"dialog.select.end": keybind("end", "Move to last dialog item"),
|
||||
"dialog.select.submit": keybind("return", "Submit selected dialog item"),
|
||||
"dialog.prompt.submit": keybind("return", "Submit dialog prompt"),
|
||||
"dialog.mcp.toggle": keybind("space", "Toggle MCP in MCP dialog"),
|
||||
"prompt.autocomplete.prev": keybind("up,ctrl+p", "Move to previous autocomplete item"),
|
||||
"prompt.autocomplete.next": keybind("down,ctrl+n", "Move to next autocomplete item"),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { TextareaRenderable, TextAttributes } from "@opentui/core"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useDialog, type DialogContext } from "./dialog"
|
||||
import { Show, createEffect, onMount, type JSX } from "solid-js"
|
||||
import { Show, createEffect, createSignal, onMount, type JSX } from "solid-js"
|
||||
import { Spinner } from "../component/spinner"
|
||||
import { useTuiConfig } from "../context/tui-config"
|
||||
import { useBindings, useCommandShortcut } from "../keymap"
|
||||
|
||||
export type DialogPromptProps = {
|
||||
title: string
|
||||
@@ -18,8 +20,32 @@ export type DialogPromptProps = {
|
||||
export function DialogPrompt(props: DialogPromptProps) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const tuiConfig = useTuiConfig()
|
||||
const submitShortcut = useCommandShortcut("dialog.prompt.submit")
|
||||
const [textareaTarget, setTextareaTarget] = createSignal<TextareaRenderable>()
|
||||
let textarea: TextareaRenderable
|
||||
|
||||
function confirm() {
|
||||
if (props.busy) return
|
||||
props.onConfirm?.(textarea.plainText)
|
||||
}
|
||||
|
||||
useBindings(() => ({
|
||||
target: textareaTarget,
|
||||
enabled: textareaTarget() !== undefined && !props.busy,
|
||||
// Dialog form semantics must win over the global managed textarea input layer.
|
||||
priority: 1,
|
||||
commands: [
|
||||
{
|
||||
name: "dialog.prompt.submit",
|
||||
title: "Submit dialog prompt",
|
||||
category: "Dialog",
|
||||
run: confirm,
|
||||
},
|
||||
],
|
||||
bindings: tuiConfig.keybinds.gather("dialog.prompt", ["dialog.prompt.submit"]),
|
||||
}))
|
||||
|
||||
onMount(() => {
|
||||
dialog.setSize("medium")
|
||||
setTimeout(() => {
|
||||
@@ -59,13 +85,10 @@ export function DialogPrompt(props: DialogPromptProps) {
|
||||
<box gap={1}>
|
||||
{props.description}
|
||||
<textarea
|
||||
onSubmit={() => {
|
||||
if (props.busy) return
|
||||
props.onConfirm?.(textarea.plainText)
|
||||
}}
|
||||
height={3}
|
||||
ref={(val: TextareaRenderable) => {
|
||||
textarea = val
|
||||
setTextareaTarget(val)
|
||||
}}
|
||||
initialValue={props.value}
|
||||
placeholder={props.placeholder ?? "Enter text"}
|
||||
@@ -80,9 +103,11 @@ export function DialogPrompt(props: DialogPromptProps) {
|
||||
</box>
|
||||
<box paddingBottom={1} gap={1} flexDirection="row">
|
||||
<Show when={!props.busy} fallback={<text fg={theme.textMuted}>processing...</text>}>
|
||||
<text fg={theme.text}>
|
||||
enter <span style={{ fg: theme.textMuted }}>submit</span>
|
||||
</text>
|
||||
<Show when={submitShortcut()}>
|
||||
<text fg={theme.text}>
|
||||
{submitShortcut()} <span style={{ fg: theme.textMuted }}>submit</span>
|
||||
</text>
|
||||
</Show>
|
||||
</Show>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Bus } from "@/bus"
|
||||
import { Config } from "@/config/config"
|
||||
import { AppRuntime } from "@/effect/app-runtime"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Installation } from "@/installation"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
|
||||
export async function upgrade() {
|
||||
const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.getGlobal()))
|
||||
@@ -13,7 +13,13 @@ export async function upgrade() {
|
||||
if (!latest) return
|
||||
|
||||
if (Flag.OPENCODE_ALWAYS_NOTIFY_UPDATE) {
|
||||
await Bus.publish(Installation.Event.UpdateAvailable, { version: latest })
|
||||
GlobalBus.emit("event", {
|
||||
directory: "global",
|
||||
payload: {
|
||||
type: Installation.Event.UpdateAvailable.type,
|
||||
properties: { version: latest },
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -22,12 +28,26 @@ export async function upgrade() {
|
||||
const kind = Installation.getReleaseType(InstallationVersion, latest)
|
||||
|
||||
if (config.autoupdate === "notify" || kind !== "patch") {
|
||||
await Bus.publish(Installation.Event.UpdateAvailable, { version: latest })
|
||||
GlobalBus.emit("event", {
|
||||
directory: "global",
|
||||
payload: {
|
||||
type: Installation.Event.UpdateAvailable.type,
|
||||
properties: { version: latest },
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (method === "unknown") return
|
||||
await Installation.upgrade(method, latest)
|
||||
.then(() => Bus.publish(Installation.Event.Updated, { version: latest }))
|
||||
.then(() =>
|
||||
GlobalBus.emit("event", {
|
||||
directory: "global",
|
||||
payload: {
|
||||
type: Installation.Event.Updated.type,
|
||||
properties: { version: latest },
|
||||
},
|
||||
}),
|
||||
)
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
export * as ConfigAgent from "./agent"
|
||||
|
||||
import { Exit, Schema, SchemaGetter } from "effect"
|
||||
import { Bus } from "@/bus"
|
||||
import { PositiveInt } from "@opencode-ai/core/schema"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Glob } from "@opencode-ai/core/util/glob"
|
||||
import { configEntryNameFromPath } from "./entry-name"
|
||||
import * as ConfigMarkdown from "./markdown"
|
||||
@@ -112,12 +110,7 @@ export async function load(dir: string) {
|
||||
dot: true,
|
||||
symlink: true,
|
||||
})) {
|
||||
const md = await ConfigMarkdown.parse(item).catch(async (err) => {
|
||||
const message = ConfigMarkdown.FrontmatterError.isInstance(err)
|
||||
? err.data.message
|
||||
: `Failed to parse agent ${item}`
|
||||
const { Session } = await import("@/session/session")
|
||||
void Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
|
||||
const md = await ConfigMarkdown.parse(item).catch((err) => {
|
||||
log.error("failed to load agent", { agent: item, err })
|
||||
return undefined
|
||||
})
|
||||
@@ -144,12 +137,7 @@ export async function loadMode(dir: string) {
|
||||
dot: true,
|
||||
symlink: true,
|
||||
})) {
|
||||
const md = await ConfigMarkdown.parse(item).catch(async (err) => {
|
||||
const message = ConfigMarkdown.FrontmatterError.isInstance(err)
|
||||
? err.data.message
|
||||
: `Failed to parse mode ${item}`
|
||||
const { Session } = await import("@/session/session")
|
||||
void Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
|
||||
const md = await ConfigMarkdown.parse(item).catch((err) => {
|
||||
log.error("failed to load mode", { mode: item, err })
|
||||
return undefined
|
||||
})
|
||||
|
||||
@@ -2,9 +2,7 @@ export * as ConfigCommand from "./command"
|
||||
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Cause, Exit, Schema } from "effect"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Glob } from "@opencode-ai/core/util/glob"
|
||||
import { Bus } from "@/bus"
|
||||
import { configEntryNameFromPath } from "./entry-name"
|
||||
import { InvalidError } from "./error"
|
||||
import * as ConfigMarkdown from "./markdown"
|
||||
@@ -32,12 +30,7 @@ export async function load(dir: string) {
|
||||
dot: true,
|
||||
symlink: true,
|
||||
})) {
|
||||
const md = await ConfigMarkdown.parse(item).catch(async (err) => {
|
||||
const message = ConfigMarkdown.FrontmatterError.isInstance(err)
|
||||
? err.data.message
|
||||
: `Failed to parse command ${item}`
|
||||
const { Session } = await import("@/session/session")
|
||||
void Bus.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
|
||||
const md = await ConfigMarkdown.parse(item).catch((err) => {
|
||||
log.error("failed to load command", { command: item, err })
|
||||
return undefined
|
||||
})
|
||||
|
||||
@@ -96,11 +96,11 @@ export const layer = Layer.effect(
|
||||
)
|
||||
|
||||
const cb: ParcelWatcher.SubscribeCallback = bridge.bind((err, evts) => {
|
||||
if (err) return
|
||||
// if (err) return
|
||||
for (const evt of evts) {
|
||||
if (evt.type === "create") void Bus.publish(Event.Updated, { file: evt.path, event: "add" })
|
||||
if (evt.type === "update") void Bus.publish(Event.Updated, { file: evt.path, event: "change" })
|
||||
if (evt.type === "delete") void Bus.publish(Event.Updated, { file: evt.path, event: "unlink" })
|
||||
if (evt.type === "create") void Bus.publish(ctx, Event.Updated, { file: evt.path, event: "add" })
|
||||
if (evt.type === "update") void Bus.publish(ctx, Event.Updated, { file: evt.path, event: "change" })
|
||||
if (evt.type === "delete") void Bus.publish(ctx, Event.Updated, { file: evt.path, event: "unlink" })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -291,7 +291,7 @@ export const layer = Layer.effect(
|
||||
if (!client) continue
|
||||
|
||||
result.push(client)
|
||||
Bus.publish(Event.Updated, {})
|
||||
await Bus.publish(ctx, Event.Updated, {})
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WorkspaceID } from "@/control-plane/schema"
|
||||
import { SessionV2 } from "@/v2/session"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { DateTime, Effect, Schema } from "effect"
|
||||
import { HttpApiBuilder, HttpApiError, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import { InstanceHttpApi } from "../../api"
|
||||
|
||||
@@ -55,7 +55,13 @@ const sessionCursor = {
|
||||
filters: Pick<SessionCursor, "directory" | "path" | "workspaceID" | "roots" | "start" | "search">,
|
||||
) {
|
||||
return Buffer.from(
|
||||
JSON.stringify({ id: session.id, time: session.time.created, order, direction, ...filters }),
|
||||
JSON.stringify({
|
||||
...filters,
|
||||
id: session.id,
|
||||
time: DateTime.toEpochMillis(session.time.updated),
|
||||
order,
|
||||
direction,
|
||||
}),
|
||||
).toString("base64url")
|
||||
},
|
||||
decode(input: string) {
|
||||
|
||||
@@ -16,10 +16,8 @@ import { Effect, Layer, Context, Schema } from "effect"
|
||||
import * as DateTime from "effect/DateTime"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { isOverflow as overflow, usable } from "./overflow"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { serviceUse } from "@/effect/service-use"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { SessionEvent } from "@opencode-ai/core/session-event"
|
||||
|
||||
@@ -638,14 +636,4 @@ export const defaultLayer = Layer.suspend(() =>
|
||||
),
|
||||
)
|
||||
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
|
||||
export async function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) {
|
||||
return runPromise((svc) => svc.isOverflow(input))
|
||||
}
|
||||
|
||||
export async function prune(input: { sessionID: SessionID }) {
|
||||
return runPromise((svc) => svc.prune(input))
|
||||
}
|
||||
|
||||
export * as SessionCompaction from "./compaction"
|
||||
|
||||
@@ -177,6 +177,8 @@ export const layer = Layer.effect(
|
||||
list: Effect.fn("V2Session.list")(function* (input) {
|
||||
const direction = input.cursor?.direction ?? "next"
|
||||
let order = input.order ?? "desc"
|
||||
// This is a load bearing sort, desktop relies on this
|
||||
const sortColumn = SessionTable.time_updated
|
||||
// Query the adjacent rows in reverse, then flip them back into the requested order below.
|
||||
if (direction === "previous" && order === "asc") order = "desc"
|
||||
if (direction === "previous" && order === "desc") order = "asc"
|
||||
@@ -186,18 +188,18 @@ export const layer = Layer.effect(
|
||||
conditions.push(or(eq(SessionTable.path, input.path), like(SessionTable.path, `${input.path}/%`))!)
|
||||
if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID))
|
||||
if (input.roots) conditions.push(isNull(SessionTable.parent_id))
|
||||
if (input.start) conditions.push(gte(SessionTable.time_created, input.start))
|
||||
if (input.start) conditions.push(gte(sortColumn, input.start))
|
||||
if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`))
|
||||
if (input.cursor) {
|
||||
conditions.push(
|
||||
order === "asc"
|
||||
? or(
|
||||
gt(SessionTable.time_created, input.cursor.time),
|
||||
and(eq(SessionTable.time_created, input.cursor.time), gt(SessionTable.id, input.cursor.id)),
|
||||
gt(sortColumn, input.cursor.time),
|
||||
and(eq(sortColumn, input.cursor.time), gt(SessionTable.id, input.cursor.id)),
|
||||
)!
|
||||
: or(
|
||||
lt(SessionTable.time_created, input.cursor.time),
|
||||
and(eq(SessionTable.time_created, input.cursor.time), lt(SessionTable.id, input.cursor.id)),
|
||||
lt(sortColumn, input.cursor.time),
|
||||
and(eq(sortColumn, input.cursor.time), lt(SessionTable.id, input.cursor.id)),
|
||||
)!,
|
||||
)
|
||||
}
|
||||
@@ -206,7 +208,7 @@ export const layer = Layer.effect(
|
||||
.from(SessionTable)
|
||||
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
||||
.orderBy(
|
||||
order === "asc" ? asc(SessionTable.time_created) : desc(SessionTable.time_created),
|
||||
order === "asc" ? asc(sortColumn) : desc(sortColumn),
|
||||
order === "asc" ? asc(SessionTable.id) : desc(SessionTable.id),
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import { TextareaRenderable } from "@opentui/core"
|
||||
import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
|
||||
import { testRender, useRenderer } from "@opentui/solid"
|
||||
import { expect, test } from "bun:test"
|
||||
import { mkdir } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { onCleanup } from "solid-js"
|
||||
import { tmpdir } from "../../fixture/fixture"
|
||||
import { createTuiResolvedConfig } from "../../fixture/tui-runtime"
|
||||
import type { TuiKeybind } from "../../../src/cli/cmd/tui/config/keybind"
|
||||
|
||||
async function wait(fn: () => boolean, timeout = 2000) {
|
||||
const start = Date.now()
|
||||
while (!fn()) {
|
||||
if (Date.now() - start > timeout) throw new Error("timed out waiting for condition")
|
||||
await Bun.sleep(10)
|
||||
}
|
||||
}
|
||||
|
||||
async function mountPrompt(input: {
|
||||
root: string
|
||||
keybinds: Partial<TuiKeybind.Keybinds>
|
||||
onConfirm: (value: string) => void
|
||||
}) {
|
||||
const { Global } = await import("@opencode-ai/core/global")
|
||||
const previous = {
|
||||
config: Global.Path.config,
|
||||
state: Global.Path.state,
|
||||
}
|
||||
Global.Path.config = path.join(input.root, "config")
|
||||
Global.Path.state = path.join(input.root, "state")
|
||||
await mkdir(Global.Path.config, { recursive: true })
|
||||
await mkdir(Global.Path.state, { recursive: true })
|
||||
await Bun.write(path.join(Global.Path.state, "kv.json"), "{}")
|
||||
|
||||
const [
|
||||
{ DialogProvider },
|
||||
{ DialogPrompt },
|
||||
{ KVProvider },
|
||||
{ ThemeProvider },
|
||||
{ TuiConfigProvider },
|
||||
{ ToastProvider },
|
||||
{ OpencodeKeymapProvider, registerOpencodeKeymap },
|
||||
] = await Promise.all([
|
||||
import("../../../src/cli/cmd/tui/ui/dialog"),
|
||||
import("../../../src/cli/cmd/tui/ui/dialog-prompt"),
|
||||
import("../../../src/cli/cmd/tui/context/kv"),
|
||||
import("../../../src/cli/cmd/tui/context/theme"),
|
||||
import("../../../src/cli/cmd/tui/context/tui-config"),
|
||||
import("../../../src/cli/cmd/tui/ui/toast"),
|
||||
import("../../../src/cli/cmd/tui/keymap"),
|
||||
])
|
||||
|
||||
function Harness() {
|
||||
const renderer = useRenderer()
|
||||
const keymap = createDefaultOpenTuiKeymap(renderer)
|
||||
const resolvedConfig = createTuiResolvedConfig({
|
||||
keybinds: input.keybinds,
|
||||
leader_timeout: 1000,
|
||||
})
|
||||
const off = registerOpencodeKeymap(keymap, renderer, resolvedConfig)
|
||||
onCleanup(off)
|
||||
|
||||
return (
|
||||
<OpencodeKeymapProvider keymap={keymap}>
|
||||
<TuiConfigProvider config={resolvedConfig}>
|
||||
<KVProvider>
|
||||
<ThemeProvider mode="dark">
|
||||
<ToastProvider>
|
||||
<DialogProvider>
|
||||
<DialogPrompt title="Rename Session" value="draft" onConfirm={input.onConfirm} />
|
||||
</DialogProvider>
|
||||
</ToastProvider>
|
||||
</ThemeProvider>
|
||||
</KVProvider>
|
||||
</TuiConfigProvider>
|
||||
</OpencodeKeymapProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const app = await testRender(() => <Harness />, { kittyKeyboard: true })
|
||||
return {
|
||||
app,
|
||||
async cleanup() {
|
||||
app.renderer.destroy()
|
||||
Global.Path.config = previous.config
|
||||
Global.Path.state = previous.state
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test("dialog prompt submit wins when return is also input newline", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const confirmed: string[] = []
|
||||
const prompt = await mountPrompt({
|
||||
root: tmp.path,
|
||||
keybinds: {
|
||||
input_submit: "super+return",
|
||||
input_newline: "return,shift+return,alt+return,ctrl+j",
|
||||
},
|
||||
onConfirm: (value) => confirmed.push(value),
|
||||
})
|
||||
|
||||
try {
|
||||
await wait(() => prompt.app.renderer.currentFocusedEditor instanceof TextareaRenderable)
|
||||
const textarea = prompt.app.renderer.currentFocusedEditor
|
||||
if (!(textarea instanceof TextareaRenderable)) throw new Error("expected focused dialog textarea")
|
||||
|
||||
prompt.app.mockInput.pressEnter()
|
||||
|
||||
expect(confirmed).toEqual(["draft"])
|
||||
expect(textarea.plainText).toBe("draft")
|
||||
} finally {
|
||||
await prompt.cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test("dialog prompt submit can be rebound separately from input submit", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const confirmed: string[] = []
|
||||
const prompt = await mountPrompt({
|
||||
root: tmp.path,
|
||||
keybinds: {
|
||||
input_submit: "return",
|
||||
"dialog.prompt.submit": "ctrl+y",
|
||||
},
|
||||
onConfirm: (value) => confirmed.push(value),
|
||||
})
|
||||
|
||||
try {
|
||||
await wait(() => prompt.app.renderer.currentFocusedEditor instanceof TextareaRenderable)
|
||||
const textarea = prompt.app.renderer.currentFocusedEditor
|
||||
if (!(textarea instanceof TextareaRenderable)) throw new Error("expected focused dialog textarea")
|
||||
|
||||
prompt.app.mockInput.pressEnter()
|
||||
expect(confirmed).toEqual([])
|
||||
expect(textarea.plainText).toBe("draft")
|
||||
|
||||
prompt.app.mockInput.pressKey("y", { ctrl: true })
|
||||
|
||||
expect(confirmed).toEqual(["draft"])
|
||||
} finally {
|
||||
await prompt.cleanup()
|
||||
}
|
||||
})
|
||||
@@ -470,6 +470,7 @@ it.instance("resolves keybind lookup from canonical keybinds", () =>
|
||||
which_key_toggle: "alt+k",
|
||||
editor_open: "ctrl+e",
|
||||
"prompt.autocomplete.next": "ctrl+j",
|
||||
"dialog.prompt.submit": "ctrl+s",
|
||||
"dialog.mcp.toggle": "ctrl+t",
|
||||
model_favorite_toggle: "ctrl+f",
|
||||
"dialog.plugins.install": "shift+i",
|
||||
@@ -491,6 +492,7 @@ it.instance("resolves keybind lookup from canonical keybinds", () =>
|
||||
)
|
||||
expect(config.keybinds.get("prompt.editor")?.[0]?.key).toBe("ctrl+e")
|
||||
expect(config.keybinds.get("prompt.autocomplete.next")?.[0]?.key).toBe("ctrl+j")
|
||||
expect(config.keybinds.get("dialog.prompt.submit")?.[0]?.key).toBe("ctrl+s")
|
||||
expect(config.keybinds.get("dialog.mcp.toggle")?.[0]?.key).toBe("ctrl+t")
|
||||
expect(config.keybinds.get("model.dialog.favorite")?.[0]?.key).toBe("ctrl+f")
|
||||
expect(config.keybinds.get("dialog.plugins.install")?.[0]?.key).toBe("shift+i")
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { describe, expect, spyOn } from "bun:test"
|
||||
import path from "path"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { Deferred, Effect, Layer } from "effect"
|
||||
import { Bus } from "@/bus"
|
||||
import { Config } from "@/config/config"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
import * as LSPServer from "@/lsp/server"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { provideTmpdirInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { awaitWithTimeout, testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(Layer.mergeAll(LSP.defaultLayer, CrossSpawnSpawner.defaultLayer))
|
||||
const experimentalTyIt = testEffect(
|
||||
@@ -16,6 +17,7 @@ const experimentalTyIt = testEffect(
|
||||
CrossSpawnSpawner.defaultLayer,
|
||||
),
|
||||
)
|
||||
const fakeServerPath = path.join(__dirname, "../fixture/lsp/fake-lsp-server.js")
|
||||
const disabledDownloadIt = testEffect(
|
||||
Layer.mergeAll(
|
||||
LSP.layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(RuntimeFlags.layer({ disableLspDownload: true }))),
|
||||
@@ -92,6 +94,35 @@ describe("lsp.spawn", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.live("publishes lsp.updated after custom LSP initialization", () =>
|
||||
provideTmpdirInstance(
|
||||
(dir) =>
|
||||
Effect.gen(function* () {
|
||||
const lsp = yield* LSP.Service
|
||||
const updated = yield* Deferred.make<void>()
|
||||
const unsubscribe = Bus.subscribe(LSP.Event.Updated, () =>
|
||||
Effect.runSync(Deferred.succeed(updated, undefined)),
|
||||
)
|
||||
yield* Effect.addFinalizer(() => Effect.sync(unsubscribe))
|
||||
|
||||
const file = path.join(dir, "sample.repro")
|
||||
yield* Effect.promise(() => Bun.write(file, "sample\n"))
|
||||
yield* lsp.touchFile(file)
|
||||
yield* awaitWithTimeout(Deferred.await(updated), "lsp.updated event was not published")
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
lsp: {
|
||||
fake: {
|
||||
command: [process.execPath, fakeServerPath],
|
||||
extensions: [".repro"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
it.live("would spawn builtin LSP for files inside instance when config object is provided", () =>
|
||||
provideTmpdirInstance(
|
||||
(dir) =>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/plugin",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -22,9 +22,9 @@
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentui/core": ">=0.2.11",
|
||||
"@opentui/keymap": ">=0.2.11",
|
||||
"@opentui/solid": ">=0.2.11"
|
||||
"@opentui/core": ">=0.2.13",
|
||||
"@opentui/keymap": ">=0.2.13",
|
||||
"@opentui/solid": ">=0.2.13"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@opentui/core": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/sdk",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -5,6 +5,12 @@ export type ClientOptions = {
|
||||
}
|
||||
|
||||
export type Event =
|
||||
| EventTuiPromptAppend
|
||||
| EventTuiCommandExecute
|
||||
| EventTuiToastShow1
|
||||
| EventTuiSessionSelect
|
||||
| EventServerConnected
|
||||
| EventGlobalDisposed
|
||||
| EventServerInstanceDisposed
|
||||
| EventFileEdited
|
||||
| EventFileWatcherUpdated
|
||||
@@ -21,10 +27,6 @@ export type Event =
|
||||
| EventTodoUpdated
|
||||
| EventSessionStatus
|
||||
| EventSessionIdle
|
||||
| EventTuiPromptAppend
|
||||
| EventTuiCommandExecute
|
||||
| EventTuiToastShow1
|
||||
| EventTuiSessionSelect
|
||||
| EventMcpToolsChanged
|
||||
| EventMcpBrowserOpenFailed
|
||||
| EventCommandExecuted
|
||||
@@ -75,8 +77,6 @@ export type Event =
|
||||
| EventSessionNextCompactionStarted
|
||||
| EventSessionNextCompactionDelta
|
||||
| EventSessionNextCompactionEnded
|
||||
| EventServerConnected
|
||||
| EventGlobalDisposed
|
||||
| EventCatalogModelUpdated
|
||||
|
||||
export type OAuth = {
|
||||
@@ -104,6 +104,61 @@ export type WellKnownAuth = {
|
||||
|
||||
export type Auth = OAuth | ApiAuth | WellKnownAuth
|
||||
|
||||
export type EventTuiPromptAppend = {
|
||||
id: string
|
||||
type: "tui.prompt.append"
|
||||
properties: {
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventTuiCommandExecute = {
|
||||
id: string
|
||||
type: "tui.command.execute"
|
||||
properties: {
|
||||
command:
|
||||
| "session.list"
|
||||
| "session.new"
|
||||
| "session.share"
|
||||
| "session.interrupt"
|
||||
| "session.compact"
|
||||
| "session.page.up"
|
||||
| "session.page.down"
|
||||
| "session.line.up"
|
||||
| "session.line.down"
|
||||
| "session.half.page.up"
|
||||
| "session.half.page.down"
|
||||
| "session.first"
|
||||
| "session.last"
|
||||
| "prompt.clear"
|
||||
| "prompt.submit"
|
||||
| "agent.cycle"
|
||||
| string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventTuiToastShow = {
|
||||
id: string
|
||||
type: "tui.toast.show"
|
||||
properties: {
|
||||
title?: string
|
||||
message: string
|
||||
variant: "info" | "success" | "warning" | "error"
|
||||
duration?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type EventTuiSessionSelect = {
|
||||
id: string
|
||||
type: "tui.session.select"
|
||||
properties: {
|
||||
/**
|
||||
* Session ID to navigate to
|
||||
*/
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type PermissionRequest = {
|
||||
id: string
|
||||
sessionID: string
|
||||
@@ -281,61 +336,6 @@ export type SessionStatus =
|
||||
type: "busy"
|
||||
}
|
||||
|
||||
export type EventTuiPromptAppend = {
|
||||
id: string
|
||||
type: "tui.prompt.append"
|
||||
properties: {
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventTuiCommandExecute = {
|
||||
id: string
|
||||
type: "tui.command.execute"
|
||||
properties: {
|
||||
command:
|
||||
| "session.list"
|
||||
| "session.new"
|
||||
| "session.share"
|
||||
| "session.interrupt"
|
||||
| "session.compact"
|
||||
| "session.page.up"
|
||||
| "session.page.down"
|
||||
| "session.line.up"
|
||||
| "session.line.down"
|
||||
| "session.half.page.up"
|
||||
| "session.half.page.down"
|
||||
| "session.first"
|
||||
| "session.last"
|
||||
| "prompt.clear"
|
||||
| "prompt.submit"
|
||||
| "agent.cycle"
|
||||
| string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventTuiToastShow = {
|
||||
id: string
|
||||
type: "tui.toast.show"
|
||||
properties: {
|
||||
title?: string
|
||||
message: string
|
||||
variant: "info" | "success" | "warning" | "error"
|
||||
duration?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type EventTuiSessionSelect = {
|
||||
id: string
|
||||
type: "tui.session.select"
|
||||
properties: {
|
||||
/**
|
||||
* Session ID to navigate to
|
||||
*/
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
id: string
|
||||
worktree: string
|
||||
@@ -790,6 +790,12 @@ export type GlobalEvent = {
|
||||
project?: string
|
||||
workspace?: string
|
||||
payload:
|
||||
| EventTuiPromptAppend
|
||||
| EventTuiCommandExecute
|
||||
| EventTuiToastShow
|
||||
| EventTuiSessionSelect
|
||||
| EventServerConnected
|
||||
| EventGlobalDisposed
|
||||
| EventServerInstanceDisposed
|
||||
| EventFileEdited
|
||||
| EventFileWatcherUpdated
|
||||
@@ -806,10 +812,6 @@ export type GlobalEvent = {
|
||||
| EventTodoUpdated
|
||||
| EventSessionStatus
|
||||
| EventSessionIdle
|
||||
| EventTuiPromptAppend
|
||||
| EventTuiCommandExecute
|
||||
| EventTuiToastShow
|
||||
| EventTuiSessionSelect
|
||||
| EventMcpToolsChanged
|
||||
| EventMcpBrowserOpenFailed
|
||||
| EventCommandExecuted
|
||||
@@ -860,8 +862,6 @@ export type GlobalEvent = {
|
||||
| EventSessionNextCompactionStarted
|
||||
| EventSessionNextCompactionDelta
|
||||
| EventSessionNextCompactionEnded
|
||||
| EventServerConnected
|
||||
| EventGlobalDisposed
|
||||
| EventCatalogModelUpdated
|
||||
| SyncEventMessageUpdated
|
||||
| SyncEventMessageRemoved
|
||||
@@ -2403,6 +2403,22 @@ export type SyncEventSessionNextCompactionEnded = {
|
||||
}
|
||||
}
|
||||
|
||||
export type EventServerConnected = {
|
||||
id: string
|
||||
type: "server.connected"
|
||||
properties: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type EventGlobalDisposed = {
|
||||
id: string
|
||||
type: "global.disposed"
|
||||
properties: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type EventServerInstanceDisposed = {
|
||||
id: string
|
||||
type: "server.instance.disposed"
|
||||
@@ -3129,22 +3145,6 @@ export type EventSessionNextCompactionEnded = {
|
||||
}
|
||||
}
|
||||
|
||||
export type EventServerConnected = {
|
||||
id: string
|
||||
type: "server.connected"
|
||||
properties: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type EventGlobalDisposed = {
|
||||
id: string
|
||||
type: "global.disposed"
|
||||
properties: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type ModelV2Info = {
|
||||
id: string
|
||||
apiID: string
|
||||
|
||||
+206
-206
@@ -9043,6 +9043,24 @@
|
||||
"schemas": {
|
||||
"Event": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.prompt.append"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.command.execute"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventTuiToastShow1"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.session.select"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventServerConnected"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventGlobalDisposed"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventServerInstanceDisposed"
|
||||
},
|
||||
@@ -9091,18 +9109,6 @@
|
||||
{
|
||||
"$ref": "#/components/schemas/EventSessionIdle"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.prompt.append"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.command.execute"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventTuiToastShow1"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.session.select"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventMcpToolsChanged"
|
||||
},
|
||||
@@ -9253,12 +9259,6 @@
|
||||
{
|
||||
"$ref": "#/components/schemas/EventSessionNextCompactionEnded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventServerConnected"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventGlobalDisposed"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventCatalogModelUpdated"
|
||||
},
|
||||
@@ -9419,6 +9419,140 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Event.tui.prompt.append": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["tui.prompt.append"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["text"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Event.tui.command.execute": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["tui.command.execute"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"session.list",
|
||||
"session.new",
|
||||
"session.share",
|
||||
"session.interrupt",
|
||||
"session.compact",
|
||||
"session.page.up",
|
||||
"session.page.down",
|
||||
"session.line.up",
|
||||
"session.line.down",
|
||||
"session.half.page.up",
|
||||
"session.half.page.down",
|
||||
"session.first",
|
||||
"session.last",
|
||||
"prompt.clear",
|
||||
"prompt.submit",
|
||||
"agent.cycle"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["command"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Event.tui.toast.show": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["tui.toast.show"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"variant": {
|
||||
"type": "string",
|
||||
"enum": ["info", "success", "warning", "error"]
|
||||
},
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"exclusiveMinimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["message", "variant"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Event.tui.session.select": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["tui.session.select"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionID": {
|
||||
"type": "string",
|
||||
"pattern": "^ses",
|
||||
"description": "Session ID to navigate to"
|
||||
}
|
||||
},
|
||||
"required": ["sessionID"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"PermissionRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9878,140 +10012,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Event.tui.prompt.append": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["tui.prompt.append"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["text"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Event.tui.command.execute": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["tui.command.execute"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"session.list",
|
||||
"session.new",
|
||||
"session.share",
|
||||
"session.interrupt",
|
||||
"session.compact",
|
||||
"session.page.up",
|
||||
"session.page.down",
|
||||
"session.line.up",
|
||||
"session.line.down",
|
||||
"session.half.page.up",
|
||||
"session.half.page.down",
|
||||
"session.first",
|
||||
"session.last",
|
||||
"prompt.clear",
|
||||
"prompt.submit",
|
||||
"agent.cycle"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["command"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Event.tui.toast.show": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["tui.toast.show"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"variant": {
|
||||
"type": "string",
|
||||
"enum": ["info", "success", "warning", "error"]
|
||||
},
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"exclusiveMinimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["message", "variant"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Event.tui.session.select": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["tui.session.select"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sessionID": {
|
||||
"type": "string",
|
||||
"pattern": "^ses",
|
||||
"description": "Session ID to navigate to"
|
||||
}
|
||||
},
|
||||
"required": ["sessionID"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Project": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11423,6 +11423,24 @@
|
||||
},
|
||||
"payload": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.prompt.append"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.command.execute"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.toast.show"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.session.select"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventServerConnected"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventGlobalDisposed"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventServerInstanceDisposed"
|
||||
},
|
||||
@@ -11471,18 +11489,6 @@
|
||||
{
|
||||
"$ref": "#/components/schemas/EventSessionIdle"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.prompt.append"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.command.execute"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.toast.show"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Event.tui.session.select"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventMcpToolsChanged"
|
||||
},
|
||||
@@ -11633,12 +11639,6 @@
|
||||
{
|
||||
"$ref": "#/components/schemas/EventSessionNextCompactionEnded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventServerConnected"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventGlobalDisposed"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EventCatalogModelUpdated"
|
||||
},
|
||||
@@ -16400,6 +16400,42 @@
|
||||
"required": ["type", "name", "id", "seq", "aggregateID", "data"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"EventServerConnected": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["server.connected"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"EventGlobalDisposed": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["global.disposed"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"EventServerInstanceDisposed": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -18603,42 +18639,6 @@
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"EventServerConnected": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["server.connected"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"EventGlobalDisposed": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["global.disposed"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"required": ["id", "type", "properties"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"ModelV2Info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -318,7 +318,7 @@ const TOOL_SAMPLES = {
|
||||
tool: "bash",
|
||||
input: { command: "bun test --filter session", description: "Run session tests" },
|
||||
output:
|
||||
"bun test v1.3.13\n\n✓ session-turn.test.tsx (3 tests) 45ms\n✓ message-part.test.tsx (7 tests) 120ms\n\nTest Suites: 2 passed, 2 total\nTests: 10 passed, 10 total\nTime: 0.89s",
|
||||
"bun test v1.3.14\n\n✓ session-turn.test.tsx (3 tests) 45ms\n✓ message-part.test.tsx (7 tests) 120ms\n\nTest Suites: 2 passed, 2 total\nTests: 10 passed, 10 total\nTime: 0.89s",
|
||||
title: "Run session tests",
|
||||
metadata: { command: "bun test --filter session" },
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@opencode-ai/web",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"version": "1.15.3",
|
||||
"version": "1.15.4",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev:remote": "VITE_API_URL=https://api.opencode.ai astro dev",
|
||||
|
||||
@@ -145,6 +145,7 @@ OpenCode has a list of keybinds that you can customize through `tui.json`.
|
||||
"dialog.select.home": "home",
|
||||
"dialog.select.end": "end",
|
||||
"dialog.select.submit": "return",
|
||||
"dialog.prompt.submit": "return",
|
||||
"dialog.mcp.toggle": "space",
|
||||
"prompt.autocomplete.prev": "up,ctrl+p",
|
||||
"prompt.autocomplete.next": "down,ctrl+n",
|
||||
|
||||
Reference in New Issue
Block a user