fix(minecraft): persist prevRun returnRaw for typed value reuse
This commit is contained in:
@@ -84,6 +84,23 @@ describe('javaScriptPlanner', () => {
|
||||
expect(planned.actions.map(a => a.action)).toEqual([{ tool: 'chat', params: { message: 'count=2' } }])
|
||||
})
|
||||
|
||||
it('persists typed previous return via prevRun.returnRaw', async () => {
|
||||
const planner = new JavaScriptPlanner()
|
||||
const executeAction = vi.fn(async action => `ok:${action.tool}`)
|
||||
|
||||
await planner.evaluate(`
|
||||
const inv = [{ name: "oak_log", count: 2 }]
|
||||
return inv
|
||||
`, actions, globals, executeAction)
|
||||
|
||||
const planned = await planner.evaluate(`
|
||||
const inv = prevRun.returnRaw
|
||||
await chat(inv.map(item => item.count + " " + item.name).join(", "))
|
||||
`, actions, globals, executeAction)
|
||||
|
||||
expect(planned.actions.map(a => a.action)).toEqual([{ tool: 'chat', params: { message: '2 oak_log' } }])
|
||||
})
|
||||
|
||||
it('provides snapshot globals in script scope', async () => {
|
||||
const planner = new JavaScriptPlanner()
|
||||
const executeAction = vi.fn(async action => `ok:${action.tool}`)
|
||||
|
||||
@@ -152,6 +152,7 @@ export class JavaScriptPlanner {
|
||||
|
||||
if (isRecord(this.sandbox.lastRun)) {
|
||||
this.sandbox.lastRun.returnValue = returnValue
|
||||
this.sandbox.lastRun.returnRaw = result
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -282,7 +283,7 @@ export class JavaScriptPlanner {
|
||||
})
|
||||
this.defineGlobalTool('log', (...args: unknown[]) => {
|
||||
if (!this.activeRun)
|
||||
throw new Error('log() is only allowed during planner evaluation')
|
||||
throw new Error('log() is only allowed during REPL evaluation')
|
||||
|
||||
const rendered = args.map(arg => inspect(arg, { depth: 4, breakLength: 120 })).join(' ')
|
||||
this.activeRun.logs.push(rendered)
|
||||
@@ -415,6 +416,7 @@ export class JavaScriptPlanner {
|
||||
this.sandbox.lastRun = {
|
||||
actions: run.executed,
|
||||
logs: run.logs,
|
||||
returnRaw: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,7 +448,7 @@ export class JavaScriptPlanner {
|
||||
|
||||
private async runAction(tool: string, params: Record<string, unknown>): Promise<ActionRuntimeResult> {
|
||||
if (!this.activeRun) {
|
||||
throw new Error('Tool calls are only allowed during planner evaluation')
|
||||
throw new Error('Tool calls are only allowed during REPL evaluation')
|
||||
}
|
||||
|
||||
if (this.activeRun.sawSkip && tool !== 'skip') {
|
||||
@@ -513,7 +515,7 @@ export class JavaScriptPlanner {
|
||||
|
||||
private validateAction(tool: string, params: Record<string, unknown>): ValidationResult {
|
||||
if (!this.activeRun)
|
||||
throw new Error('Tool calls are only allowed during planner evaluation')
|
||||
throw new Error('Tool calls are only allowed during REPL evaluation')
|
||||
|
||||
const action = this.activeRun.actionsByName.get(tool)
|
||||
if (!action)
|
||||
|
||||
@@ -18,6 +18,7 @@ You are an autonomous agent playing Minecraft.
|
||||
- Use `await` on tool calls when later logic depends on the result.
|
||||
- Globals refreshed every turn: `snapshot`, `self`, `environment`, `social`, `threat`, `attention`, `autonomy`, `event`, `now`, `query`, `bot`, `mineflayer`, `currentInput`, `llmLog`.
|
||||
- Persistent globals: `mem` (cross-turn memory), `lastRun` (this run), `prevRun` (previous run), `lastAction` (latest action result), `log(...)`.
|
||||
- Cross-turn result access: use `prevRun.returnRaw` for typed values (arrays/objects); `prevRun.returnValue` is stringified for display/logging.
|
||||
- `forget_conversation()` clears conversation memory (`conversationHistory` and `lastLlmInputSnapshot`) for prompt/debug reset workflows.
|
||||
- Last script outcome is also echoed in the next turn as `[SCRIPT]` context (return value, action stats, and logs).
|
||||
- Maximum actions per turn: 5. If you need more, break down your task to perform in multiple turns.
|
||||
@@ -61,15 +62,20 @@ Core query entrypoints:
|
||||
|
||||
Composable patterns:
|
||||
- `const ores = query.blocks().within(24).isOre().names().uniq().list()`
|
||||
- `const me = query.self(); return me`
|
||||
- `const snap = query.snapshot(20); return snap.inventory.summary`
|
||||
- `const me = query.self(); me`
|
||||
- `const snap = query.snapshot(20); snap.inventory.summary`
|
||||
- `const nearestLog = query.blocks().whereName(["oak_log", "birch_log"]).first()`
|
||||
- `const nearbyPlayers = query.entities().whereType("player").within(32).list()`
|
||||
- `const inv = query.inventory().countByName(); const hasFood = (inv.bread ?? 0) > 0`
|
||||
- `const hasPickaxe = query.inventory().has("stone_pickaxe", 1)`
|
||||
- `const invSummary = query.inventory().summary(); return invSummary`
|
||||
- `const invSummary = query.inventory().summary(); invSummary`
|
||||
- `const invLine = query.inventory().summary().map(({ name, count }) => `${count} ${name}`).join(", "); invLine`
|
||||
- `const craftableTools = query.craftable().whereIncludes("pickaxe").uniq().list()`
|
||||
|
||||
Inventory summary shape reminder:
|
||||
- `query.inventory().summary()` returns an **array** of `{ name, count }`.
|
||||
- Do **not** use `Object.entries(summary)` for inventory summary formatting.
|
||||
|
||||
Callable-only reminder (strict):
|
||||
- Query helpers that are functions must be called with `()`.
|
||||
- Never return function references as values (invalid): `query.inventory().summary`
|
||||
@@ -100,16 +106,22 @@ Silent-eval pattern (strongly encouraged):
|
||||
- Turn A: `let blocksToMine = someFunc(); blocksToMine`
|
||||
- Turn B: inspect `[SCRIPT]` return / `llmLog`, then act: `await collectBlocks({ type: ..., num: ... })`
|
||||
- Prefer this when a wrong action would be costly, dangerous, or hard to undo.
|
||||
- A `no_actions` follow-up after an eval-only turn is normal; treat it as the handoff turn for action/reporting.
|
||||
|
||||
Value-first rule (mandatory for read -> action flows):
|
||||
- If a request depends on observed world/query data, first run an evaluation-only turn and `return` the concrete value.
|
||||
- If a request depends on observed world/query data, first run an evaluation-only turn and end with the concrete value expression.
|
||||
- Do not call world/chat tools in that first turn.
|
||||
- End eval turns with a concrete final expression (for example `inv`, `target`, `summary`) so `[SCRIPT]` captures it.
|
||||
- In the next turn, use `[SCRIPT] Last eval return=...` as the source of truth for tool parameters/messages.
|
||||
- Do not re-query the same read value in the follow-up turn; use the persisted value to avoid TOCTOU drift.
|
||||
- Avoid acting on unresolved intermediate variables when a concrete returned value can be verified first.
|
||||
- For explicit user tasks (e.g. "get X", "craft Y", "go to Z"), do not stay in repeated evaluation-only turns.
|
||||
- After one evaluation turn, the next turn must either:
|
||||
- call at least one action/chat tool toward completion, or
|
||||
- call `giveUp({ reason, cooldown_seconds })` with a concrete blocker.
|
||||
- Example (read -> chat report):
|
||||
- Turn A: `const inv = query.inventory().summary(); inv`
|
||||
- Turn B: `const inv = prevRun.returnValue; const text = Array.isArray(inv) && inv.length ? inv.map(({ name, count }) => `${count} ${name}`).join(", ") : "nothing"; await chat({ message: `I have: ${text}`, feedback: false })`
|
||||
|
||||
# Response Format
|
||||
You must respond with JavaScript only (no markdown code fences).
|
||||
@@ -161,9 +173,9 @@ Common patterns:
|
||||
- Plan with `mem.plan`, execute in small steps, and verify each step before continuing.
|
||||
- Prefer deterministic scripts: no random branching unless needed.
|
||||
- Keep per-turn scripts short and focused on one tactical objective.
|
||||
- Prefer "evaluate then act" loops: first compute and return candidate values (no actions), then perform tools in the next turn using confirmed values.
|
||||
- Prefer "evaluate then act" loops: first compute and surface candidate values (no actions), then perform tools in the next turn using confirmed values.
|
||||
- For read->chat/report tasks, always prefer:
|
||||
- Turn A: `const value = ...; return value`
|
||||
- Turn A: `const value = ...; value`
|
||||
- Turn B: construct tool params/messages from confirmed returned value.
|
||||
- If you hit repeated failures with no progress, call `await giveUp({ reason, cooldown_seconds })` once instead of retry-spamming.
|
||||
- Treat `environment.nearbyPlayersGaze` as a weak hint, not a command. Never move solely because someone looked somewhere unless they also gave a clear instruction.
|
||||
|
||||
Reference in New Issue
Block a user