From 9c10878dbfc2d2bd89edbf91b02af3bc67237fbc Mon Sep 17 00:00:00 2001 From: Simon Klee Date: Fri, 17 Apr 2026 15:07:33 +0200 Subject: [PATCH] minor startup improvment --- packages/opencode/src/cli/cmd/run.ts | 2 +- packages/opencode/src/cli/cmd/run/footer.ts | 24 +++++++++- packages/opencode/src/cli/cmd/run/runtime.ts | 50 ++++++++++++++++---- packages/opencode/src/cli/cmd/run/types.ts | 5 ++ 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 575f28f6da..20ce245831 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -700,7 +700,7 @@ export const RunCommand = cmd({ directory: cwd, sessionID, sessionTitle: sess.title, - resume: Boolean(args.session) && !args.fork, + resume: Boolean(args.session || args.continue) && !args.fork, agent, model, variant: args.variant, diff --git a/packages/opencode/src/cli/cmd/run/footer.ts b/packages/opencode/src/cli/cmd/run/footer.ts index f36d765751..e71be4b4e0 100644 --- a/packages/opencode/src/cli/cmd/run/footer.ts +++ b/packages/opencode/src/cli/cmd/run/footer.ts @@ -106,6 +106,10 @@ export class RunFooter implements FooterApi { // Fixed portion of footer height above the textarea. private base: number private rows = TEXTAREA_MIN_ROWS + private agents: Accessor + private setAgents: Setter + private resources: Accessor + private setResources: Setter private state: Accessor private setState: Setter private view: Accessor @@ -138,6 +142,12 @@ export class RunFooter implements FooterApi { const [view, setView] = createSignal({ type: "prompt" }) this.view = view this.setView = setView + const [agents, setAgents] = createSignal(options.agents) + this.agents = agents + this.setAgents = setAgents + const [resources, setResources] = createSignal(options.resources) + this.resources = resources + this.setResources = setResources const [subagent, setSubagent] = createSignal(createEmptySubagentState()) this.subagent = subagent this.setSubagent = setSubagent @@ -154,8 +164,8 @@ export class RunFooter implements FooterApi { view: this.view, subagent: this.subagent, findFiles: options.findFiles, - agents: () => options.agents, - resources: () => options.resources, + agents: this.agents, + resources: this.resources, theme: options.theme, diffStyle: options.diffStyle, keybinds: options.keybinds, @@ -206,6 +216,16 @@ export class RunFooter implements FooterApi { } public event(next: FooterEvent): void { + if (next.type === "catalog") { + if (this.destroyed || this.renderer.isDestroyed) { + return + } + + this.setAgents(next.agents) + this.setResources(next.resources) + return + } + if (next.type === "queue") { this.patch({ queue: next.queue }) return diff --git a/packages/opencode/src/cli/cmd/run/runtime.ts b/packages/opencode/src/cli/cmd/run/runtime.ts index 670a8f808a..e61033c1d2 100644 --- a/packages/opencode/src/cli/cmd/run/runtime.ts +++ b/packages/opencode/src/cli/cmd/run/runtime.ts @@ -12,6 +12,7 @@ // 3. starts the stream transport (SDK event subscription), // 4. runs the prompt queue until the footer closes. import { createOpencodeClient } from "@opencode-ai/sdk/v2" +import { Flag } from "@/flag/flag" import { createRunDemo } from "./demo" import { resolveDiffStyle, resolveFooterKeybinds, resolveModelInfo, resolveSessionInfo } from "./runtime.boot" import { createRuntimeLifecycle } from "./runtime.lifecycle" @@ -25,7 +26,10 @@ export { pickVariant, resolveVariant } from "./variant.shared" /** @internal Exported for testing */ export { runPromptQueue } from "./runtime.queue" -type BootContext = Pick +type BootContext = Pick< + RunInput, + "sdk" | "directory" | "sessionID" | "sessionTitle" | "resume" | "agent" | "model" | "variant" +> type RunRuntimeInput = { boot: () => Promise @@ -60,12 +64,20 @@ type RunLocalInput = { // Files only attach on the first prompt turn -- after that, includeFiles // flips to false so subsequent turns don't re-send attachments. async function runInteractiveRuntime(input: RunRuntimeInput): Promise { + const start = performance.now() const log = trace() const keybindTask = resolveFooterKeybinds() const diffTask = resolveDiffStyle() const ctx = await input.boot() const modelTask = resolveModelInfo(ctx.sdk, ctx.model) - const sessionTask = resolveSessionInfo(ctx.sdk, ctx.sessionID, ctx.model) + const sessionTask = + ctx.resume === true + ? resolveSessionInfo(ctx.sdk, ctx.sessionID, ctx.model) + : Promise.resolve({ + first: true, + history: [], + variant: undefined, + }) const savedTask = resolveSavedVariant(ctx.model) const agentsTask = ctx.sdk.app .agents({ directory: ctx.directory }) @@ -80,13 +92,11 @@ async function runInteractiveRuntime(input: RunRuntimeInput): Promise { let aborting = false let shown = false let demo: ReturnType | undefined - const [keybinds, diffStyle, session, savedVariant, agents, resources] = await Promise.all([ + const [keybinds, diffStyle, session, savedVariant] = await Promise.all([ keybindTask, diffTask, sessionTask, savedTask, - agentsTask, - resourcesTask, ]) shown = !session.first let activeVariant = resolveVariant(ctx.variant, session.variant, savedVariant, variants) @@ -99,8 +109,8 @@ async function runInteractiveRuntime(input: RunRuntimeInput): Promise { .files({ query, directory: ctx.directory }) .then((x) => x.data ?? []) .catch(() => []), - agents, - resources, + agents: [], + resources: [], sessionID: ctx.sessionID, sessionTitle: ctx.sessionTitle, first: session.first, @@ -170,6 +180,27 @@ async function runInteractiveRuntime(input: RunRuntimeInput): Promise { }) const footer = shell.footer + void Promise.all([agentsTask, resourcesTask]).then(([agents, resources]) => { + if (footer.isClosed) { + return + } + + footer.event({ + type: "catalog", + agents, + resources, + }) + }) + + if (Flag.OPENCODE_SHOW_TTFD) { + footer.append({ + kind: "system", + text: `startup ${Math.max(0, Math.round(performance.now() - start))}ms`, + phase: "final", + source: "system", + }) + } + if (input.demo) { demo = createRunDemo({ mode: input.demo, @@ -292,8 +323,7 @@ export async function runInteractiveLocalMode(input: RunLocalInput): Promise input.share(ctx.sdk, ctx.sessionID), boot: async () => { - const agent = await input.resolveAgent() - const session = await input.session(sdk) + const [agent, session] = await Promise.all([input.resolveAgent(), input.session(sdk)]) if (!session?.id) { throw new Error("Session not found") } @@ -303,6 +333,7 @@ export async function runInteractiveLocalMode(input: RunLocalInput): Promise { directory: input.directory, sessionID: input.sessionID, sessionTitle: input.sessionTitle, + resume: input.resume, agent: input.agent, model: input.model, variant: input.variant, diff --git a/packages/opencode/src/cli/cmd/run/types.ts b/packages/opencode/src/cli/cmd/run/types.ts index 2686e7bc0e..8a22c107ac 100644 --- a/packages/opencode/src/cli/cmd/run/types.ts +++ b/packages/opencode/src/cli/cmd/run/types.ts @@ -129,6 +129,11 @@ export type FooterOutput = { // transport both emit these to update footer state without reaching into // internal signals directly. export type FooterEvent = + | { + type: "catalog" + agents: RunAgent[] + resources: RunResource[] + } | { type: "queue" queue: number