Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
404 changes: 404 additions & 0 deletions docs/MCP_ERROR_MONITORING.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/core/World.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Entities } from './systems/Entities'
import { Physics } from './systems/Physics'
import { Stage } from './systems/Stage'
import { Scripts } from './systems/Scripts'
import { ErrorMonitor } from './systems/ErrorMonitor'

export class World extends EventEmitter {
constructor() {
Expand All @@ -35,6 +36,8 @@ export class World extends EventEmitter {
this.camera = new THREE.PerspectiveCamera(70, 0, 0.2, 1200)
this.rig.add(this.camera)

// Initialize ErrorMonitor first to capture initialization errors
this.register('errorMonitor', ErrorMonitor)
this.register('settings', Settings)
this.register('collections', Collections)
this.register('apps', Apps)
Expand Down
3 changes: 3 additions & 0 deletions src/core/createClientWorld.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import { Particles } from './systems/Particles'
import { Snaps } from './systems/Snaps'
import { Wind } from './systems/Wind'
import { XR } from './systems/XR'
import { ErrorMonitor } from './systems/ErrorMonitor'

export function createClientWorld() {
const world = new World()
world.isClient = true
world.register('client', Client)
world.register('livekit', ClientLiveKit)
world.register('pointer', ClientPointer)
Expand All @@ -45,5 +47,6 @@ export function createClientWorld() {
world.register('snaps', Snaps)
world.register('wind', Wind)
world.register('xr', XR)
world.register('errorMonitor', ErrorMonitor)
return world
}
3 changes: 3 additions & 0 deletions src/core/createServerWorld.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import { ServerNetwork } from './systems/ServerNetwork'
import { ServerLoader } from './systems/ServerLoader'
import { ServerEnvironment } from './systems/ServerEnvironment'
import { ServerMonitor } from './systems/ServerMonitor'
import { ErrorMonitor } from './systems/ErrorMonitor'

export function createServerWorld() {
const world = new World()
world.isServer = true
world.register('server', Server)
world.register('livekit', ServerLiveKit)
world.register('network', ServerNetwork)
world.register('loader', ServerLoader)
world.register('environment', ServerEnvironment)
world.register('monitor', ServerMonitor)
world.register('errorMonitor', ErrorMonitor)
return world
}
77 changes: 58 additions & 19 deletions src/core/entities/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,16 @@ export class App extends Entity {
// fetch blueprint
const blueprint = this.world.blueprints.get(this.data.blueprint)

if (blueprint.disabled) {
if (!blueprint) {
console.error(`Blueprint "${this.data.blueprint}" not found`)
// Forward to ErrorMonitor if available
if (this.world.errorMonitor) {
this.world.errorMonitor.captureError('app.blueprint.missing', [`Blueprint "${this.data.blueprint}" not found`], '')
}
crashed = true
}

if (blueprint && blueprint.disabled) {
this.unbuild()
this.blueprint = blueprint
this.building = false
Expand All @@ -74,22 +83,30 @@ export class App extends Entity {
// otherwise we can load the model and script
else {
try {
const type = blueprint.model.endsWith('vrm') ? 'avatar' : 'model'
let glb = this.world.loader.get(type, blueprint.model)
if (!glb) glb = await this.world.loader.load(type, blueprint.model)
root = glb.toNodes()
const type = blueprint && blueprint.model && blueprint.model.endsWith('vrm') ? 'avatar' : 'model'
let glb = blueprint && blueprint.model ? this.world.loader.get(type, blueprint.model) : null
if (!glb && blueprint && blueprint.model) glb = await this.world.loader.load(type, blueprint.model)
if (glb) root = glb.toNodes()
} catch (err) {
console.error(err)
// Forward to ErrorMonitor if available
if (this.world.errorMonitor) {
this.world.errorMonitor.captureError('app.model.load', [err.message], err.stack || '')
}
crashed = true
// no model, will use crash block below
}
// fetch script (if any)
if (blueprint.script) {
if (blueprint && blueprint.script) {
try {
script = this.world.loader.get('script', blueprint.script)
if (!script) script = await this.world.loader.load('script', blueprint.script)
} catch (err) {
console.error(err)
// Forward to ErrorMonitor if available
if (this.world.errorMonitor) {
this.world.errorMonitor.captureError('app.script.load', [err.message], err.stack || '')
}
crashed = true
}
}
Expand All @@ -111,13 +128,15 @@ export class App extends Entity {
// setup
this.blueprint = blueprint
this.root = root
if (!blueprint.scene) {
if (this.root && (!blueprint || !blueprint.scene)) {
this.root.position.fromArray(this.data.position)
this.root.quaternion.fromArray(this.data.quaternion)
this.root.scale.fromArray(this.data.scale)
}
// activate
this.root.activate({ world: this.world, entity: this, moving: !!this.data.mover })
if (this.root) {
this.root.activate({ world: this.world, entity: this, moving: !!this.data.mover })
}
// execute script
const runScript =
(this.mode === Modes.ACTIVE && script && !crashed) || (this.mode === Modes.MOVING && this.keepActive)
Expand All @@ -130,6 +149,10 @@ export class App extends Entity {
} catch (err) {
console.error('script crashed')
console.error(err)
// Forward to ErrorMonitor for MCP transmission
if (this.world.errorMonitor) {
this.world.errorMonitor.captureError('app.script.execution', [err.message], err.stack || '')
}
return this.crash()
}
}
Expand All @@ -138,20 +161,24 @@ export class App extends Entity {
this.world.setHot(this, true)
// and we need a list of any snap points
this.snaps = []
this.root.traverse(node => {
if (node.name === 'snap') {
this.snaps.push(node.worldPosition)
}
})
if (this.root) {
this.root.traverse(node => {
if (node.name === 'snap') {
this.snaps.push(node.worldPosition)
}
})
}
}
// if remote is moving, set up to receive network updates
this.networkPos = new LerpVector3(root.position, this.world.networkRate)
this.networkQuat = new LerpQuaternion(root.quaternion, this.world.networkRate)
this.networkSca = new LerpVector3(root.scale, this.world.networkRate)
if (root) {
this.networkPos = new LerpVector3(root.position, this.world.networkRate)
this.networkQuat = new LerpQuaternion(root.quaternion, this.world.networkRate)
this.networkSca = new LerpVector3(root.scale, this.world.networkRate)
}
// execute any events we collected while building
while (this.eventQueue.length) {
const event = this.eventQueue[0]
if (event.version > this.blueprint.version) break // ignore future versions
if (this.blueprint && event.version > this.blueprint.version) break // ignore future versions
this.eventQueue.shift()
this.emit(event.name, event.data, event.networkId)
}
Expand Down Expand Up @@ -199,6 +226,10 @@ export class App extends Entity {
} catch (err) {
console.error('script fixedUpdate crashed', this)
console.error(err)
// CRITICAL FIX: Forward to ErrorMonitor for real-time MCP transmission
if (this.world.errorMonitor) {
this.world.errorMonitor.captureError('app.script.fixedUpdate', [err.message], err.stack || '')
}
this.crash()
return
}
Expand All @@ -219,6 +250,10 @@ export class App extends Entity {
} catch (err) {
console.error('script update() crashed', this)
console.error(err)
// CRITICAL FIX: Forward to ErrorMonitor for real-time MCP transmission
if (this.world.errorMonitor) {
this.world.errorMonitor.captureError('app.script.update', [err.message], err.stack || '')
}
this.crash()
return
}
Expand All @@ -232,6 +267,10 @@ export class App extends Entity {
} catch (err) {
console.error('script lateUpdate() crashed', this)
console.error(err)
// CRITICAL FIX: Forward to ErrorMonitor for real-time MCP transmission
if (this.world.errorMonitor) {
this.world.errorMonitor.captureError('app.script.lateUpdate', [err.message], err.stack || '')
}
this.crash()
return
}
Expand Down Expand Up @@ -360,7 +399,7 @@ export class App extends Entity {
}

onEvent(version, name, data, networkId) {
if (this.building || version > this.blueprint.version) {
if (this.building || (this.blueprint && version > this.blueprint.version)) {
this.eventQueue.push({ version, name, data, networkId })
} else {
this.emit(name, data, networkId)
Expand Down Expand Up @@ -406,7 +445,7 @@ export class App extends Entity {
getNodes() {
// note: this is currently just used in the nodes tab in the app inspector
// to get a clean hierarchy
if (!this.blueprint) return
if (!this.blueprint || !this.blueprint.model) return
const type = this.blueprint.model.endsWith('vrm') ? 'avatar' : 'model'
let glb = this.world.loader.get(type, this.blueprint.model)
if (!glb) return
Expand Down
6 changes: 6 additions & 0 deletions src/core/packets.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ const names = [
'kick',
'ping',
'pong',
'errorReport',
'getErrors',
'clearErrors',
'errors',
'mcpSubscribeErrors',
'mcpErrorEvent',
]

const byName = {}
Expand Down
Loading