## Highlights

`Foreground bash now auto-promotes long-running tasks to the background instead of killing them at an arbitrary timeout. Agents get a fast inline result for short commands and a reliable completion reminder for long ones, with no need to predict task duration up front.

Three other user-visible changes:

- **Vue (`.vue`) is now a first-class language** for `aft_outline`, `aft_zoom`, and `ast_grep_search` / `ast_grep_replace`.
- **Auto-update reliability fix** — the plugin update checker now triggers at plugin load instead of only at `session.created`, so resumed sessions and parallel OpenCode windows actually check for new versions.
- **Brace-aware grep includes** — patterns like `*.{vue,ts}` and `*.{js,jsx,ts,tsx}` no longer get split on the comma into invalid separate globs.

## Foreground-as-polled-background bash

Every `bash` call now routes through the same background infrastructure internally, so the Rust dispatch loop never blocks. Foreground execution becomes a thin polling layer on top:

- Plugin polls `bash_status` for up to ~5 seconds.
- If the task finishes inside the wait-window, it returns inline as before.
- If it doesn't, the plugin returns a "promoted to background" message and the agent gets a `taskId` it can `bash_status` / `bash_kill` against later.
- A completion reminder is delivered automatically when the task actually finishes, even if the agent has already moved on to other work.

Crucially, the **wait-window is decoupled from the task's kill cap**:

| Call shape | Wait-window | Task kill cap |
|---|---|---|
| `bash({ command })` | 5s | **30 minutes** |
| `bash({ command, timeout: 30000 })` | 5s | 30s (hard kill at `timeout`) |
| `bash({ command, timeout: 2000 })` | 2s | 2s |
| `bash({ command, background: true })` | 0 (no poll) | 30 min |
| `bash({ command, background: true, timeout: 600000 })` | 0 | 10 min |

Practical effect: a long-running e2e test launched as foreground bash with no `timeout` no longer gets killed after 30 seconds. It runs in the background up to the 30-minute default, and the completion reminder carries the actual exit code and a tail of the output. Explicit `timeout: N` still means "hard kill at N seconds" — same mental model as `timeout(1)`, Docker, and Kubernetes.

`bash_status` and `bash_kill` are now registered alongside `bash` whenever any `experimental.bash.*` flag is on, not just when `experimental.bash.background` is enabled. This way the agent always has tools to inspect or kill auto-promoted tasks, regardless of which experimental originally enabled bash hoisting.

The `timeout` schema is also tightened — agents can only pass positive integer milliseconds; `NaN`, negatives, zero, and floats are rejected at the schema level, eliminating a class of invalid-input edge cases that could hang the polling loop.

## Vue support

`tree-sitter-vue` is now wired through the parser, language detection, outline, zoom, and AST stack. Single-file components extract template, `<script setup lang="ts">`, and `<style scoped>` as top-level outline nodes. Embedded script content is opaque `raw_text` to tree-sitter-vue (a known upstream limitation), so deep symbol extraction inside the script block is not yet available — but Vue templates and component structure are now searchable and editable through AFT's structural tools.

AST patterns work too: `@click="$NAME"` and similar template patterns capture meta-variables correctly.

## Auto-update fix

The plugin's update checker previously hooked into `session.created`, which meant resumed `opencode -s` sessions and parallel windows that joined an existing project never re-checked for new versions. The checker now triggers at plugin init with a short delay, coordinates across parallel plugin instances via an on-disk dedup file under the plugin storage directory, and clears pending timers on abort.

## Brace-aware grep includes

Naive comma-splitting in the hoisted `grep` tool's `include` parameter was breaking patterns like `*.{vue,ts}` into the two invalid fragments `*.{vue` and `ts}`. The split is now brace-aware in both OpenCode and Pi adapters, so multi-extension include patterns work correctly.

## Quality

- **+62 Pi unit tests across 17 new files** (audited against actual module gaps rather than indiscriminate coverage).
- **Audit-driven safety hardening** in the bash subsystem: input validation at the schema boundary, wait-window math simplification, transport-timeout cleanup, parity fix between Pi and OpenCode `isTerminalStatus` allowlists.
- **Combined test surface**: 1,155 Rust tests, 672 OpenCode plugin tests, 450 Pi plugin tests, plus typecheck and lint clean across all four workspaces.

**Full Changelog:**
https://github.com/cortexkit/aft/compare/v0.19.6...v0.20.0
