Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
e768f4c
feat: add subagents sidebar with clickable navigation and parent keybind
franlol Nov 28, 2025
f49b8dd
Merge branch 'dev' into subagents-in-the-sidebar
franlol Nov 28, 2025
ef95381
fix: fix sdk
franlol Nov 29, 2025
ff7a3b5
Merge branch 'dev' into subagents-in-the-sidebar
franlol Nov 29, 2025
62e6469
fix: navigation click propagation
franlol Nov 29, 2025
d61b04a
tweak: better error message if no primary agents are enabled
rekram1-node Dec 20, 2025
e265ee2
Revert "tweak: better error message if no primary agents are enabled"
rekram1-node Dec 20, 2025
4a822a1
Fix TypeError when accessing default model ID for providers with no m…
ariane-emory Dec 20, 2025
f6c85db
Merge branch 'dev' into fix/providers-safety
ariane-emory Dec 20, 2025
7a7cc02
Merge dev into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 20, 2025
347afe3
Merge branch 'dev' into fix/providers-safety
ariane-emory Dec 21, 2025
b2b7324
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 21, 2025
62c2490
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 21, 2025
e193c35
Merge branch 'dev' into fix/providers-safety
ariane-emory Dec 21, 2025
0f92fff
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 22, 2025
55f4e8f
Merge branch 'dev' into fix/providers-safety
ariane-emory Dec 22, 2025
137509b
Merge branch 'dev' into fix/providers-safety
ariane-emory Dec 23, 2025
e6bd96f
Merge dev branch with resolved conflicts
ariane-emory Dec 23, 2025
5ef6b36
Merge branch 'dev' into fix/providers-safety
ariane-emory Dec 23, 2025
6b07855
Merge dev into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 23, 2025
1ba4c8f
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 23, 2025
2decd8b
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 23, 2025
ec82932
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 23, 2025
1f4f2e4
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 23, 2025
1002103
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 24, 2025
2c22dae
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 24, 2025
97d847f
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 24, 2025
f861561
Fix type compatibility for new AI SDK providers
ariane-emory Dec 24, 2025
d66a688
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 24, 2025
9b4d358
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 25, 2025
3af3b14
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 25, 2025
f0c320c
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 25, 2025
5e621b3
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 26, 2025
c12fd82
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 27, 2025
d7ef309
revert a file
ariane-emory Dec 27, 2025
0c65001
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 27, 2025
5483c82
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 27, 2025
497dbbd
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 28, 2025
6f63e91
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 28, 2025
df71625
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 28, 2025
401103b
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 29, 2025
3cde278
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 29, 2025
9fe7ad4
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 29, 2025
98a1d98
Merge branch 'feat/franlol--subagents-in-the-sidebar' of github.com:a…
ariane-emory Dec 29, 2025
4edcd11
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 29, 2025
90e57b0
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 29, 2025
2d69c61
Set smallOptions for google models on openrouter (#6362)
neominik Dec 29, 2025
4314d0c
docs: opencode notificator plugin (fixed link) (#6341)
panta82 Dec 29, 2025
6720611
tweak: adjust git watcher to ignore files other than HEAD
rekram1-node Dec 29, 2025
1434f7d
fix: add timeout to filewatcher subscriptions
rekram1-node Dec 29, 2025
f79338f
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 30, 2025
a583cb4
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 30, 2025
a832fdc
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 30, 2025
dc5b849
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 30, 2025
dc5cc85
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 30, 2025
72ab1f5
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 30, 2025
959acf9
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Dec 31, 2025
7d0dd48
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 1, 2026
dfebed3
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 1, 2026
7df5b21
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 1, 2026
2434b6e
Fix type error for Vercel provider after merge
ariane-emory Jan 1, 2026
6b442b4
revert a file
ariane-emory Jan 1, 2026
b4b122e
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 2, 2026
0e86291
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 3, 2026
ffca71f
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 3, 2026
b34a9f0
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 3, 2026
547e4d8
Merge branch 'feat/franlol--subagents-in-the-sidebar' of github.com:a…
ariane-emory Jan 3, 2026
b3ad83d
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 4, 2026
5de7574
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 4, 2026
18a1408
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 4, 2026
e3a22e0
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 5, 2026
27d599c
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 5, 2026
edffc93
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 5, 2026
b13bac1
Fix SDK type import mismatch after merge
ariane-emory Jan 5, 2026
5884f15
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 5, 2026
b9f722b
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 6, 2026
6d4edc9
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 6, 2026
86227a4
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 6, 2026
d7aa32b
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 6, 2026
a7e02f7
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 6, 2026
457d353
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 7, 2026
f6d5c0a
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 7, 2026
7aa255f
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 7, 2026
6526139
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 7, 2026
e7585af
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 8, 2026
0d8d5a4
Merge dev into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 11, 2026
659b51f
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 13, 2026
ded9c56
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 13, 2026
12bd91a
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 13, 2026
455f32a
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 15, 2026
6b2c2ae
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 16, 2026
d9339e8
Merge dev into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 17, 2026
5f68194
Fix merge conflicts in server.ts
ariane-emory Jan 17, 2026
1336d84
Fix SessionStatus import
ariane-emory Jan 17, 2026
30a5b36
Fix SessionRevert import
ariane-emory Jan 17, 2026
d62c02f
Fix SessionCompaction import
ariane-emory Jan 17, 2026
d0d55f4
Merge branch 'dev' into feat/franlol--subagents-in-the-sidebar
ariane-emory Jan 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ export function Session() {
}
}

function goToParent() {
const parentID = session()?.parentID
if (parentID) {
navigate({ type: "session", sessionID: parentID })
}
}

const command = useCommandDialog()
command.register(() => [
...(sync.data.config.share !== "disabled"
Expand Down Expand Up @@ -815,15 +822,9 @@ export function Session() {
value: "session.parent",
keybind: "session_parent",
category: "Session",
disabled: true,
disabled: !session()?.parentID,
onSelect: (dialog) => {
const parentID = session()?.parentID
if (parentID) {
navigate({
type: "session",
sessionID: parentID,
})
}
goToParent()
dialog.clear()
},
},
Expand Down
107 changes: 104 additions & 3 deletions packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { useSync } from "@tui/context/sync"
import { createMemo, For, Show, Switch, Match } from "solid-js"
import { createMemo, For, Show, Switch, Match, createSignal, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
import { useTheme } from "../../context/theme"
import { useRoute } from "../../context/route"
import { Locale } from "@/util/locale"
import path from "path"
import type { AssistantMessage } from "@opencode-ai/sdk/v2"
import type { AssistantMessage } from "@opencode-ai/sdk"
import type { AssistantMessage as AssistantMessageV2, ToolPart } from "@opencode-ai/sdk/v2"
import { Global } from "@/global"
import { Installation } from "@/installation"
import { useKeybind } from "../../context/keybind"
import { useDirectory } from "../../context/directory"
import { useKV } from "../../context/kv"
import { TodoItem } from "../../component/todo-item"

export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
const sync = useSync()
const route = useRoute()
const { theme } = useTheme()
const session = createMemo(() => sync.session.get(props.sessionID)!)
const diff = createMemo(() => sync.data.session_diff[props.sessionID] ?? [])
Expand All @@ -25,11 +27,43 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
diff: true,
todo: true,
lsp: true,
subagents: true,
})

// Animated spinner
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
const [spinnerIndex, setSpinnerIndex] = createSignal(0)

const intervalId = setInterval(() => {
setSpinnerIndex((prev) => (prev + 1) % spinnerFrames.length)
}, 100)
onCleanup(() => clearInterval(intervalId))

// Sort MCP servers alphabetically for consistent display order
const mcpEntries = createMemo(() => Object.entries(sync.data.mcp).sort(([a], [b]) => a.localeCompare(b)))

const taskToolParts = createMemo(() => {
const parts: ToolPart[] = []
for (const message of messages()) {
for (const part of sync.data.part[message.id] ?? []) {
if (part.type === "tool" && part.tool === "task") parts.push(part)
}
}
return parts
})

const subagentGroups = createMemo(() => {
const groups = new Map<string, ToolPart[]>()
for (const part of taskToolParts()) {
const input = part.state.input as Record<string, unknown>
const agentName = input?.subagent_type as string
if (!agentName) continue
if (!groups.has(agentName)) groups.set(agentName, [])
groups.get(agentName)!.push(part)
}
return Array.from(groups.entries())
})

// Count connected and error MCP servers for collapsed header display
const connectedMcpCount = createMemo(() => mcpEntries().filter(([_, item]) => item.status === "connected").length)
const errorMcpCount = createMemo(
Expand Down Expand Up @@ -158,6 +192,73 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
</Show>
</box>
</Show>
<Show when={subagentGroups().length > 0}>
<box>
<box
flexDirection="row"
gap={1}
onMouseDown={() => subagentGroups().length > 2 && setExpanded("subagents", !expanded.subagents)}
>
<Show when={subagentGroups().length > 2}>
<text fg={theme.text}>{expanded.subagents ? "▼" : "▶"}</text>
</Show>
<text fg={theme.text}>
<b>Subagents</b>
</text>
</box>
<Show when={subagentGroups().length <= 2 || expanded.subagents}>
<For each={subagentGroups()}>
{([agentName, parts]) => {
const hasActive = () =>
parts.some((p) => p.state.status === "running" || p.state.status === "pending")
return (
<box>
<box flexDirection="row" gap={1}>
<text flexShrink={0} style={{ fg: hasActive() ? theme.success : theme.text }}>
</text>
<text fg={theme.text} wrapMode="word">
{agentName}
</text>
</box>
<For each={parts}>
{(part) => {
const isActive = () => part.state.status === "running" || part.state.status === "pending"
const isError = () => part.state.status === "error"
const input = part.state.input as Record<string, unknown>
const description = (input?.description as string) ?? ""
const stateMetadata = (part.state as { metadata?: Record<string, unknown> }).metadata
const sessionId = (part.metadata?.sessionId ?? stateMetadata?.sessionId) as
| string
| undefined
return (
<box
flexDirection="row"
gap={1}
paddingLeft={2}
onMouseUp={(e) => {
if (e.button === 0 && sessionId) {
route.navigate({ type: "session", sessionID: sessionId })
}
}}
>
<text flexShrink={0} fg={isActive() ? theme.success : theme.textMuted}>
{isActive() ? spinnerFrames[spinnerIndex()] : isError() ? "✗" : "✓"}
</text>
<text fg={isActive() ? theme.text : theme.textMuted} wrapMode="word">
{description}
</text>
</box>
)
}}
</For>
</box>
)
}}
</For>
</Show>
</box>
</Show>
<box>
<box
flexDirection="row"
Expand Down
Loading