feat(tui): integrate composer picker
This commit is contained in:
parent
fa73546a86
commit
c65a7d50c1
3 changed files with 57 additions and 41 deletions
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# From packages/cli: local V2 TUI
|
# From packages/cli: local V2 TUI
|
||||||
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev --standalone
|
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
|
||||||
|
|
||||||
# Released legacy TUI behavior reference
|
# Released legacy TUI behavior reference
|
||||||
termctrl start opencode-legacy --host opentui --cols 112 --rows 34 -- bunx opencode-ai@latest
|
termctrl start opencode-legacy --host opentui --cols 112 --rows 34 -- bunx opencode-ai@latest
|
||||||
|
|
@ -23,12 +23,12 @@ termctrl save opencode-legacy --format png --out /tmp/opencode/legacy.png
|
||||||
## Interactive debugging
|
## Interactive debugging
|
||||||
|
|
||||||
- This package is the V2 CLI adapter. Run its `dev` script when testing the TUI; do not use the repository-root `bun dev`, which launches the legacy `packages/opencode` CLI.
|
- This package is the V2 CLI adapter. Run its `dev` script when testing the TUI; do not use the repository-root `bun dev`, which launches the legacy `packages/opencode` CLI.
|
||||||
- Run commands from `packages/cli`. Use `bun dev --standalone` for most debugging so the TUI starts with a private V2 server instead of depending on the background service.
|
- Run commands from `packages/cli`. Use `bun dev` for most debugging so the TUI starts with a private V2 server.
|
||||||
- Use `termctrl` for interactive checks instead of starting the TUI as a blocking foreground process. It provides a real PTY, handles OpenTUI's host handshake, and can save reviewable screenshots.
|
- Use `termctrl` for interactive checks instead of starting the TUI as a blocking foreground process. It provides a real PTY, handles OpenTUI's host handshake, and can save reviewable screenshots.
|
||||||
- Use a dedicated session name and do not reuse or kill an unrelated session.
|
- Use a dedicated session name and do not reuse or kill an unrelated session.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev --standalone
|
termctrl start opencode-v2-dev --host opentui --cols 112 --rows 34 -- bun dev
|
||||||
termctrl wait opencode-v2-dev "Ask anything" --timeout 20000
|
termctrl wait opencode-v2-dev "Ask anything" --timeout 20000
|
||||||
termctrl show opencode-v2-dev
|
termctrl show opencode-v2-dev
|
||||||
```
|
```
|
||||||
|
|
@ -56,7 +56,7 @@ termctrl show opencode-v2-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
- Source changes may require restarting the process. Use `termctrl restart opencode-v2-dev` rather than assuming the running TUI reloaded the change.
|
- Source changes may require restarting the process. Use `termctrl restart opencode-v2-dev` rather than assuming the running TUI reloaded the change.
|
||||||
- To exercise background-service behavior, omit `--standalone`. Service lifecycle commands are available through `bun dev service start`, `bun dev service status`, and `bun dev service stop`.
|
- To exercise background-service behavior, use `bun dev service start`, `bun dev service status`, and `bun dev service stop`.
|
||||||
- Always clean up the Terminal Control session when the check is complete:
|
- Always clean up the Terminal Control session when the check is complete:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -85,7 +85,7 @@ bun dev api <operationId> --param key=value
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
termctrl start opencode-v2-debug --host opentui --cols 112 --rows 34 -- \
|
termctrl start opencode-v2-debug --host opentui --cols 112 --rows 34 -- \
|
||||||
bun run --inspect=ws://localhost:6499/ src/index.ts --standalone
|
bun run --inspect=ws://localhost:6499/ src/index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
- Use `--inspect-wait` or `--inspect-brk` when execution must pause until the debugger attaches.
|
- Use `--inspect-wait` or `--inspect-brk` when execution must pause until the debugger attaches.
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ export const Definitions = {
|
||||||
session_toggle_timestamps: keybind("none", "Toggle message timestamps"),
|
session_toggle_timestamps: keybind("none", "Toggle message timestamps"),
|
||||||
session_toggle_generic_tool_output: keybind("none", "Toggle generic tool output"),
|
session_toggle_generic_tool_output: keybind("none", "Toggle generic tool output"),
|
||||||
session_queued_prompts: keybind("<leader>q", "Manage queued prompts"),
|
session_queued_prompts: keybind("<leader>q", "Manage queued prompts"),
|
||||||
session_child_first: keybind("<leader>down", "Go to first child session"),
|
session_child_first: keybind("<leader>down", "Toggle subagent picker"),
|
||||||
session_child_cycle: keybind("right", "Go to next child session"),
|
session_child_cycle: keybind("right", "Go to next child session"),
|
||||||
session_child_cycle_reverse: keybind("left", "Go to previous child session"),
|
session_child_cycle_reverse: keybind("left", "Go to previous child session"),
|
||||||
session_parent: keybind("up", "Go to parent session"),
|
session_parent: keybind("up", "Go to parent session"),
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
import { mkdir, writeFile } from "node:fs/promises"
|
import { mkdir, writeFile } from "node:fs/promises"
|
||||||
import { useRoute, useRouteData } from "../../context/route"
|
import { useRoute, useRouteData } from "../../context/route"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { useProject } from "../../context/project"
|
import { useProject } from "../../context/project"
|
||||||
import { useData } from "../../context/data"
|
import { useData } from "../../context/data"
|
||||||
import { SplitBorder } from "../../ui/border"
|
import { SplitBorder } from "../../ui/border"
|
||||||
|
|
@ -46,7 +47,7 @@ import { DialogSessionRename } from "../../component/dialog-session-rename"
|
||||||
import { TodoItem } from "../../component/todo-item"
|
import { TodoItem } from "../../component/todo-item"
|
||||||
import { DialogMessage } from "./dialog-message"
|
import { DialogMessage } from "./dialog-message"
|
||||||
import { Sidebar } from "./sidebar"
|
import { Sidebar } from "./sidebar"
|
||||||
import { SubagentFooter } from "./subagent-footer.tsx"
|
import { Composer } from "./composer"
|
||||||
import { filetype } from "../../util/filetype"
|
import { filetype } from "../../util/filetype"
|
||||||
import parsers from "../../parsers-config"
|
import parsers from "../../parsers-config"
|
||||||
import { errorMessage } from "../../util/error"
|
import { errorMessage } from "../../util/error"
|
||||||
|
|
@ -177,7 +178,10 @@ export function Session() {
|
||||||
if (session()?.parentID) return []
|
if (session()?.parentID) return []
|
||||||
return data.session.question.list(route.sessionID) ?? []
|
return data.session.question.list(route.sessionID) ?? []
|
||||||
})
|
})
|
||||||
const visible = createMemo(() => !session()?.parentID && permissions().length === 0 && questions().length === 0)
|
const [composer, setComposer] = createStore({
|
||||||
|
open: false,
|
||||||
|
tab: undefined as string | undefined,
|
||||||
|
})
|
||||||
const disabled = createMemo(() => permissions().length > 0 || questions().length > 0)
|
const disabled = createMemo(() => permissions().length > 0 || questions().length > 0)
|
||||||
|
|
||||||
const pending = createMemo(() => {
|
const pending = createMemo(() => {
|
||||||
|
|
@ -764,11 +768,14 @@ export function Session() {
|
||||||
run: () => unavailable("Backgrounding subagents"),
|
run: () => unavailable("Backgrounding subagents"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Go to child session",
|
title: "Toggle subagent picker",
|
||||||
value: "session.child.first",
|
value: "session.child.first",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
hidden: true,
|
run: () => {
|
||||||
run: () => unavailable("Child session discovery"),
|
if (composer.open || session()?.parentID) setComposer("open", false)
|
||||||
|
else setComposer("open", true)
|
||||||
|
dialog.clear()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Go to parent session",
|
title: "Go to parent session",
|
||||||
|
|
@ -836,6 +843,7 @@ export function Session() {
|
||||||
|
|
||||||
// snap to bottom when session changes
|
// snap to bottom when session changes
|
||||||
createEffect(on(() => route.sessionID, toBottom))
|
createEffect(on(() => route.sessionID, toBottom))
|
||||||
|
createEffect(on(() => route.sessionID, () => setComposer("open", false)))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocationProvider location={location()}>
|
<LocationProvider location={location()}>
|
||||||
|
|
@ -898,37 +906,45 @@ export function Session() {
|
||||||
</Show>
|
</Show>
|
||||||
</scrollbox>
|
</scrollbox>
|
||||||
<box flexShrink={0}>
|
<box flexShrink={0}>
|
||||||
<Show when={permissions().length > 0}>
|
<Composer
|
||||||
<PermissionPrompt request={permissions()[0]} directory={session()?.location.directory} />
|
sessionID={route.sessionID}
|
||||||
</Show>
|
open={composer.open || !!session()?.parentID}
|
||||||
<Show when={permissions().length === 0 && questions().length > 0}>
|
defaultTab={composer.tab ?? (session()?.parentID ? "subagents" : undefined)}
|
||||||
<QuestionPrompt request={questions()[0]} directory={session()?.location.directory} />
|
onClose={() => setComposer("open", false)}
|
||||||
</Show>
|
/>
|
||||||
<Show when={session()?.parentID}>
|
<Switch>
|
||||||
<SubagentFooter />
|
<Match when={composer.open || !!session()?.parentID}>
|
||||||
</Show>
|
{null}
|
||||||
<Show when={visible()}>
|
</Match>
|
||||||
<pluginRuntime.Slot
|
<Match when={permissions().length > 0}>
|
||||||
name="session_prompt"
|
<PermissionPrompt request={permissions()[0]} directory={session()?.location.directory} />
|
||||||
mode="replace"
|
</Match>
|
||||||
session_id={route.sessionID}
|
<Match when={questions().length > 0}>
|
||||||
visible={visible()}
|
<QuestionPrompt request={questions()[0]} directory={session()?.location.directory} />
|
||||||
disabled={disabled()}
|
</Match>
|
||||||
on_submit={toBottom}
|
<Match when={!disabled()}>
|
||||||
ref={bind}
|
<pluginRuntime.Slot
|
||||||
>
|
name="session_prompt"
|
||||||
<Prompt
|
mode="replace"
|
||||||
visible={visible()}
|
session_id={route.sessionID}
|
||||||
|
visible={true}
|
||||||
|
disabled={false}
|
||||||
|
on_submit={toBottom}
|
||||||
ref={bind}
|
ref={bind}
|
||||||
disabled={disabled()}
|
>
|
||||||
onSubmit={() => {
|
<Prompt
|
||||||
toBottom()
|
visible={true}
|
||||||
}}
|
ref={bind}
|
||||||
sessionID={route.sessionID}
|
disabled={false}
|
||||||
right={<pluginRuntime.Slot name="session_prompt_right" session_id={route.sessionID} />}
|
onSubmit={() => {
|
||||||
/>
|
toBottom()
|
||||||
</pluginRuntime.Slot>
|
}}
|
||||||
</Show>
|
sessionID={route.sessionID}
|
||||||
|
right={<pluginRuntime.Slot name="session_prompt_right" session_id={route.sessionID} />}
|
||||||
|
/>
|
||||||
|
</pluginRuntime.Slot>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
</box>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
<Toast />
|
<Toast />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue