embark-ai 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1042,6 +1042,7 @@ function startAgentLoop() {
1042
1042
  let prevGoal = 'idle'
1043
1043
  let lostTicks = 0 // grace period before "Lost you"
1044
1044
  let prevFollowTarget = null
1045
+ let lookTick = 0
1045
1046
  let followRefreshTicks = 0 // re-issue follow goal periodically — recovers from stuck pathfinder
1046
1047
  let stuckCheckTicks = 0
1047
1048
  let lastBotPos = null
@@ -1060,12 +1061,18 @@ function startAgentLoop() {
1060
1061
  setGoal('following', { source: 'agent_loop', reason: 'follow_auto_resume' })
1061
1062
  }
1062
1063
 
1063
- // Look at the player we're following, or the nearest one if just idling
1064
- const lookTarget = state.goal === 'following'
1065
- ? getPlayer(state.followTarget)
1066
- : getNearestPlayer()
1067
- if (lookTarget) {
1068
- try { bot.lookAt(lookTarget.entity.position.offset(0, lookTarget.entity.height, 0)) } catch {}
1064
+ // Natural look only when not busy, at reduced rate so Ember doesn't robotically stare
1065
+ lookTick++
1066
+ if (!taskBusy) {
1067
+ let lookTarget = null
1068
+ if (state.goal === 'following' && lookTick % 12 === 0) {
1069
+ lookTarget = getPlayer(state.followTarget) // glance every ~3s while following
1070
+ } else if (state.goal === 'idle' && lookTick % 20 === 0) {
1071
+ lookTarget = getNearestPlayer() // rare ambient glance every ~5s
1072
+ }
1073
+ if (lookTarget) {
1074
+ try { bot.lookAt(lookTarget.entity.position.offset(0, lookTarget.entity.height, 0)) } catch {}
1075
+ }
1069
1076
  }
1070
1077
 
1071
1078
  if (bot.entity?.isInWater && !taskBusy) {
@@ -191,8 +191,9 @@ function selectAutonomousGoal(groundedState) {
191
191
  return { action: 'craft', target: 'wooden_sword', say: 'Making a sword.' }
192
192
  }
193
193
 
194
- // 8. GATHER wood — keep stockpile up if trees are around
195
- if (hasLog && planksCount < 32 && !lowEnergy) {
194
+ // 8. GATHER wood — keep stockpile up if trees are around (skip in creative: no drops)
195
+ const isCreative = groundedState.gameMode === 'creative'
196
+ if (!isCreative && hasLog && planksCount < 32 && !lowEnergy) {
196
197
  return { action: 'gather_wood', say: pick(['Need wood.', 'Heading for that tree.']) }
197
198
  }
198
199
 
@@ -13,7 +13,8 @@ const API_KEY = process.env.FEATHERLESS_API_KEY
13
13
  const USER_AGENT = 'project-k/1.0 (https://github.com/Syrthax/project-k)'
14
14
 
15
15
  function buildPrompt(groundedState, intent, playerMessage, botName = 'Ember') {
16
- const { self, inventory, nearbyBlocks, entities, hostileMobs, droppedCount, knownLocations, anger, environment } = groundedState
16
+ const { self, gameMode, inventory, nearbyBlocks, entities, hostileMobs, droppedCount, knownLocations, anger, environment } = groundedState
17
+ const isCreative = gameMode === 'creative'
17
18
 
18
19
  const energyLabel = self.energy < 25 ? 'CRITICAL' : self.energy < 60 ? 'hurt' : 'full'
19
20
  const hungerLabel = self.hunger < 25 ? 'STARVING' : self.hunger < 60 ? 'hungry' : 'fed'
@@ -54,6 +55,7 @@ Respond ONLY with valid JSON. NEVER invent facts not in GROUNDED STATE.
54
55
  health: ${(self.hp ?? self.energy / 5).toFixed(1)}/20 hearts [${energyLabel}]
55
56
  hunger: ${(self.food ?? self.hunger / 5).toFixed(0)}/20 [${hungerLabel}]
56
57
  current goal: ${self.goal}
58
+ game mode: ${gameMode}${isCreative ? ' (creative — no item drops, gathering clears space only)' : ''}
57
59
  inventory: ${invStr}
58
60
  nearby blocks: ${blocksStr}
59
61
  visible entities: ${entStr}
@@ -79,7 +81,7 @@ intent: ${intent}
79
81
  - "follow" → come to player. Refuse if TIRED.
80
82
  - "stop" → stop everything.
81
83
  - "explore" → walk around.
82
- - "gather_wood" → chop nearest log. Need logs nearby: ${hasLogs ? '✓' : '✗ no logs visible'}.
84
+ - "gather_wood" → chop nearest log${isCreative ? ' (creative: clears space, no drops)' : ''}. Need logs nearby: ${hasLogs ? '✓' : '✗ no logs visible'}.
83
85
  - "craft_planks" → convert your logs to planks. Have logs: ${logsInInv ? '✓' : '✗ no logs in inventory'}.
84
86
  - "go_to" → navigate to known location. Add "target":"name".
85
87
  - "remember_here" → save current spot.
@@ -74,6 +74,7 @@ function buildGroundedState(bot, state, memory, anger = new Map(), perception =
74
74
  goal: state.goal,
75
75
  pos: { x: Math.floor(pos.x), y: Math.floor(pos.y), z: Math.floor(pos.z) },
76
76
  },
77
+ gameMode: bot.game?.gameMode ?? 'survival',
77
78
  inventory, // from bot.inventory.items() — real
78
79
  nearbyBlocks, // from bot.findBlock() — real
79
80
  entities, // from bot.entities — real
@@ -233,16 +233,21 @@ module.exports = function createTasks(deps) {
233
233
  const ids = logIds()
234
234
  const logBlock = bot.findBlock({ matching: ids, maxDistance: 50 })
235
235
  if (!logBlock) { safeChat('No trees in range.'); return }
236
- console.log(`[${BOT_NAME}] Chopping log at ${logBlock.position}`)
236
+ const creative = bot.game?.gameMode === 'creative'
237
+ console.log(`[${BOT_NAME}] Chopping log at ${logBlock.position}${creative ? ' (creative)' : ''}`)
237
238
  await navNear(logBlock.position.x, logBlock.position.y, logBlock.position.z, 2)
238
239
  const fresh = bot.blockAt(logBlock.position)
239
240
  if (fresh && ids.includes(fresh.type) && bot.canDigBlock(fresh)) {
240
241
  await safeDig(bot, fresh)
241
- // Step into the drop so the item entity is auto-collected
242
- await new Promise(r => setTimeout(r, 300))
243
- try { await navNear(logBlock.position.x, logBlock.position.y, logBlock.position.z, 0) } catch {}
244
- safeChat('Got wood.')
245
- rememberEvent(memory, 'gathered_wood', {})
242
+ if (!creative) {
243
+ // Survival: step into the drop so the item entity is auto-collected
244
+ await new Promise(r => setTimeout(r, 300))
245
+ try { await navNear(logBlock.position.x, logBlock.position.y, logBlock.position.z, 0) } catch {}
246
+ safeChat('Got wood.')
247
+ rememberEvent(memory, 'gathered_wood', {})
248
+ } else {
249
+ safeChat('Cleared.')
250
+ }
246
251
  }
247
252
  }
248
253
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "embark-ai",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Autonomous Minecraft agent powered by Featherless AI or Ollama",
5
5
  "keywords": [
6
6
  "minecraft",
package/tui.js CHANGED
@@ -1016,15 +1016,17 @@ async function actWorldSettings() {
1016
1016
  const choice = await modalSelect('World Settings', items)
1017
1017
  if (!choice || choice === '__cancel__') return
1018
1018
  if (choice === '__save__') {
1019
- writeServerProps(draft)
1019
+ const toSave = { ...draft }
1020
+ if (toSave.gamemode !== props.gamemode) toSave['force-gamemode'] = 'true'
1021
+ writeServerProps(toSave)
1020
1022
  await modalMessage('{green-fg}Saved to server.properties.{/}\nRestart server to apply.', 'green')
1021
1023
  return
1022
1024
  }
1023
1025
 
1024
1026
  const def = WORLD_DEFS.find(d => d.key === choice)
1025
1027
  if (def.type === 'cycle') {
1026
- const idx = def.options.indexOf(draft[def.key])
1027
- draft[def.key] = def.options[(idx + 1) % def.options.length]
1028
+ const picked = await modalSelect(def.label, def.options.map(o => ({ label: o, value: o })))
1029
+ if (picked) draft[def.key] = picked
1028
1030
  } else if (def.type === 'text') {
1029
1031
  const val = await modalPrompt(`${def.label} (current: "${draft[def.key] || 'blank'}")`, draft[def.key] || '')
1030
1032
  if (val !== null) draft[def.key] = val.trim()