Files
orca/src/shared/opencode-launch-command.ts
T
Brennan Benson 850e71e672 Resume in New CLI for OpenCode and OMP chats (#28075)
* feat(chat): Resume in New CLI for OpenCode and OMP chats

OpenCode's and OMP's CLIs have no copy flag, but their chat protocol copies a
conversation (ACP session/fork). For a fork of one of their chats, the chat's
host now has a short-lived agent, launched exactly as the chat's own, copy the
conversation; the terminal then opens the copy with a plain resume. The chat
keeps its own conversation, so the CLI is never a second writer on it.

The copy travels on the existing aiVault.prepareSessionResume fork request as
an optional forkedSessionId; a host that predates it answers without one, and
the client asks for an Orca update instead of resuming the original.

* fix(chat): open an OpenCode 2.x chat's CLI copy with opencode, not opencode2

The copy kept the row's agent, so Resume in New CLI ran `opencode2 --standalone --session <copy>`,
which OpenCode 2.x doesn't install. The chat's host now answers the copy with the agent whose CLI
opens it: the chat's own.

* fix(chat): type the copied conversation's agent as a history agent

* fix: preserve independent chat copies through CLI launch

* test: keep only the tests that pin user-visible behavior

* fix(chat): validate OpenCode copies on their execution host

* test(chat): expect copied OMP forks after main integration
2026-10-14 16:06:50 -07:00

46 lines
1.7 KiB
TypeScript

import type { TuiAgent } from './tui-agent'
import { getAgentLaunchExecutableName } from './agent-command-line-intent'
import { findOptionOccurrence } from './command-option-occurrence'
export class OpenCodeCopyServerRouteError extends Error {
constructor() {
super(
'Remove --server from the OpenCode command or arguments in Settings to open a copy in a CLI.'
)
}
}
/** A configured server may not hold the copy saved by this execution host. */
export function assertOpenCodeCopyUsesLocalStore(args: readonly string[]): void {
if (findOptionOccurrence(args, ['--server'], true)) {
throw new OpenCodeCopyServerRouteError()
}
}
function openCodeCommandAgent(launchCommand: string | undefined): 'opencode' | 'opencode2' | null {
const binary = getAgentLaunchExecutableName(launchCommand)
return binary === 'opencode' || binary === 'opencode2' ? binary : null
}
export function isOpenCode2LaunchCommand(launchCommand: string | undefined): boolean {
return openCodeCommandAgent(launchCommand) === 'opencode2'
}
export function selectOpenCodeHookAgent(
launchAgent: TuiAgent | undefined,
launchCommand: string | undefined,
isEnabled: (agent: 'opencode' | 'opencode2') => boolean
): 'opencode' | 'opencode2' | null {
const explicit =
launchAgent === 'opencode' || launchAgent === 'opencode2'
? launchAgent
: launchAgent === undefined
? openCodeCommandAgent(launchCommand)
: null
// An explicit version selects its own integration; disabling it never substitutes the other.
if (explicit) {
return isEnabled(explicit) ? explicit : null
}
return isEnabled('opencode') ? 'opencode' : isEnabled('opencode2') ? 'opencode2' : null
}