ProviderService.sendTurn appends its own blocks to the prompt string after the user's text and after the <t3_context> envelope: an [Attached <type> "<name>" is saved at: <path>] line per attachment, and, for a captured window, an Untrusted captured-window data follows as JSON block carrying the window's accessibility text (apps/server/src/provider/Layers/ProviderService.ts, the two loops around the appendAttachmentContext helper).
Every adapter that rewrites $name mentions then scans that combined string, so provider-added text is treated as if the user had typed it. Two consequences, both raised by Codex on #39:
- Untrusted window text can invoke a skill. A captured window whose accessibility text contains
$<installed-skill-name> gets rewritten into a real invocation. The block is explicitly labelled untrusted data, and the user never picked that skill. On OhMyPi this also suppresses the runtime-instructions block, since the prompt is treated as consumed.
- Native commands do not travel bare. OhMyPi strips the in-place
[Kind: label; ref=ctx_1] markers before sending a builtin command, because omp folds extra text into the command's arguments and strict commands such as /computer status print their usage instead of running. The [Attached ...] line is not one of those markers and survives, and image attachments are additionally sent as ACP content blocks.
This is not specific to OhMyPi. Claude and Cursor rewrite mentions over the same combined string today, so the injection path in (1) is cross-provider. It was left out of #39 rather than widening that PR.
The fix belongs where the prompt is assembled: give adapters the user-authored text separately from what T3 appended, so mention rewriting and command detection only ever see what the user wrote. Attachment context would then be reattached after dispatch, and an adapter that must send a command bare can drop it.
Acceptance criteria
ProviderService.sendTurnappends its own blocks to the prompt string after the user's text and after the<t3_context>envelope: an[Attached <type> "<name>" is saved at: <path>]line per attachment, and, for a captured window, anUntrusted captured-window data follows as JSONblock carrying the window's accessibility text (apps/server/src/provider/Layers/ProviderService.ts, the two loops around theappendAttachmentContexthelper).Every adapter that rewrites
$namementions then scans that combined string, so provider-added text is treated as if the user had typed it. Two consequences, both raised by Codex on #39:$<installed-skill-name>gets rewritten into a real invocation. The block is explicitly labelled untrusted data, and the user never picked that skill. On OhMyPi this also suppresses the runtime-instructions block, since the prompt is treated as consumed.[Kind: label; ref=ctx_1]markers before sending a builtin command, because omp folds extra text into the command's arguments and strict commands such as/computer statusprint their usage instead of running. The[Attached ...]line is not one of those markers and survives, and image attachments are additionally sent as ACP content blocks.This is not specific to OhMyPi. Claude and Cursor rewrite mentions over the same combined string today, so the injection path in (1) is cross-provider. It was left out of #39 rather than widening that PR.
The fix belongs where the prompt is assembled: give adapters the user-authored text separately from what T3 appended, so mention rewriting and command detection only ever see what the user wrote. Attachment context would then be reattached after dispatch, and an adapter that must send a command bare can drop it.
Acceptance criteria
$nameinside captured-window accessibility text does not dispatch a skill on any provider.[Attached ...]line and without image content blocks.