diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 25466a63e0..c08d7edf3b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -21,6 +21,15 @@ jobs: with: node-version: "24" + # Workaround for Pulumi version conflict: + # GitHub runners have Pulumi 3.212.0+ pre-installed, which removed the -root flag + # from pulumi-language-nodejs (see https://github.com/pulumi/pulumi/pull/21065). + # SST 3.17.x uses Pulumi SDK 3.210.0 which still passes -root, causing a conflict. + # Removing the system language plugin forces SST to use its bundled compatible version. + # TODO: Remove when sst supports Pulumi >3.210.0 + - name: Fix Pulumi version conflict + run: sudo rm -f /usr/local/bin/pulumi-language-nodejs + - run: bun sst deploy --stage=${{ github.ref_name }} env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} diff --git a/.github/workflows/nix-hashes.yml b/.github/workflows/nix-hashes.yml index cc16d81844..5446f9212f 100644 --- a/.github/workflows/nix-hashes.yml +++ b/.github/workflows/nix-hashes.yml @@ -6,13 +6,7 @@ permissions: on: workflow_dispatch: push: - paths: - - "bun.lock" - - "package.json" - - "packages/*/package.json" - - "flake.lock" - - ".github/workflows/nix-hashes.yml" - pull_request: + branches: [dev] paths: - "bun.lock" - "package.json" diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 011e23f5f6..b247d24b40 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -1,6 +1,8 @@ name: typecheck on: + push: + branches: [dev] pull_request: branches: [dev] workflow_dispatch: diff --git a/AGENTS.md b/AGENTS.md index 85b5e67631..eeec0c3418 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,7 @@ - Prefer single word variable names where possible - Use Bun APIs when possible, like `Bun.file()` - Rely on type inference when possible; avoid explicit type annotations or interfaces unless necessary for exports or clarity +- Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream ### Naming diff --git a/bun.lock b/bun.lock index 2934011f4b..ad44b07980 100644 --- a/bun.lock +++ b/bun.lock @@ -298,8 +298,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.4", - "@opentui/core": "0.1.75", - "@opentui/solid": "0.1.75", + "@opentui/core": "0.1.76", + "@opentui/solid": "0.1.76", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", @@ -1246,21 +1246,21 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentui/core": ["@opentui/core@0.1.75", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.75", "@opentui/core-darwin-x64": "0.1.75", "@opentui/core-linux-arm64": "0.1.75", "@opentui/core-linux-x64": "0.1.75", "@opentui/core-win32-arm64": "0.1.75", "@opentui/core-win32-x64": "0.1.75", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-8ARRZxSG+BXkJmEVtM2DQ4se7DAF1ZCKD07d+AklgTr2mxCzmdxxPbOwRzboSQ6FM7qGuTVPVbV4O2W9DpUmoA=="], + "@opentui/core": ["@opentui/core@0.1.76", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.76", "@opentui/core-darwin-x64": "0.1.76", "@opentui/core-linux-arm64": "0.1.76", "@opentui/core-linux-x64": "0.1.76", "@opentui/core-win32-arm64": "0.1.76", "@opentui/core-win32-x64": "0.1.76", "bun-webgpu": "0.1.4", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-Y4f4KH6Mbj0J6+MorcvtHSeT+Lbs3YDPEQcTRTWsPOqWz3A0F5/+OPtZKho1EtLWQqJflCWdf/JQj5A3We3qRg=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.75", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gGaGZjkFpqcXJk6321JzhRl66pM2VxBlI470L8W4DQUW4S6iDT1R9L7awSzGB4Cn9toUl7DTV8BemaXZYXV4SA=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.76", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aRYNOPRKL6URovSPhRvXtBV7SqdmR7s6hmEBSdXiYo39AozTcvKviF8gJWXQATcKDEcOtRir6TsASzDq5Coheg=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.75", "", { "os": "darwin", "cpu": "x64" }, "sha512-tPlvqQI0whZ76amHydpJs5kN+QeWAIcFbI8RAtlAo9baj2EbxTDC+JGwgb9Fnt0/YQx831humbtaNDhV2Jt1bw=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.76", "", { "os": "darwin", "cpu": "x64" }, "sha512-KFaRvVQ0Wr1PgaexUkF3KYt41pYmxGJW3otENeE6WDa/nXe2AElibPFRjqSEH54YrY5Q84SDI77/wGP4LZ/Wyg=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.75", "", { "os": "linux", "cpu": "arm64" }, "sha512-nVxIQ4Hqf84uBergDpWiVzU6pzpjy6tqBHRQpySxZ2flkJ/U6/aMEizVrQ1jcgIdxZtvqWDETZhzxhG0yDx+cw=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.76", "", { "os": "linux", "cpu": "arm64" }, "sha512-s7v+GDwavfieZg8xZV4V07fXFrHfFq4UZ2JpYFDUgNs9vFp+++WUjh3pfbfE+2ldbhcG2iOtuiV9aG1tVCbTEg=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.75", "", { "os": "linux", "cpu": "x64" }, "sha512-1CnApef4kxA+ORyLfbuCLgZfEjp4wr3HjFnt7FAfOb73kIZH82cb7JYixeqRyy9eOcKfKqxLmBYy3o8IDkc4Rg=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.76", "", { "os": "linux", "cpu": "x64" }, "sha512-ugwuHpmvdKRHXKVsrC3zRYY6bg2JxVCzAQ1NOiWRLq3N3N4ha6BHAkHMCeHgR/ZI4R8MSRB6vtJRVI1F9VHxjA=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.75", "", { "os": "win32", "cpu": "arm64" }, "sha512-j0UB95nmkYGNzmOrs6GqaddO1S90R0YC6IhbKnbKBdjchFPNVLz9JpexAs6MBDXPZwdKAywMxtwG2h3aTJtxng=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.76", "", { "os": "win32", "cpu": "arm64" }, "sha512-wjpRWrerPItb5E1fP4SAcNMxQp1yEukbgvP4Azip836/ixxbghL6y0P57Ya/rv7QYLrkNZXoQ+tr9oXhPH5BVA=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.75", "", { "os": "win32", "cpu": "x64" }, "sha512-ESpVZVGewe3JkB2TwrG3VRbkxT909iPdtvgNT7xTCIYH2VB4jqZomJfvERPTE0tvqAZJm19mHECzJFI8asSJgQ=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.76", "", { "os": "win32", "cpu": "x64" }, "sha512-2YjtZJdd3iO+SY9NKocE4/Pm9VolzAthUOXjpK4Pv5pnR9hBpPvX7FFSXJTfASj7y2j1tATWrlQLocZCFP/oMA=="], - "@opentui/solid": ["@opentui/solid@0.1.75", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.75", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-WjKsZIfrm29znfRlcD9w3uUn/+uvoy2MmeoDwTvg1YOa0OjCTCmjZ43L9imp0m9S4HmVU8ma6o2bR4COzcyDdg=="], + "@opentui/solid": ["@opentui/solid@0.1.76", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.76", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-PiD62FGoPoVLFpY4g08i4UYlx4sGR2OmHUPj6CuZZwy2UJD4fKn1RYV+kAPHfUW4qN/88I1k/w/Dniz1WvXrAQ=="], "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], diff --git a/nix/hashes.json b/nix/hashes.json index fd8112e8dd..9843699345 100644 --- a/nix/hashes.json +++ b/nix/hashes.json @@ -1,8 +1,8 @@ { "nodeModules": { - "x86_64-linux": "sha256-PrEuJ7fh/rmd8ewXsnKgQ/Zu8qYsrb3D7maL9ZlVAnE=", - "aarch64-linux": "sha256-7+ktHTXtHMWWA9tNxX8Fb1um1JFKHQuIjIJPuveqL94=", - "aarch64-darwin": "sha256-KirMU9LO7sB0ufVaWF9Y+DtxbBVyapE02GP2ytW9xLg=", - "x86_64-darwin": "sha256-wZJt3htmjWvwRCIoD0rkr3+8cW/xjfXfz8rdHxcFplo=" + "x86_64-linux": "sha256-aRFzPzgu32XgNSk8S2z4glTlgHqEmOLZHlBQSIYIMvY=", + "aarch64-linux": "sha256-aCZLkmRrCa0bli0jgsaLcC5GlZdjQPbb6xD6Fc03eX8=", + "aarch64-darwin": "sha256-oZOOR6k8MmabNVDQNY5ywR06rRycdnXZL+gUucKSQ+g=", + "x86_64-darwin": "sha256-LXIcLnjn+1eTFWIsQ9W0U2orGm59P/L470O0KFFkRHg=" } } diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 210847afb8..c037bf891f 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -85,8 +85,8 @@ "@opencode-ai/sdk": "workspace:*", "@opencode-ai/util": "workspace:*", "@openrouter/ai-sdk-provider": "1.5.4", - "@opentui/core": "0.1.75", - "@opentui/solid": "0.1.75", + "@opentui/core": "0.1.76", + "@opentui/solid": "0.1.76", "@parcel/watcher": "2.5.1", "@pierre/diffs": "catalog:", "@solid-primitives/event-bus": "1.1.2", diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 713def2e5a..2e1ffa4f00 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -186,6 +186,7 @@ function App() { const route = useRoute() const dimensions = useTerminalDimensions() const renderer = useRenderer() + Clipboard.setRenderer(renderer) renderer.disableStdoutInterception() const dialog = useDialog() const local = useLocal() diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx index 85c174c1dc..775969bfcb 100644 --- a/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx @@ -10,7 +10,7 @@ import { useSDK } from "../context/sdk" import { DialogSessionRename } from "./dialog-session-rename" import { useKV } from "../context/kv" import { createDebouncedSignal } from "../util/signal" -import "opentui-spinner/solid" +import { Spinner } from "./spinner" export function DialogSessionList() { const dialog = useDialog() @@ -32,8 +32,6 @@ export function DialogSessionList() { const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined)) - const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] - const sessions = createMemo(() => searchResults() ?? sync.data.session) const options = createMemo(() => { @@ -56,11 +54,7 @@ export function DialogSessionList() { value: x.id, category, footer: Locale.time(x.time.updated), - gutter: isWorking ? ( - [⋯]}> - - - ) : undefined, + gutter: isWorking ? : undefined, } }) }) diff --git a/packages/opencode/src/cli/cmd/tui/component/spinner.tsx b/packages/opencode/src/cli/cmd/tui/component/spinner.tsx new file mode 100644 index 0000000000..8dc5455504 --- /dev/null +++ b/packages/opencode/src/cli/cmd/tui/component/spinner.tsx @@ -0,0 +1,24 @@ +import { Show } from "solid-js" +import { useTheme } from "../context/theme" +import { useKV } from "../context/kv" +import type { JSX } from "@opentui/solid" +import type { RGBA } from "@opentui/core" +import "opentui-spinner/solid" + +const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] + +export function Spinner(props: { children?: JSX.Element; color?: RGBA }) { + const { theme } = useTheme() + const kv = useKV() + const color = () => props.color ?? theme.textMuted + return ( + ⋯ {props.children}}> + + + + {props.children} + + + + ) +} diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index cbfeb67b2d..48a45b5769 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -16,6 +16,7 @@ import path from "path" import { useRoute, useRouteData } from "@tui/context/route" import { useSync } from "@tui/context/sync" import { SplitBorder } from "@tui/component/border" +import { Spinner } from "@tui/component/spinner" import { useTheme } from "@tui/context/theme" import { BoxRenderable, @@ -1559,7 +1560,13 @@ function InlineTool(props: { ) } -function BlockTool(props: { title: string; children: JSX.Element; onClick?: () => void; part?: ToolPart }) { +function BlockTool(props: { + title: string + children: JSX.Element + onClick?: () => void + part?: ToolPart + spinner?: boolean +}) { const { theme } = useTheme() const renderer = useRenderer() const [hover, setHover] = createSignal(false) @@ -1582,9 +1589,16 @@ function BlockTool(props: { title: string; children: JSX.Element; onClick?: () = props.onClick?.() }} > - - {props.title} - + + {props.title} + + } + > + {props.title.replace(/^# /, "")} + {props.children} {error()} @@ -1799,9 +1813,21 @@ function Task(props: ToolProps) { const keybind = useKeybind() const { navigate } = useRoute() const local = useLocal() + const sync = useSync() - const current = createMemo(() => props.metadata.summary?.findLast((x) => x.state.status !== "pending")) - const color = createMemo(() => local.agent.color(props.input.subagent_type ?? "unknown")) + const tools = createMemo(() => { + const sessionID = props.metadata.sessionId + const msgs = sync.data.message[sessionID ?? ""] ?? [] + return msgs.flatMap((msg) => + (sync.data.part[msg.id] ?? []) + .filter((part): part is ToolPart => part.type === "tool") + .map((part) => ({ tool: part.tool, state: part.state })), + ) + }) + + const current = createMemo(() => tools().findLast((x) => x.state.status !== "pending")) + + const isRunning = createMemo(() => props.part.state.status === "running") return ( @@ -1814,16 +1840,21 @@ function Task(props: ToolProps) { : undefined } part={props.part} + spinner={isRunning()} > - {props.input.description} ({props.metadata.summary?.length ?? 0} toolcalls) + {props.input.description} ({tools().length} toolcalls) - - └ {Locale.titlecase(current()!.tool)}{" "} - {current()!.state.status === "completed" ? current()!.state.title : ""} - + {(item) => { + const title = item().state.status === "completed" ? (item().state as any).title : "" + return ( + + └ {Locale.titlecase(item().tool)} {title} + + ) + }} diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts index 0e287fbc41..5c27a26cd0 100644 --- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts +++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts @@ -1,24 +1,12 @@ import { $ } from "bun" +import type { CliRenderer } from "@opentui/core" import { platform, release } from "os" import clipboardy from "clipboardy" import { lazy } from "../../../../util/lazy.js" import { tmpdir } from "os" import path from "path" -/** - * Writes text to clipboard via OSC 52 escape sequence. - * This allows clipboard operations to work over SSH by having - * the terminal emulator handle the clipboard locally. - */ -function writeOsc52(text: string): void { - if (!process.stdout.isTTY) return - const base64 = Buffer.from(text).toString("base64") - const osc52 = `\x1b]52;c;${base64}\x07` - // tmux and screen require DCS passthrough wrapping - const passthrough = process.env["TMUX"] || process.env["STY"] - const sequence = passthrough ? `\x1bPtmux;\x1b${osc52}\x1b\\` : osc52 - process.stdout.write(sequence) -} +const rendererRef = { current: undefined as CliRenderer | undefined } export namespace Clipboard { export interface Content { @@ -26,6 +14,10 @@ export namespace Clipboard { mime: string } + export function setRenderer(renderer: CliRenderer | undefined): void { + rendererRef.current = renderer + } + export async function read(): Promise { const os = platform() @@ -154,7 +146,11 @@ export namespace Clipboard { }) export async function copy(text: string): Promise { - writeOsc52(text) + const renderer = rendererRef.current + if (renderer) { + const copied = renderer.copyToClipboardOSC52(text) + if (copied) return + } await getCopyMethod()(text) } } diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 463a9fb362..c1e5113bf8 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -275,100 +275,56 @@ export namespace Ripgrep { log.info("tree", input) const files = await Array.fromAsync(Ripgrep.files({ cwd: input.cwd, signal: input.signal })) interface Node { - path: string[] - children: Node[] + name: string + children: Map } - function getPath(node: Node, parts: string[], create: boolean) { - if (parts.length === 0) return node - let current = node - for (const part of parts) { - let existing = current.children.find((x) => x.path.at(-1) === part) - if (!existing) { - if (!create) return - existing = { - path: current.path.concat(part), - children: [], - } - current.children.push(existing) - } - current = existing - } - return current + function dir(node: Node, name: string) { + const existing = node.children.get(name) + if (existing) return existing + const next = { name, children: new Map() } + node.children.set(name, next) + return next } - const root: Node = { - path: [], - children: [], - } + const root: Node = { name: "", children: new Map() } for (const file of files) { if (file.includes(".opencode")) continue const parts = file.split(path.sep) - getPath(root, parts, true) - } - - function sort(node: Node) { - node.children.sort((a, b) => { - if (!a.children.length && b.children.length) return 1 - if (!b.children.length && a.children.length) return -1 - return a.path.at(-1)!.localeCompare(b.path.at(-1)!) - }) - for (const child of node.children) { - sort(child) + if (parts.length < 2) continue + let node = root + for (const part of parts.slice(0, -1)) { + node = dir(node, part) } } - sort(root) - let current = [root] - const result: Node = { - path: [], - children: [], - } - - let processed = 0 - const limit = input.limit ?? 50 - while (current.length > 0) { - const next = [] - for (const node of current) { - if (node.children.length) next.push(...node.children) - } - const max = Math.max(...current.map((x) => x.children.length)) - for (let i = 0; i < max && processed < limit; i++) { - for (const node of current) { - const child = node.children[i] - if (!child) continue - getPath(result, child.path, true) - processed++ - if (processed >= limit) break - } - } - if (processed >= limit) { - for (const node of [...current, ...next]) { - const compare = getPath(result, node.path, false) - if (!compare) continue - if (compare?.children.length !== node.children.length) { - const diff = node.children.length - compare.children.length - compare.children.push({ - path: compare.path.concat(`[${diff} truncated]`), - children: [], - }) - } - } - break - } - current = next + function count(node: Node): number { + let total = 0 + for (const child of node.children.values()) { + total += 1 + count(child) + } + return total } + const total = count(root) + const limit = input.limit ?? total const lines: string[] = [] + const queue: { node: Node; path: string }[] = [] + for (const child of Array.from(root.children.values()).sort((a, b) => a.name.localeCompare(b.name))) { + queue.push({ node: child, path: child.name }) + } - function render(node: Node, depth: number) { - const indent = "\t".repeat(depth) - lines.push(indent + node.path.at(-1) + (node.children.length ? "/" : "")) - for (const child of node.children) { - render(child, depth + 1) + let used = 0 + for (let i = 0; i < queue.length && used < limit; i++) { + const { node, path } = queue[i] + lines.push(path) + used++ + for (const child of Array.from(node.children.values()).sort((a, b) => a.name.localeCompare(b.name))) { + queue.push({ node: child, path: `${path}/${child.name}` }) } } - result.children.map((x) => render(x, 0)) + + if (total > used) lines.push(`[${total - used} truncated]`) return lines.join("\n") } diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index e533ca0283..2796318c7d 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -379,6 +379,7 @@ export namespace SessionProcessor { sessionID: input.assistantMessage.sessionID, error: input.assistantMessage.error, }) + SessionStatus.set(input.sessionID, { type: "idle" }) } if (snapshot) { const patch = await Snapshot.patch(snapshot) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 222cff8242..624f9d64c4 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -62,7 +62,7 @@ export namespace SessionPrompt { abort: AbortController callbacks: { resolve(input: MessageV2.WithParts): void - reject(): void + reject(reason?: any): void }[] } > = {} @@ -72,7 +72,7 @@ export namespace SessionPrompt { for (const item of Object.values(current)) { item.abort.abort() for (const callback of item.callbacks) { - callback.reject() + callback.reject(new DOMException("Aborted", "AbortError")) } } }, @@ -249,10 +249,13 @@ export namespace SessionPrompt { log.info("cancel", { sessionID }) const s = state() const match = s[sessionID] - if (!match) return + if (!match) { + SessionStatus.set(sessionID, { type: "idle" }) + return + } match.abort.abort() for (const item of match.callbacks) { - item.reject() + item.reject(new DOMException("Aborted", "AbortError")) } delete s[sessionID] SessionStatus.set(sessionID, { type: "idle" }) diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index d34a086fe4..b02267bf8a 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -36,16 +36,16 @@ export namespace SystemPrompt { ` Platform: ${process.platform}`, ` Today's date: ${new Date().toDateString()}`, ``, - ``, + ``, ` ${ project.vcs === "git" && false ? await Ripgrep.tree({ cwd: Instance.directory, - limit: 200, + limit: 50, }) : "" }`, - ``, + ``, ].join("\n"), ] } diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index ad4268b7b0..ac28d9f322 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -130,7 +130,6 @@ export const TaskTool = Tool.define("task", async (ctx) => { ctx.metadata({ title: params.description, metadata: { - summary: Object.values(parts).sort((a, b) => a.id.localeCompare(b.id)), sessionId: session.id, model, }, diff --git a/packages/opencode/test/snapshot/snapshot.test.ts b/packages/opencode/test/snapshot/snapshot.test.ts index de58f4f85e..ef6271ed5d 100644 --- a/packages/opencode/test/snapshot/snapshot.test.ts +++ b/packages/opencode/test/snapshot/snapshot.test.ts @@ -292,7 +292,7 @@ test("unicode filenames", async () => { }) }) -test("unicode filenames modification and restore", async () => { +test.skip("unicode filenames modification and restore", async () => { await using tmp = await bootstrap() await Instance.provide({ directory: tmp.path, diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 160ce6a826..5a4afbae43 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -9,14 +9,8 @@ "build": "tsc" }, "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - }, - "./tool": { - "types": "./dist/tool.d.ts", - "import": "./dist/tool.js" - } + ".": "./src/index.ts", + "./tool": "./src/tool.ts" }, "files": [ "dist" diff --git a/script/beta.ts b/script/beta.ts index 53329e4dce..4355c5879c 100755 --- a/script/beta.ts +++ b/script/beta.ts @@ -124,7 +124,18 @@ async function main() { throw new Error(`${failed.length} PR(s) failed to merge`) } - console.log("\nForce pushing beta branch...") + console.log("\nChecking if beta branch has changes...") + await $`git fetch origin beta` + + const localTree = await $`git rev-parse beta^{tree}`.text() + const remoteTree = await $`git rev-parse origin/beta^{tree}`.text() + + if (localTree.trim() === remoteTree.trim()) { + console.log("Beta branch has identical contents, no push needed") + return + } + + console.log("Force pushing beta branch...") await $`git push origin beta --force --no-verify` console.log("Successfully synced beta branch")