fix(minecraft): auth failure misleading err close #2071

This commit is contained in:
Rin
2026-08-05 18:19:36 +08:00
parent 20d6158144
commit da139435da
6 changed files with 79 additions and 29 deletions
+6
View File
@@ -29,6 +29,12 @@ BOT_PORT=''
# BOT_AUTH='microsoft'
BOT_VERSION=''
# AIRI desktop server channel
# Copy the Auth Token from AIRI desktop under Settings > Connection.
AIRI_WS_BASEURL='ws://localhost:6121/ws'
AIRI_WS_TOKEN=''
AIRI_CLIENT_NAME='minecraft-bot'
# ==============================================================================
# Debug and Development Tools
# ==============================================================================
+15 -3
View File
@@ -30,9 +30,21 @@ Treat this service as a local-development and trusted-server tool only.
cp integrations/minecraft/.env integrations/minecraft/.env.local
```
3. Edit `integrations/minecraft/.env.local`.
3. Open **Settings > Connection** in the AIRI desktop app.
4. Start the service:
4. Copy the **Auth Token**.
5. Edit `integrations/minecraft/.env.local` and set the token:
```dotenv
AIRI_WS_TOKEN='paste-the-desktop-auth-token-here'
```
The desktop app generates this token by default. Keep the token private.
The default `AIRI_WS_BASEURL` is `ws://localhost:6121/ws`. Change itwhen the desktop server uses another address.
6. Start the service:
```bash
pnpm -F @proj-airi/minecraft-bot dev
@@ -44,7 +56,7 @@ Treat this service as a local-development and trusted-server tool only.
pnpm dev
```
5. The bot should automatically connect to both AIRI and the Minecraft server.
7. The bot should automatically connect to both AIRI and the Minecraft server.
## Cognitive Architecture
+1
View File
@@ -16,6 +16,7 @@
"@modelcontextprotocol/sdk": "catalog:",
"@moeru/std": "catalog:",
"@proj-airi/server-sdk": "workspace:*",
"@proj-airi/server-shared": "workspace:*",
"@xsai/generate-text": "catalog:",
"@xsai/shared-chat": "catalog:",
"alien-signals": "catalog:",
@@ -1,4 +1,5 @@
import { errorMessageFrom } from '@moeru/std'
import { parseServerErrorMessage } from '@proj-airi/server-shared'
interface AiriClientLike {
connect: () => Promise<void>
@@ -10,49 +11,75 @@ interface LoggerLike {
withFields: (fields: Record<string, unknown>) => LoggerLike
}
type ReportedFailureKind = 'authentication-retrying' | 'authentication-terminal' | 'connection'
export function startAiriClientConnection(client: AiriClientLike, deps: {
logger: LoggerLike
url: string
}) {
let unavailableReported = false
let connectionInterrupted = false
let reportedFailureKind: ReportedFailureKind | undefined
const reportUnavailable = (error: unknown) => {
if (unavailableReported)
const reportError = (error: unknown) => {
const errorMessage = errorMessageFrom(error) ?? 'Unknown error'
const serverError = parseServerErrorMessage(errorMessage)
let failureKind: ReportedFailureKind = 'connection'
if (serverError.authentication) {
failureKind = serverError.terminal ? 'authentication-terminal' : 'authentication-retrying'
}
if (reportedFailureKind === failureKind)
return
unavailableReported = true
connectionInterrupted = true
reportedFailureKind = failureKind
if (serverError.authentication) {
deps.logger.withFields({
url: deps.url,
error: errorMessage,
retrying: !serverError.terminal,
}).warn(
serverError.terminal
? 'AIRI server authentication failed. Check AIRI_WS_TOKEN before restarting the service'
: 'AIRI server authentication failed. Set AIRI_WS_TOKEN to the desktop server Auth Token. The client will retry in background',
)
return
}
deps.logger.withFields({
url: deps.url,
error: errorMessageFrom(error) ?? 'Unknown error',
}).warn('AIRI server is unavailable; continuing startup without AIRI and retrying in background')
error: errorMessage,
retrying: true,
}).warn('AIRI server is unavailable. The service will continue startup and retry in background')
}
const reportDisconnected = () => {
connectionInterrupted = true
reportedFailureKind = 'connection'
deps.logger.withFields({
url: deps.url,
}).warn('AIRI server connection closed; retrying in background')
retrying: true,
}).warn('AIRI server connection closed. The client will retry in background')
}
void client.connect()
.then(() => {
deps.logger.withFields({
url: deps.url,
}).log(
unavailableReported
? 'Connected to AIRI server after background retry'
: 'Connected to AIRI server',
)
unavailableReported = false
})
.catch((error) => {
deps.logger.withFields({
url: deps.url,
error: errorMessageFrom(error) ?? 'Unknown error',
}).warn('AIRI client stopped retrying')
})
const reportConnected = () => {
deps.logger.withFields({
url: deps.url,
}).log(
connectionInterrupted
? 'Connected to AIRI server after background retry'
: 'Connected to AIRI server',
)
connectionInterrupted = false
reportedFailureKind = undefined
}
void client.connect().catch(reportError)
return {
reportUnavailable,
reportConnected,
reportDisconnected,
reportError,
}
}
+2 -1
View File
@@ -86,8 +86,9 @@ async function main() {
// the connection recovers immediately once the loop frees up.
// Root cause (the periodic event-loop stall itself) is tracked separately.
heartbeat: { readTimeout: 120_000, pingInterval: 20_000 },
onError: error => airiConnectionLifecycle?.reportUnavailable(error),
onError: error => airiConnectionLifecycle?.reportError(error),
onClose: () => airiConnectionLifecycle?.reportDisconnected(),
onReady: () => airiConnectionLifecycle?.reportConnected(),
})
airiConnectionLifecycle = startAiriClientConnection(airiClient, {
logger,
+3
View File
@@ -3100,6 +3100,9 @@ importers:
'@proj-airi/server-sdk':
specifier: workspace:*
version: link:../../packages/server-sdk
'@proj-airi/server-shared':
specifier: workspace:*
version: link:../../packages/server-shared
'@xsai/generate-text':
specifier: 'catalog:'
version: 0.5.0-beta.2(patch_hash=306bfb723913596b334140f0d6fa48063f336e3b44024efc1d72bf60d54b15e6)