## Summary
- Bump catalog `@xsai/*`, `@xsai-ext/providers`, and `xsschema` to
**0.5.0-beta.8**, and delete the three `@xsai/*` pnpm patches — the
beta.2 ones from #1602 and the beta.8 regenerations that landed on main
in `38a008500`.
- Always capture tool failures on the core-agent chat path: xsAI beta.8
marks failed tool executions with `isError: true` on `tool-result.done`,
and `llm-service.ts` maps that to AIRI's `tool-error` event via
`toAiriStreamEvent`, so the agent loop continues instead of aborting.
- Remove the `captureToolErrors` request flag (and stop forwarding it
into `streamText`).
- Rebased onto latest `main` (`b230e16b2`). Includes one follow-up fix:
steps are marked settled before the finish listener runs, and finish
listener failures still reject the stream.
### Related
- Supersedes / follows up on
[#1602](https://github.com/moeru-ai/airi/pull/1602) (`captureToolErrors`
+ xsai patches).
### Scope of capture
| Case | Covered |
| --- | --- |
| A — unknown tool | yes |
| B — invalid / unparseable arguments JSON | yes |
| C — `validate` failure | yes |
| D — `execute` throw | yes |
| `missing_name` / `missing_arguments` | no (xsai still aborts) |
| `repairToolCall` | no |
Error copy on beta.8: `Tool "<toolName>" execution failed: …` (produced
by xsAI).
## Test plan
### Automated (Vitest)
- [x] core-agent `llm-service.test.ts` — 16/16
- [x] core-agent full suite — 82/82
- [x] stage-ui `llm.test.ts` + `chat.contract.test.ts` — 44/44 (the
previous `stepsSettled` timing failure is fixed in this branch)
- [x] stage-ui full suite — 594 passed / 0 failed (one
browser-test-runner teardown error, not a test failure)
- [x] typecheck — core-agent, stage-ui, component-calling, satori-bot
pass; telegram-bot fails only at `src/utils/velin.ts`, which is
pre-existing on main and untouched by this PR
### Real-environment E2E (rebase branch, DeepSeek V4 Flash via DeepSeek
API)
Harness: `/Users/lulu/GitHub/airi-e2e/pr2164/tool-error-e2e-rebase.mjs`
— drives the built `core-agent` `streamFrom` with a deliberately failing
tool.
| Case | Result | Evidence |
| --- | --- | --- |
| D — execute throw | **pass** | `tool-error` carried `Tool
"always_fail" execution failed: boom: deterministic tool failure`; the
model answered: "The always_fail tool threw a deterministic error as
expected." |
| A — unknown tool | **pass** | The model called the unavailable
`search_the_moon_database` after being told truthfully that this tests
AIRI's error capture; runtime returned `tool-error` and the conversation
continued. |
| B — bad arguments JSON | not observed on real model | providers rarely
emit invalid `arguments`; covered by unit test |
| C — `validate` failure | pass (earlier manual run with a temporary
validate-gated tool) | — |
Evidence artifacts:
`/Users/lulu/GitHub/airi-e2e/artifacts/pr2164/tool-error-e2e-2026-08-10T16-49-15-384Z.{json,log}`
### Notes / non-goals
- Fallout-only updates for the xsai beta.8 API rename: `textStream`,
`inputTokens` / `outputTokens` / `totalTokens` in component-calling /
telegram / satori.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
> **Reworked.** This PR has been rebuilt around the neutral Context Flow
architecture, per @shinohara-rin's review. Minecraft is no longer
special-cased in the generic stage-ui runtime — desktop relay &
read-aloud are now reintroduced through a **Minecraft adapter** that
contributes into the existing generic stores.
## Stacked PRs (please review/merge in order)
This rework is split as you suggested — "first restore
`services/minecraft` to only own Minecraft semantics, then reintroduce
desktop relay/read-aloud through ... a Minecraft adapter":
1. **#1949** — generic stage-ui robustness fixes (spark:command result
guard + TTS session isolation), split out as you noted they were
separable.
2. **#1950** — `refactor(minecraft)`: restore `services/minecraft` to
neutral Minecraft semantics (removes the desktop-relay assumptions baked
into the merged #1915 — `handleActionIntent`'s
`username='主人'`/`relayedFrom`, the `master:` status hint, and the
`minecraft:speech` forwarding).
3. **this PR** — reintroduces desktop relay & read-aloud via the
Minecraft adapter.
Because #1949 and #1950 are not merged yet, their commits currently
appear in this PR's diff. Once they land I'll rebase this PR onto `main`
so the diff shrinks to just the adapter work.
## The adapter (`apps/stage-tamagotchi/src/renderer/stores/minecraft/`)
The renderer owns the entire desktop ↔ in-game-bot integration and
contributes into the **existing** generic stores — the same pattern as
`mcp-tools.ts` / `plugin-tools.ts`:
- **`relayToMinecraft` tool** →
`useLlmToolsStore.registerTools('minecraft', …)`, registered **only
while the bot is online** (a hard capability gate, replacing the old
prompt-only "don't relay when offline"). `execute()` re-checks
availability, so a relay is never acked after the bot disconnects.
- **Persona directive** →
`useLlmToolsetPromptsStore.registerToolsetPrompts('minecraft', …)`,
re-registered whenever online/master/runtime-context change so the model
gets a fresh directive each turn.
- **Read-aloud** → consumes the bot's `minecraft:speech` chat into the
stage TTS (Chinese-gated), and binds 主人 by **parsing the bot's neutral
status text** — no desktop-specific hint from the bot service.
- **Notify muting** →
`orchestratorStore.muteNotifySource('minecraft-bot')`.
## Generic, non-Minecraft additions
- **`useSystemSpeechStore`** (stage-ui): a neutral bridge so any module
can voice a one-off system line; `Stage.vue` consumes it via
independent, tracked TTS sessions cancelled on unmount /
provider-or-voice change.
- **`orchestrator.muteNotifySource(id)`**: a generic primitive so a
module suppresses only **its own** notifies — every other module/plugin
notify still reacts (this fixes the earlier P1 where all
`character`-targeted notifies were dropped).
- **`./tools/*` export** from stage-ui so app-side tool authors can
reuse the shared spark-command normalizers.
## services/minecraft
Re-adds the bot's own-chat forwarding on `minecraft:speech`, now landing
**together with** its adapter consumer so the read-aloud contract is
never half-present on `main`.
## How tested
- `pnpm -F @proj-airi/stage-ui typecheck` + `pnpm -F
@proj-airi/stage-tamagotchi typecheck` → 0 errors.
- 16 new unit tests (persona prompt builder + relay tool: availability
gate, do/stop, full-label fidelity, master parsing, read-aloud gating);
orchestrator suite 5/5.
- `eslint` → 0 problems.
## Addressed review points
- Restore non-Minecraft notifications → generic `muteNotifySource` (only
the bot's source is muted).
- Isolate / track-and-cancel one-off system TTS sessions → `Stage.vue`
`oneOffSessions`.
- Re-check bot availability before relaying → `isAvailable()` in
`execute()`.
- Read the master hint that actually exists → desktop now parses the
master from neutral status **text** (the `master:` hint is removed in
#1950).
- Defer Minecraft init until after channel config → adapter `setup()`
runs after the configured `serverChannelStore.initialize(...)`.
---------
Co-authored-by: Rin <shinohara-rin@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>