Compare commits

...

6 Commits

Author SHA1 Message Date
Brendan Allan f69ff61600 fix(app): sync session selection before prompts 2026-08-05 19:32:42 +08:00
Brendan Allan a81628ebfe test(app): stabilize reconnect offset timing 2026-08-05 18:53:30 +08:00
Brendan Allan 232ee3b10d test(app): fix v2 timeline fixture ids 2026-08-05 16:15:56 +08:00
Brendan Allan dcd1d457b0 chore(app): merge v2 into v1 removal 2026-08-05 16:02:18 +08:00
Brendan Allan 76b318e990 refactor(app): gate unsupported v2 capabilities (#40381) 2026-08-05 15:15:26 +08:00
opencode-agent[bot] c0ab35c3c2 fix(app): show active search details (#40597)
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
2026-08-05 17:09:42 +10:00
6 changed files with 72 additions and 8 deletions
@@ -263,7 +263,7 @@ function turn(index: number, target: boolean, status: "running" | "completed" =
),
contextTool(contextIDs[3]!, assistantID, "list", { path: "src" }, status),
{
id: "prt_0104_text",
id: followingTextID,
sessionID,
messageID: assistantID,
type: "text",
@@ -31,6 +31,12 @@ const promotedDrafts: Array<{ draftID: string; server: string; sessionId: string
const sentPrompts: string[] = []
const promptInputs: unknown[] = []
const sentCommands: unknown[] = []
const switchedAgents: Array<{ sessionID: string; agent: string }> = []
const switchedModels: Array<{
sessionID: string
model: { id: string; providerID: string; variant?: string }
}> = []
const sessionRequestOrder: string[] = []
const commands: Array<{ name: string }> = []
let serverSessionSyncs = 0
@@ -93,10 +99,22 @@ const clientFor = (directory: string) => {
}
},
prompt: async (input: unknown) => {
sessionRequestOrder.push("prompt")
sentPrompts.push(directory)
promptInputs.push(input)
return { data: undefined }
},
switchAgent: async (input: { sessionID: string; agent: string }) => {
sessionRequestOrder.push("agent")
switchedAgents.push(input)
},
switchModel: async (input: {
sessionID: string
model: { id: string; providerID: string; variant?: string }
}) => {
sessionRequestOrder.push("model")
switchedModels.push(input)
},
command: async (input: unknown) => {
sentCommands.push(input)
},
@@ -279,6 +297,9 @@ beforeEach(() => {
sentPrompts.length = 0
promptInputs.length = 0
sentCommands.length = 0
switchedAgents.length = 0
switchedModels.length = 0
sessionRequestOrder.length = 0
commands.length = 0
promptValue = [{ type: "text", content: "ls", start: 0, end: 2 }]
params = {}
@@ -436,13 +457,17 @@ describe("prompt submit worktree selection", () => {
expect(promotedDrafts).toEqual([{ draftID: "draft-1", server: "project-server", sessionId: "session-1" }])
})
test("includes the selected variant on optimistic prompts", async () => {
test("switches the selected agent and model before prompting", async () => {
params = { id: "session-1" }
variant = "high"
const submit = createPromptSubmit({
prompt,
info: () => ({ id: "session-1" }),
info: () => ({
id: "session-1",
agent: "old-agent",
model: { id: "old-model", providerID: "old-provider" },
}),
imageAttachments: () => [],
commentCount: () => 0,
autoAccept: () => false,
@@ -471,6 +496,14 @@ describe("prompt submit worktree selection", () => {
},
})
expect(sentPrompts).toEqual(["/repo/main"])
expect(switchedAgents).toEqual([{ sessionID: "session-1", agent: "agent" }])
expect(switchedModels).toEqual([
{
sessionID: "session-1",
model: { id: "model", providerID: "provider", variant: "high" },
},
])
expect(sessionRequestOrder).toEqual(["agent", "model", "prompt"])
expect(promptInputs[0]).toMatchObject({
sessionID: "session-1",
text: "ls",
@@ -45,6 +45,7 @@ type FollowupSendInput = {
api: DirectorySDK["api"]["session"]
serverSync: ServerSync
sync: DirectorySync
session: Accessor<{ agent?: string; model?: { id: string; providerID: string; variant?: string } } | undefined>
draft: FollowupDraft
messageID?: string
optimisticBusy?: boolean
@@ -157,6 +158,25 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
return false
}
const session = input.session()
if (session?.agent !== input.draft.agent) {
await input.api.switchAgent({ sessionID: input.draft.sessionID, agent: input.draft.agent })
}
if (
session?.model?.providerID !== input.draft.model.providerID ||
session.model.id !== input.draft.model.modelID ||
(session.model.variant ?? "default") !== (input.draft.variant ?? "default")
) {
await input.api.switchModel({
sessionID: input.draft.sessionID,
model: {
id: input.draft.model.modelID,
providerID: input.draft.model.providerID,
variant: input.draft.variant,
},
})
}
await input.api.prompt({
sessionID: input.draft.sessionID,
id: messageID,
@@ -197,7 +217,9 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
type PromptSubmitInput = {
prompt: ReturnType<typeof usePrompt>
info: Accessor<{ id: string } | undefined>
info: Accessor<
{ id: string; agent?: string; model?: { id: string; providerID: string; variant?: string } } | undefined
>
imageAttachments: Accessor<ImageAttachmentPart[]>
commentCount: Accessor<number>
autoAccept: Accessor<boolean>
@@ -595,6 +617,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
api: sdk().api.session,
sync: sync(),
serverSync: serverSync(),
session: () => input.info() ?? session,
draft,
messageID,
optimisticBusy: sessionDirectory === projectDirectory,
+1
View File
@@ -1721,6 +1721,7 @@ export default function Page() {
api: sdk().api.session,
sync: sync(),
serverSync: serverSync(),
session: () => sync().session.get(input.sessionID),
draft: item,
optimisticBusy: item.sessionDirectory === sdk().directory,
}).catch((err) => {
@@ -45,8 +45,7 @@ test("reports a divergent native offset once and ignores equal offsets and unrel
route.remove()
document.body.append(route)
await new Promise((resolve) => setTimeout(resolve, 0))
await frames(3)
await waitFor(() => calls.length === 1)
expect(calls).toEqual([[0, false]])
route.remove()
@@ -197,3 +196,8 @@ async function frames(count: number) {
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
}
}
async function waitFor(condition: () => boolean) {
const deadline = performance.now() + 1_000
while (!condition() && performance.now() < deadline) await frames(1)
}
@@ -1142,6 +1142,9 @@ export function ContextToolGroup(props: {
const running = createMemo(
() => partAccessor().state.status === "pending" || partAccessor().state.status === "running",
)
const showDetails = createMemo(
() => !running() || partAccessor().tool === "glob" || partAccessor().tool === "grep",
)
return (
<div data-slot="context-tool-group-item">
<div data-component="tool-trigger">
@@ -1152,10 +1155,10 @@ export function ContextToolGroup(props: {
<span data-slot="basic-tool-tool-title">
<TextShimmer text={trigger().title} active={running()} />
</span>
<Show when={!running() && trigger().subtitle}>
<Show when={showDetails() && trigger().subtitle}>
<span data-slot="basic-tool-tool-subtitle">{trigger().subtitle}</span>
</Show>
<Show when={!running() && trigger().args?.length}>
<Show when={showDetails() && trigger().args?.length}>
<For each={trigger().args}>
{(arg) => <span data-slot="basic-tool-tool-arg">{arg}</span>}
</For>