From 3a47b0ed9095582b4b3f2d869315f7a6edcc44e2 Mon Sep 17 00:00:00 2001 From: Ryan Vogel Date: Mon, 6 Apr 2026 14:31:03 +0000 Subject: [PATCH] fix(tui): remove feature flag, fix stale suggestion, limit to 110 chars - Remove OPENCODE_EXPERIMENTAL_NEXT_PROMPT gate so suggest always runs - Use reconcile() in sync store to clear stale suggestion on status change - Limit suggestion to 110 characters and instruct model to be concise - Remove temporary debug sidebar panel and plumbing --- .../opencode/src/cli/cmd/tui/context/sync.tsx | 15 +--- .../tui/feature-plugins/sidebar/suggest.tsx | 71 ------------------- .../opencode/src/cli/cmd/tui/plugin/api.tsx | 3 - .../src/cli/cmd/tui/plugin/internal.ts | 2 - packages/opencode/src/session/prompt.ts | 41 +++-------- .../src/session/prompt/suggest-next.txt | 2 +- packages/opencode/src/session/status.ts | 11 --- 7 files changed, 12 insertions(+), 133 deletions(-) delete mode 100644 packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/suggest.tsx diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index b6e36b1953..a42b4c20ba 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -54,9 +54,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ session_status: { [sessionID: string]: SessionStatus } - suggest_debug: { - [sessionID: string]: { state: string; detail?: string; time: number } - } session_diff: { [sessionID: string]: Snapshot.FileDiff[] } @@ -98,7 +95,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ provider_default: {}, session: [], session_status: {}, - suggest_debug: {}, session_diff: {}, todo: {}, message: {}, @@ -237,16 +233,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ } case "session.status": { - setStore("session_status", event.properties.sessionID, event.properties.status) - break - } - - case "session.suggest_debug": { - setStore("suggest_debug", event.properties.sessionID, { - state: event.properties.state, - detail: event.properties.detail, - time: Date.now(), - }) + setStore("session_status", event.properties.sessionID, reconcile(event.properties.status)) break } diff --git a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/suggest.tsx b/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/suggest.tsx deleted file mode 100644 index 7a6f543d21..0000000000 --- a/packages/opencode/src/cli/cmd/tui/feature-plugins/sidebar/suggest.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@opencode-ai/plugin/tui" -import { createMemo, createSignal, onCleanup } from "solid-js" - -const id = "internal:sidebar-suggest" - -function View(props: { api: TuiPluginApi; session_id: string }) { - const theme = () => props.api.theme.current - const debug = createMemo( - () => - (props.api.state.session as any).suggestDebug(props.session_id) as - | { state: string; detail?: string; time: number } - | undefined, - ) - - const [now, setNow] = createSignal(Date.now()) - const timer = setInterval(() => setNow(Date.now()), 1000) - onCleanup(() => clearInterval(timer)) - - const age = createMemo(() => { - const d = debug() - if (!d) return "" - const ms = now() - d.time - if (ms < 1000) return "just now" - return `${Math.floor(ms / 1000)}s ago` - }) - - const color = createMemo(() => { - const state = debug()?.state - if (state === "generating") return theme().brand - if (state === "done") return theme().textSuccess ?? "green" - if (state === "error") return theme().textDanger ?? "red" - if (state === "refused") return theme().textWarning ?? "yellow" - return theme().textMuted - }) - - return ( - - - Suggest - - {debug() ? ( - <> - - {debug()!.state} {age()} - - {debug()!.detail ? {debug()!.detail!.slice(0, 38)} : null} - - ) : ( - waiting - )} - - ) -} - -const tui: TuiPlugin = async (api) => { - api.slots.register({ - order: 50, - slots: { - sidebar_content(_ctx, props) { - return - }, - }, - }) -} - -const plugin: TuiPluginModule & { id: string } = { - id, - tui, -} - -export default plugin diff --git a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx index 53e690f3ec..529c50cfa3 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/api.tsx +++ b/packages/opencode/src/cli/cmd/tui/plugin/api.tsx @@ -173,9 +173,6 @@ function stateApi(sync: ReturnType): TuiPluginApi["state"] { status(sessionID) { return sync.data.session_status[sessionID] }, - suggestDebug(sessionID) { - return sync.data.suggest_debug[sessionID] - }, permission(sessionID) { return sync.data.permission[sessionID] ?? [] }, diff --git a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts index 5ffc6ffaa5..856ee0ebb1 100644 --- a/packages/opencode/src/cli/cmd/tui/plugin/internal.ts +++ b/packages/opencode/src/cli/cmd/tui/plugin/internal.ts @@ -6,7 +6,6 @@ import SidebarLsp from "../feature-plugins/sidebar/lsp" import SidebarTodo from "../feature-plugins/sidebar/todo" import SidebarFiles from "../feature-plugins/sidebar/files" import SidebarFooter from "../feature-plugins/sidebar/footer" -import SidebarSuggest from "../feature-plugins/sidebar/suggest" import PluginManager from "../feature-plugins/system/plugins" import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui" @@ -18,7 +17,6 @@ export type InternalTuiPlugin = TuiPluginModule & { export const INTERNAL_TUI_PLUGINS: InternalTuiPlugin[] = [ HomeFooter, HomeTips, - SidebarSuggest, SidebarContext, SidebarMcp, SidebarLsp, diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 68a7ca4b66..87e7be643f 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -250,9 +250,6 @@ export namespace SessionPrompt { ) }) - const suggestDebug = (sessionID: SessionID, state: SessionStatus.SuggestState, detail?: string) => - bus.publish(SessionStatus.Event.SuggestDebug, { sessionID, state, detail }) - const suggest = Effect.fn("SessionPrompt.suggest")(function* (input: { session: Session.Info sessionID: SessionID @@ -275,8 +272,6 @@ export namespace SessionPrompt { const ag = yield* agents.get(message.agent ?? "code") if (!ag) return - yield* suggestDebug(input.sessionID, "generating") - // Full message history so the cached KV from the main conversation is reused const msgs = yield* MessageV2.filterCompactedEffect(input.sessionID) const real = (item: MessageV2.WithParts) => @@ -292,7 +287,7 @@ export namespace SessionPrompt { const modelMsgs = yield* Effect.promise(() => MessageV2.toModelMessages(msgs, model)) const system = [...env, ...(skills ? [skills] : []), ...instructions] - const exit = yield* Effect.promise(async (signal) => { + const text = yield* Effect.promise(async (signal) => { const result = await LLM.stream({ agent: ag, user, @@ -308,14 +303,7 @@ export namespace SessionPrompt { messages: [...modelMsgs, { role: "user" as const, content: PROMPT_SUGGEST_NEXT }], }) return result.text - }).pipe(Effect.exit) - - if (Exit.isFailure(exit)) { - const err = Cause.squash(exit.cause) - yield* suggestDebug(input.sessionID, "error", err instanceof Error ? err.message : String(err)) - return - } - const text = exit.value + }) const line = text .replace(/[\s\S]*?<\/think>\s*/g, "") @@ -323,24 +311,17 @@ export namespace SessionPrompt { .map((item) => item.trim()) .find((item) => item.length > 0) ?.replace(/^["'`]+|["'`]+$/g, "") - if (!line) { - yield* suggestDebug(input.sessionID, "refused", "empty response") - return - } + if (!line) return const tag = line .toUpperCase() .replace(/[\s-]+/g, "_") .replace(/[^A-Z_]/g, "") - if (tag === "NO_SUGGESTION") { - yield* suggestDebug(input.sessionID, "refused", "NO_SUGGESTION") - return - } + if (tag === "NO_SUGGESTION") return - const suggestion = line.length > 240 ? line.slice(0, 237) + "..." : line + const suggestion = line.length > 110 ? line.slice(0, 107) + "..." : line if ((yield* status.get(input.sessionID)).type !== "idle") return yield* status.suggest(input.sessionID, suggestion) - yield* suggestDebug(input.sessionID, "done", suggestion) }) const insertReminders = Effect.fn("SessionPrompt.insertReminders")(function* (input: { @@ -1414,13 +1395,11 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (input.noReply === true) return message const result = yield* loop({ sessionID: input.sessionID }) - if (Flag.OPENCODE_EXPERIMENTAL_NEXT_PROMPT) { - yield* suggest({ - session, - sessionID: input.sessionID, - message: result, - }).pipe(Effect.ignore, Effect.forkIn(scope)) - } + yield* suggest({ + session, + sessionID: input.sessionID, + message: result, + }).pipe(Effect.ignore, Effect.forkIn(scope)) return result }, ) diff --git a/packages/opencode/src/session/prompt/suggest-next.txt b/packages/opencode/src/session/prompt/suggest-next.txt index ebcb3e5d9f..dbb2756dc1 100644 --- a/packages/opencode/src/session/prompt/suggest-next.txt +++ b/packages/opencode/src/session/prompt/suggest-next.txt @@ -4,7 +4,7 @@ Goal: - Suggest a useful next step that keeps momentum. Rules: -- Output exactly one line. +- Output exactly one line, 110 characters max. Be concise. - Write as the user speaking to the assistant (for example: "Can you...", "Help me...", "Let's..."). - Match the user's tone and language; keep it natural and human. - Prefer a concrete action over a broad question. diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts index bc31792d2e..6a85c77127 100644 --- a/packages/opencode/src/session/status.ts +++ b/packages/opencode/src/session/status.ts @@ -28,9 +28,6 @@ export namespace SessionStatus { }) export type Info = z.infer - export const SuggestState = z.enum(["generating", "done", "refused", "error"]) - export type SuggestState = z.infer - export const Event = { Status: BusEvent.define( "session.status", @@ -39,14 +36,6 @@ export namespace SessionStatus { status: Info, }), ), - SuggestDebug: BusEvent.define( - "session.suggest_debug", - z.object({ - sessionID: SessionID.zod, - state: SuggestState, - detail: z.string().optional(), - }), - ), // deprecated Idle: BusEvent.define( "session.idle",