| 200 | }, |
| 201 | |
| 202 | async onAbort(ctx, _info: AbortInfo) { |
| 203 | const state = runState.get(ctx) |
| 204 | if (!state) return |
| 205 | |
| 206 | await state.watcher?.stop() |
| 207 | |
| 208 | // ALWAYS tear down on an explicit abort, regardless of `destroyOnComplete`. |
| 209 | // The in-sandbox agent process is not killed by closing its IO stream |
| 210 | // (e.g. a Docker exec survives client disconnect), so the only reliable way |
| 211 | // to stop it — and the token/cost drain of its ongoing API calls — is to |
| 212 | // destroy the sandbox (stop the container/VM). `keepAlive` / |
| 213 | // `destroyOnComplete:false` governs *successful completion*, never cancel. |
| 214 | await definition.destroy(state.ensureCtx) |
| 215 | await definition.hooks?.onDestroy?.() |
| 216 | }, |
| 217 | |
| 218 | async onError(ctx, info) { |
| 219 | const state = runState.get(ctx) |