mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 17:47:40 +00:00
Start and resume Qoder through the existing execution-host selector when only the documented qoder command exists. Preserve legacy qodercli preference, explicit commands, quoting and session identity; disconnected SSH execution refuses without local fallback. Mobile history uses command-at-create only with the optional owned-create capability and an existing stable mutation identity. Reconcile authoritative execution-host inventory before retrying creation, adopt the same surviving operation, preserve WSL/incarnation metadata and restore only missing original launch metadata. Changed retry settings cannot replace original capture. Bounded evidence expires after 15 minutes; unavailable inventory or original evidence refuses recovery and leaves surviving execution untouched. Authoritative inventory proving absence preserves the existing recreation behavior; this is not a durable exactly-once ledger for completed one-shot side effects. Older hosts retain the acknowledged create-then-send path. Scope mobile launch authority to the current committed host/client generation, and recheck operation ownership after asynchronous preparation before later sends. Retire the mutation once the host acknowledges the resume; later ownership changes stop navigation without reporting a completed resume as failed or reusing a cached legacy pane. Preserve genuinely interrupted mutation identity. Preserve current-main OpenCode validation and merged Pi/Cursor behavior. Correct unchanged-main editor test fixtures to their production insertion-range and store contracts while retaining original assertions and production behavior. Credit: Neil Parker (@nwparker); Soperf and jyang for Qoder integration/history groundwork in #24614; actual Pullfrog and CodeRabbit reviews and the independent Source reviewer for the mobile retry, launch capture, evidence lifetime and connection ownership findings. Conservative positional process recognition and existing folder-history matching-worktree limitations remain documented.
102 lines
3.8 KiB
TypeScript
102 lines
3.8 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import { buildAgentStartupPlan, buildAgentResumeStartupPlan } from '../../shared/tui-agent-startup'
|
|
import { buildAiVaultResumeShellCommand } from '../../shared/ai-vault-resume-command'
|
|
import { resolveQoderTerminalCommand } from './qoder-terminal-command-resolution'
|
|
|
|
vi.mock('../preflight/agent-detection', () => ({ detectAgentCommandsOnHost: vi.fn() }))
|
|
|
|
describe('execution-host Qoder command selection', () => {
|
|
it.each(['darwin', 'linux', 'win32'] as const)(
|
|
'starts and resumes modern-only and legacy-only installs on %s',
|
|
async (platform) => {
|
|
const startup = buildAgentStartupPlan({
|
|
agent: 'qoder',
|
|
prompt: 'qodercli is a prompt word',
|
|
cmdOverrides: {},
|
|
platform
|
|
})
|
|
const resume = buildAgentResumeStartupPlan({
|
|
agent: 'qoder',
|
|
providerSession: { key: 'session_id', id: 'existing-session' },
|
|
cmdOverrides: {},
|
|
platform
|
|
})
|
|
if (!startup || !resume) {
|
|
throw new Error('Missing Qoder plans')
|
|
}
|
|
const shell = platform === 'win32' ? 'powershell' : 'posix'
|
|
for (const found of [
|
|
new Set(['qoder']),
|
|
new Set(['qodercli']),
|
|
new Set(['qoder', 'qodercli'])
|
|
]) {
|
|
const selected = found.has('qodercli') ? 'qodercli' : 'qoder'
|
|
const detect = vi.fn(async () => found)
|
|
for (const plan of [startup, resume]) {
|
|
const result = await resolveQoderTerminalCommand(
|
|
{ launchAgent: 'qoder', command: plan.launchCommand, launchConfig: plan.launchConfig },
|
|
{ shell },
|
|
detect
|
|
)
|
|
expect(result.command).toBe(plan.launchCommand.replace(/^qodercli/, selected))
|
|
expect(result.launchConfig?.agentCommand).toBe(selected)
|
|
}
|
|
const historyCommand = buildAiVaultResumeShellCommand({
|
|
resumeCommand: resume.launchCommand,
|
|
cwd: '/folder workspace',
|
|
platform,
|
|
shell
|
|
})
|
|
const history = await resolveQoderTerminalCommand(
|
|
{ launchAgent: 'qoder', command: historyCommand, launchConfig: resume.launchConfig },
|
|
{ shell },
|
|
detect
|
|
)
|
|
expect(history.command).toBe(historyCommand.replace(/qodercli(?=\s)/, selected))
|
|
}
|
|
}
|
|
)
|
|
|
|
it('queries each execution host without sharing a result or falling back after contact loss', async () => {
|
|
const options = { launchAgent: 'qoder' as const, command: 'qodercli --resume existing-session' }
|
|
const detect = vi
|
|
.fn()
|
|
.mockResolvedValueOnce(new Set(['qoder']))
|
|
.mockResolvedValueOnce(new Set(['qodercli']))
|
|
.mockRejectedValueOnce(new Error('Host disconnected'))
|
|
const wsl = { shell: 'posix' as const, context: { wslDistro: 'Ubuntu' } }
|
|
const ssh = { shell: 'posix' as const, connectionId: 'ssh-host' }
|
|
expect((await resolveQoderTerminalCommand(options, wsl, detect)).command).toBe(
|
|
'qoder --resume existing-session'
|
|
)
|
|
expect((await resolveQoderTerminalCommand(options, ssh, detect)).command).toBe(options.command)
|
|
await expect(resolveQoderTerminalCommand(options, ssh, detect)).rejects.toThrow(
|
|
'Host disconnected'
|
|
)
|
|
expect(detect.mock.calls).toEqual([
|
|
[['qodercli', 'qoder'], wsl],
|
|
[['qodercli', 'qoder'], ssh],
|
|
[['qodercli', 'qoder'], ssh]
|
|
])
|
|
})
|
|
|
|
it.each([
|
|
'/custom/qodercli --resume id',
|
|
'qoder --prompt-interactive hello',
|
|
'echo qodercli',
|
|
'qodercli-other'
|
|
])('preserves explicit paths, modern commands and unrelated commands: %s', async (command) => {
|
|
const detect = vi.fn()
|
|
expect(
|
|
(
|
|
await resolveQoderTerminalCommand(
|
|
{ launchAgent: 'qoder', command },
|
|
{ shell: 'posix' },
|
|
detect
|
|
)
|
|
).command
|
|
).toBe(command)
|
|
expect(detect).not.toHaveBeenCalled()
|
|
})
|
|
})
|