mirror of
https://github.com/stablyai/orca.git
synced 2026-09-26 08:02:38 +00:00
refactor(mobile): parse the launch outcome into a named type at its boundary
anti-slop/no-object-parameters flagged terminalLaunchWarning's `result: object`. The rule is pointing at a real seam rather than a style nit: the helper advertised a loose object and did the narrowing inside itself, so every caller handed it unparsed wire data and nothing downstream held a real type. Parsed at the boundary instead. parseTerminalLaunchOutcome takes `unknown` and returns TerminalLaunchOutcome | null, so the narrowing happens once, where the untrusted payload enters, and the consumer works with a named type. The type is taken from the shared contract rather than restated - a Pick over the terminal member of AgentLaunchOutcome - so a change to that union fails here instead of flowing through. `handle` is deliberately excluded: nothing reads it, and requiring it would drop the warning off a reply that omitted one, which is a behaviour change smuggled in under a typing change. No assertion and no config exemption: reintroducing `as Partial<AgentLaunchResult>` would trade this finding for the defect removed earlier in this branch, and the rule is correct here. The rule arrived with the merge-forward (#20781, newer than this branch's merge-base), and anti-slop is not one of the changed-code gate's six scans - it runs only repo-wide - which is why a clean local gate did not predict it. Behaviour is unchanged across all five warning cases, and the positive case was re-ablated on the new parser: dropping the warning reddens exactly it, 1 failed | 18 passed, restored byte-identical to 19 passed.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
import {
|
||||
withoutReservedAgentCreateFields,
|
||||
type AgentLaunchOutcome,
|
||||
type AgentLaunchResult
|
||||
} from '../../../src/shared/agent-launch-intent'
|
||||
import type { TuiAgent } from '../../../src/shared/tui-agent'
|
||||
@@ -59,27 +60,46 @@ export function readAgentLaunchCreateOutcome(result: unknown): AgentLaunchCreate
|
||||
if (typeof worktreeId !== 'string' || !worktreeId.trim()) {
|
||||
return null
|
||||
}
|
||||
const warning = terminalLaunchWarning(result)
|
||||
const warning = parseTerminalLaunchOutcome(
|
||||
'outcome' in result ? result.outcome : undefined
|
||||
)?.warning?.trim()
|
||||
return { worktreeId, ...(warning ? { warning } : {}) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The startup failure a terminal launch reports: the workspace exists, the agent did not start
|
||||
* (pty exhaustion). Dropping it is what lands the phone on an unexplained empty session.
|
||||
* The terminal outcome, narrowed to the fields this reader consumes. Taken from the shared union
|
||||
* rather than restated, so a change to the contract fails here instead of flowing through.
|
||||
*
|
||||
* Narrowed rather than asserted, because the payload is whatever the host sent — a reader that
|
||||
* claims the contract's shape without checking it is how a malformed reply reaches the UI as a
|
||||
* TypeError instead of a message.
|
||||
* `handle` is deliberately not required: nothing here reads it, and demanding it would drop the
|
||||
* warning off a reply that omitted it — a behaviour change smuggled in under a typing change.
|
||||
*/
|
||||
function terminalLaunchWarning(result: object): string {
|
||||
if (!('outcome' in result) || !result.outcome || typeof result.outcome !== 'object') {
|
||||
return ''
|
||||
type TerminalLaunchOutcome = Pick<
|
||||
Extract<AgentLaunchOutcome, { kind: 'terminal' }>,
|
||||
'kind' | 'warning'
|
||||
>
|
||||
|
||||
/**
|
||||
* Parses the launch outcome, which arrives as whatever the host sent.
|
||||
*
|
||||
* A terminal launch reports its startup failure here: the workspace exists, the agent did not
|
||||
* start (pty exhaustion). Dropping it is what lands the phone on an unexplained empty session.
|
||||
*
|
||||
* Parsed into a named type at this boundary rather than read off a loose `object`, and narrowed
|
||||
* rather than asserted — a reader that claims the contract's shape without checking it is how a
|
||||
* malformed reply reaches the UI as a TypeError instead of a message.
|
||||
*/
|
||||
function parseTerminalLaunchOutcome(outcome: unknown): TerminalLaunchOutcome | null {
|
||||
if (
|
||||
!outcome ||
|
||||
typeof outcome !== 'object' ||
|
||||
!('kind' in outcome) ||
|
||||
outcome.kind !== 'terminal'
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const outcome = result.outcome
|
||||
if (!('kind' in outcome) || outcome.kind !== 'terminal') {
|
||||
return ''
|
||||
}
|
||||
return 'warning' in outcome && typeof outcome.warning === 'string' ? outcome.warning.trim() : ''
|
||||
const warning =
|
||||
'warning' in outcome && typeof outcome.warning === 'string' ? outcome.warning : undefined
|
||||
return { kind: 'terminal', ...(warning === undefined ? {} : { warning }) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user