`void resp.body?.cancel()` in streamChatEvents' finally floated a
rejected promise: an aborted fetch errors the body with the abort
reason, and cancel() on an errored stream rejects with it, escaping
as unhandledRejection -> uncaughtException and killing pi on Escape.
Await the cancel inside the existing try/catch so the rejection is
actually swallowed. Adds a regression test that aborts a mocked
in-flight stream and asserts no unhandled rejection is raised.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pi 0.86.0 hands custom providers a TranscriptContext: `systemPrompt` and
`tools` are gone, and tool declarations live in the transcript's system
messages. Reading `context.tools` therefore sent zero tools to Devin, so
every Devin model answered that it cannot run commands, and the system
prompt was dropped with it.
Collapse the system messages first, then take the prompt and the current
tool set from the replayed transcript.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With ide=windsurf, GetChatMessage rejects every GPT-5.6 variant with
'This model is only in Devin Local.' Switching the client identity to
devin-desktop makes the server serve them; response-header wire echo
confirms the exact model (gpt-5-6-sol-high -> 'GPT-5.6 Sol High Thinking').
Unknown UIDs still hard-error, so there is no silent fallback.