const { Space, BodyAdapter, Brain } = require("@srk0102/plexa")
const { OllamaBrain } = require("@srk0102/plexa/bridges/ollama")
// -- Cartpole physics omitted; same as the cart-pole example --
class CartpoleBody extends BodyAdapter {
static bodyName = "cartpole"
static tools = {
apply_force: {
description: "push the cart",
parameters: {
direction: { type: "string", enum: ["left", "right"], required: true },
magnitude: { type: "number", min: 0, max: 1, required: true },
},
},
hold: { description: "no-op", parameters: {} },
}
constructor() { super(); this.physics = new CartpolePhysics() }
async apply_force({ direction, magnitude }) { this.physics.apply(direction, magnitude); return {} }
async hold() { return {} }
async tick() {
await super.tick()
this.physics.step()
this.setState({ pole_angle: this.physics.theta })
if (Math.abs(this.physics.theta) > 0.7) {
this.emit("pole_critical", { angle: this.physics.theta }, "CRITICAL")
}
}
}
class LightBody extends BodyAdapter {
static bodyName = "light"
static tools = {
turn_on: { description: "turn on", parameters: {} },
turn_off: { description: "turn off", parameters: {} },
toggle: { description: "toggle", parameters: {} },
}
constructor() { super(); this.on = false; this.alarmCount = 0 }
async turn_on() { this.on = true; return { on: true } }
async turn_off() { this.on = false; return { on: false } }
async toggle() { this.on = !this.on; return { on: this.on } }
// Lateral event from the cartpole when it goes critical.
async onPeerEvent(from, type, payload) {
if (type === "pole_critical") {
this.alarmCount++
this.on = true
console.log(`[light] alarm from ${from}, lit (count=${this.alarmCount})`)
}
}
}
class StubBrain extends Brain {
constructor() { super({ model: "stub" }); this._i = 0 }
async _rawCall() {
this._i++
if (this._i % 3 === 0) {
return JSON.stringify({ target_body: "light", tool: "toggle", parameters: {} })
}
return JSON.stringify({
target_body: "cartpole",
tool: "apply_force",
parameters: { direction: this._i % 2 === 0 ? "left" : "right", magnitude: 0.4 },
})
}
}
async function main() {
const space = new Space("two_bodies", { tickHz: 60, brainIntervalMs: 1500 })
const cart = new CartpoleBody()
const light = new LightBody()
space.addBody(cart)
space.addBody(light)
// Lateral link: pole_critical from cartpole goes directly to light.onPeerEvent.
space.link("cartpole", "light", ["pole_critical"])
const ollamaUp = await OllamaBrain.isAvailable()
space.setBrain(ollamaUp ? new OllamaBrain({ model: "llama3.2", maxTokens: 120 }) : new StubBrain())
space.on("tool_dispatched", (e) =>
console.log(`[plexa] ${e.body}.${e.tool}(${JSON.stringify(e.parameters)})`)
)
await space.run()
console.log(`[plexa] running, brain=${ollamaUp ? "ollama" : "stub"}`)
}
main()