Submit initial OpenCode 2.0.12 prompts through the native startup plugin (#25428)

* fix(opencode): submit initial prompts for reviewed 2.0.12

Route the reported release through the existing native startup intent.
Retain conservative behavior for other unreviewed versions.

* Wait for OpenCode startup fixture file reads in the hydration test

---------

Co-authored-by: nwparker <nwparker@users.noreply.github.com>
This commit is contained in:
Neil
2026-10-05 03:08:22 -07:00
committed by GitHub
co-authored by nwparker
parent 40f9e7a3fc
commit 8b5de39217
5 changed files with 107 additions and 42 deletions
+46 -33
View File
@@ -91,32 +91,40 @@ describe('execution-host OpenCode launch preparation', () => {
expect(finalEnv.OPENCODE_CONFIG_DIR).toBe('/private/owned-overlay')
expect(finalEnv.ORCA_OPENCODE_SOURCE_CONFIG_DIR).toBe('/private/real-source')
})
it('removes only the automatic verified v2 prompt argument, retaining explicit run and manual flags', async () => {
probe.mockResolvedValue(getOpenCodeCliCapabilities('2.0.16'))
const env = {
ORCA_AGENT_LAUNCH_TOKEN: 'admitted-launch',
[OPENCODE_STARTUP_PROMPT_SHA256_ENV]: createHash('sha256').update('task').digest('hex'),
[OPENCODE_STARTUP_PROMPT_BODY_ENV]: 'task',
[OPENCODE_STARTUP_PROMPT_SHELL_ENV]: 'posix'
it.each(['2.0.12', '2.0.16'])(
'removes only the automatic %s prompt argument, retaining explicit run and manual flags',
async (version) => {
probe.mockResolvedValue(getOpenCodeCliCapabilities(version))
const env = {
ORCA_AGENT_LAUNCH_TOKEN: 'admitted-launch',
[OPENCODE_STARTUP_PROMPT_SHA256_ENV]: createHash('sha256').update('task').digest('hex'),
[OPENCODE_STARTUP_PROMPT_BODY_ENV]: 'task',
[OPENCODE_STARTUP_PROMPT_SHELL_ENV]: 'posix'
}
const options = { env, envToDelete: [], isFreshLaunch: true }
expect(
(
await prepareOpenCodePtyLaunch({
...options,
command: "opencode --standalone --prompt 'task'"
})
).command
).toBe('opencode --standalone')
expect(
(await prepareOpenCodePtyLaunch({ ...options, command: "opencode run --prompt 'task'" }))
.command
).toBe("opencode run --prompt 'task'")
expect(
(
await prepareOpenCodePtyLaunch({
...options,
env: {},
command: "opencode --prompt 'task'"
})
).command
).toBe("opencode --prompt 'task'")
}
const options = { env, envToDelete: [], isFreshLaunch: true }
expect(
(
await prepareOpenCodePtyLaunch({
...options,
command: "opencode --standalone --prompt 'task'"
})
).command
).toBe('opencode --standalone')
expect(
(await prepareOpenCodePtyLaunch({ ...options, command: "opencode run --prompt 'task'" }))
.command
).toBe("opencode run --prompt 'task'")
expect(
(await prepareOpenCodePtyLaunch({ ...options, env: {}, command: "opencode --prompt 'task'" }))
.command
).toBe("opencode --prompt 'task'")
})
)
it.each(['inherited', 'explicit', 'deleted'] as const)(
'passes the %s config environment used by the execution-host version probe to the prompt installer',
async (selection) => {
@@ -178,9 +186,16 @@ describe('execution-host OpenCode launch preparation', () => {
expect(result.env).not.toHaveProperty('ORCA_OPENCODE_STARTUP_PROMPT_NONCE')
}
)
it.each(['1.1.23', '2.0.16', '2.0.17', 'unknown'])(
'gates native intent against the executing %s capability',
async (version) => {
it.each([
{ version: '1.1.23', nativeIntent: false },
{ version: '2.0.12', nativeIntent: true },
{ version: '2.0.16', nativeIntent: true },
{ version: '2.0.17', nativeIntent: false },
{ version: '2.0.21', nativeIntent: false },
{ version: 'unknown', nativeIntent: false }
])(
'gates native intent against the executing $version capability',
async ({ version, nativeIntent }) => {
probe.mockResolvedValue(getOpenCodeCliCapabilities(version))
const envToDelete: string[] = []
const fingerprint = createHash('sha256').update('task').digest('hex')
@@ -195,10 +210,8 @@ describe('execution-host OpenCode launch preparation', () => {
envToDelete,
isFreshLaunch: true
})
expect(env?.[OPENCODE_STARTUP_PROMPT_SHA256_ENV]).toBe(
version === '2.0.16' ? fingerprint : undefined
)
expect(envToDelete.includes(OPENCODE_STARTUP_PROMPT_SHA256_ENV)).toBe(version !== '2.0.16')
expect(env?.[OPENCODE_STARTUP_PROMPT_SHA256_ENV]).toBe(nativeIntent ? fingerprint : undefined)
expect(envToDelete.includes(OPENCODE_STARTUP_PROMPT_SHA256_ENV)).toBe(!nativeIntent)
}
)
@@ -266,7 +279,7 @@ describe('execution-host OpenCode launch preparation', () => {
expect(finalEnv).not.toHaveProperty('KEEP_DELETED')
})
it.each(['1.1.23', '2.0.16'])(
it.each(['1.1.23', '2.0.12', '2.0.16'])(
'selects the probed %s plugin for the execution host',
async (version) => {
const capabilities = getOpenCodeCliCapabilities(version)
@@ -42,7 +42,7 @@ class Editor extends EventEmitter {
type FixtureLocation = { directory: string; workspaceID?: string }
function fixture() {
function fixture(version = '2.0.16') {
const editor = new Editor()
const input = new EventEmitter()
const memory = { settled: false, expiresAt: Date.now() + 20000 }
@@ -52,7 +52,7 @@ function fixture() {
const sync = vi.fn(async (_location: FixtureLocation) => {})
const dispatch = vi.fn(() => editor.replace(''))
const ctx = {
app: { version: '2.0.16' },
app: { version },
renderer: { keyInput: input, currentFocusedEditor: editor },
storage: { memory: () => [memory, (mutate: (draft: typeof memory) => void) => mutate(memory)] },
keymap: { dispatch },
@@ -90,6 +90,41 @@ afterEach(() => {
})
describe('installed-version native prompt intent plugin', () => {
it.each(['2.0.12', '2.0.16'])(
'hydrates the source-reviewed %s location and delivers the same intent only once',
async (version) => {
const f = fixture(version)
let hydrate = () => {}
f.sync.mockImplementation(
() =>
new Promise<void>((resolve) => {
hydrate = resolve
})
)
const insert = vi.spyOn(f.editor, 'insertText')
const dispose = await setup(f.ctx)
try {
await vi.advanceTimersByTimeAsync(500)
expect(f.sync).toHaveBeenCalledExactlyOnceWith(f.ctx.location)
expect(claim).not.toHaveBeenCalled()
expect(insert).not.toHaveBeenCalled()
hydrate()
await vi.advanceTimersByTimeAsync(1000)
await vi.waitFor(() => expect(claim).toHaveBeenCalledTimes(1))
expect(insert).toHaveBeenCalledExactlyOnceWith(prompt)
expect(f.dispatch).toHaveBeenCalledExactlyOnceWith('prompt.submit')
expect(f.memory.settled).toBe(true)
const reloadDispose = await setup(f.ctx)
await vi.advanceTimersByTimeAsync(500)
expect(claim).toHaveBeenCalledTimes(1)
expect(f.dispatch).toHaveBeenCalledTimes(1)
await reloadDispose()
} finally {
await dispose()
}
}
)
it('waits for the current home location after the startup directory changes', async () => {
const f = fixture()
let location: FixtureLocation = { directory: '/private/home/private-folder' }
@@ -1,3 +1,4 @@
import { OPENCODE_NATIVE_PROMPT_VERSIONS } from '../../shared/opencode-cli-version'
import { cancelUnreadResponseBody } from '../lib/unread-response-body'
import { parseAgentHookEndpointFile } from '../../shared/agent-hook-endpoint-file'
import {
@@ -40,7 +41,7 @@ async function submitStartupPrompt(ctx) {
const nonce = process.env.${OPENCODE_STARTUP_PROMPT_NONCE_ENV};
const endpoint = process.env.${OPENCODE_STARTUP_PROMPT_ENDPOINT_ENV};
const prompt = process.env.${OPENCODE_STARTUP_PROMPT_BODY_ENV};
if (ctx?.app?.version !== "2.0.16" || !/^[a-f0-9]{64}$/.test(digest || "") || !nonce || !endpoint || !prompt) return noop;
if (!${JSON.stringify(OPENCODE_NATIVE_PROMPT_VERSIONS)}.includes(ctx?.app?.version) || !/^[a-f0-9]{64}$/.test(digest || "") || !nonce || !endpoint || !prompt) return noop;
const input = ctx.renderer?.keyInput;
if (typeof ctx.storage?.memory !== "function" || typeof input?.on !== "function" ||
typeof input?.off !== "function" || typeof ctx.keymap?.dispatch !== "function" ||
+14 -5
View File
@@ -1,9 +1,12 @@
import { expect, it } from 'vitest'
import { getOpenCodeCliCapabilities, parseOpenCodeCliVersion } from './opencode-cli-version'
it.each(['1.1.23', 'opencode v2.0.16', '2.0.0-beta.1+build'])('parses CLI output %s', (output) => {
expect(parseOpenCodeCliVersion(output)).toBe(output.replace(/^opencode v/, ''))
})
it.each(['1.1.23', 'opencode v2.0.12', 'opencode v2.0.16', '2.0.0-beta.1+build'])(
'parses CLI output %s',
(output) => {
expect(parseOpenCodeCliVersion(output)).toBe(output.replace(/^opencode v/, ''))
}
)
it.each([null, '', 'Error 2.0.16', '1.1', '2.0.16\nwarning'])(
'does not mistake other output for a version',
(output) => {
@@ -21,6 +24,11 @@ it('keeps future versions unknown instead of assuming a plugin loader or prompt
pluginApi: 'v1',
promptMode: 'submit'
})
expect(getOpenCodeCliCapabilities('opencode v2.0.12')).toEqual({
version: '2.0.12',
pluginApi: 'v2',
promptMode: 'prefill'
})
expect(getOpenCodeCliCapabilities('opencode v2.0.16')).toEqual({
version: '2.0.16',
pluginApi: 'v2',
@@ -29,6 +37,7 @@ it('keeps future versions unknown instead of assuming a plugin loader or prompt
})
it('does not opt unverified v2 builds into an extra prompt submission', () => {
expect(getOpenCodeCliCapabilities('2.0.17').promptMode).toBe('unknown')
expect(getOpenCodeCliCapabilities('2.0.16-beta').promptMode).toBe('unknown')
for (const version of ['2.0.13', '2.0.15', '2.0.17', '2.0.21', '2.0.12-beta', '2.0.16-beta']) {
expect(getOpenCodeCliCapabilities(version).promptMode).toBe('unknown')
}
})
+8 -1
View File
@@ -1,5 +1,7 @@
import { isValidAppVersion } from './app-version'
export const OPENCODE_NATIVE_PROMPT_VERSIONS = ['2.0.12', '2.0.16'] as const
export type OpenCodeCliCapabilities = {
version: string | null
pluginApi: 'v1' | 'v2' | 'unknown'
@@ -21,6 +23,11 @@ export function getOpenCodeCliCapabilities(
return {
version,
pluginApi: major === '1' ? 'v1' : major === '2' ? 'v2' : 'unknown',
promptMode: major === '1' ? 'submit' : version === '2.0.16' ? 'prefill' : 'unknown'
promptMode:
major === '1'
? 'submit'
: OPENCODE_NATIVE_PROMPT_VERSIONS.some((supported) => supported === version)
? 'prefill'
: 'unknown'
}
}