mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
fix(windows): engage the legacy fallback when the host THROWS a refusal
Review found a P1 this merge composes: neither parent could reach it. At the
lane head the only structured entry was launch-agent-in-new-tab (full
store-backed WSL check); on main all win32 was refused. The merge enables
win32 in creation flows that pass no projectRuntime, so a WSL folder
workspace, a WSL-configured repo, or a repair-required runtime now routes
structured -- and the host refuses correctly, but by THROWING rather than
returning {ok:false, refusal}.
Callers engage their legacy-terminal fallback on the refusal CLASS, so an
unmapped throw arrives as a generic RPC rejection: no fallback, empty
workspace, error toast, prompt stranded in the launch outbox. Pre-merge the
same action opened a legacy terminal agent.
Map the host's thrown definitive refusals onto the refusal class at the
launch boundary, so every creation flow -- present and future -- degrades to
the legacy terminal instead of stranding. Narrow predicate: unrelated
failures (ECONNRESET, empty message, non-Error) still propagate untouched.
Ablation-proven: removing the mapping reddens the fallback test.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { isThrownStructuredRefusal } from './launch-structured-codex-session'
|
||||
|
||||
describe('isThrownStructuredRefusal', () => {
|
||||
it('recognises the unsupported-location sentinel the host throws', () => {
|
||||
expect(isThrownStructuredRefusal(new Error('structured_agent_session_unsupported'))).toBe(true)
|
||||
})
|
||||
|
||||
it('recognises it when the transport prefixes the message', () => {
|
||||
expect(
|
||||
isThrownStructuredRefusal(new Error('rpc error: structured_agent_session_unsupported'))
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('recognises a runtime that needs repair', () => {
|
||||
expect(
|
||||
isThrownStructuredRefusal(new Error('Project runtime requires repair: wsl-distro-missing'))
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('does NOT swallow an unrelated failure', () => {
|
||||
expect(isThrownStructuredRefusal(new Error('ECONNRESET'))).toBe(false)
|
||||
expect(isThrownStructuredRefusal(Object.assign(new Error('x'), { message: '' }))).toBe(false)
|
||||
expect(isThrownStructuredRefusal('not an error')).toBe(false)
|
||||
expect(isThrownStructuredRefusal(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
vi.mock('@/runtime/structured-agent-session-client', () => ({
|
||||
callStructuredAgentSession: vi.fn(async () => {
|
||||
throw new Error('structured_agent_session_unsupported')
|
||||
})
|
||||
}))
|
||||
|
||||
describe('launchStructuredCodexSession', () => {
|
||||
// Without the mapping the host's throw arrives as a generic RPC rejection and
|
||||
// every caller's legacy-terminal fallback (which keys on the refusal class)
|
||||
// silently fails to engage, stranding the launch.
|
||||
it('converts a thrown host refusal into the refusal class callers key on', async () => {
|
||||
const { launchStructuredCodexSession, StructuredAgentSessionCreateRefusalError } =
|
||||
await import('./launch-structured-codex-session')
|
||||
await expect(
|
||||
launchStructuredCodexSession({
|
||||
sessionId: 's1',
|
||||
worktreeId: 'folder:f1',
|
||||
params: {} as never
|
||||
} as never)
|
||||
).rejects.toBeInstanceOf(StructuredAgentSessionCreateRefusalError)
|
||||
})
|
||||
})
|
||||
@@ -73,12 +73,38 @@ export function abandonStructuredAgentSessionLaunchIntent(
|
||||
)
|
||||
}
|
||||
|
||||
/** The host refuses some launches by THROWING rather than returning a refusal
|
||||
* envelope -- an unsupported location, or a runtime that needs repair. Those
|
||||
* are still definitive refusals, but callers engage their legacy-terminal
|
||||
* fallback on the refusal class alone, so an unmapped throw strands the launch
|
||||
* with no agent pane and the prompt left in the outbox. */
|
||||
const THROWN_REFUSAL_MARKERS = [
|
||||
'structured_agent_session_unsupported',
|
||||
'Project runtime requires repair'
|
||||
] as const
|
||||
|
||||
export function isThrownStructuredRefusal(error: unknown): boolean {
|
||||
const message = error instanceof Error ? error.message : ''
|
||||
return THROWN_REFUSAL_MARKERS.some((marker) => message.includes(marker))
|
||||
}
|
||||
|
||||
export async function launchStructuredCodexSession(
|
||||
intent: StructuredAgentSessionLaunchIntent
|
||||
): Promise<Pick<AgentSessionAttachResult, 'sessionId' | 'fence'>> {
|
||||
const result = await callStructuredAgentSession<
|
||||
AgentSessionMutationResult<AgentSessionAttachResult>
|
||||
>({ kind: 'local' }, 'agentSession.create', intent.params)
|
||||
let result: AgentSessionMutationResult<AgentSessionAttachResult>
|
||||
try {
|
||||
result = await callStructuredAgentSession<
|
||||
AgentSessionMutationResult<AgentSessionAttachResult>
|
||||
>({ kind: 'local' }, 'agentSession.create', intent.params)
|
||||
} catch (error) {
|
||||
if (!isThrownStructuredRefusal(error)) {
|
||||
throw error
|
||||
}
|
||||
abandonStructuredAgentSessionLaunchIntent(intent)
|
||||
throw new StructuredAgentSessionCreateRefusalError(
|
||||
error instanceof Error ? error.message : 'structured_agent_session_unsupported'
|
||||
)
|
||||
}
|
||||
if (!result.ok) {
|
||||
abandonStructuredAgentSessionLaunchIntent(intent)
|
||||
throw new StructuredAgentSessionCreateRefusalError(result.refusal.message)
|
||||
|
||||
Reference in New Issue
Block a user