mindcraft 0.1.4-0

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.
Files changed (116) hide show
  1. package/FAQ.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +255 -0
  4. package/andy.json +6 -0
  5. package/bin/mindcraft.js +80 -0
  6. package/keys.example.json +19 -0
  7. package/main.js +80 -0
  8. package/package.json +78 -0
  9. package/patches/minecraft-data+3.97.0.patch +13 -0
  10. package/patches/mineflayer+4.33.0.patch +54 -0
  11. package/patches/mineflayer-pathfinder+2.4.5.patch +265 -0
  12. package/patches/mineflayer-pvp+1.3.2.patch +13 -0
  13. package/patches/prismarine-viewer+1.33.0.patch +13 -0
  14. package/patches/protodef+1.19.0.patch +15 -0
  15. package/profiles/andy-4-reasoning.json +14 -0
  16. package/profiles/andy-4.json +7 -0
  17. package/profiles/azure.json +19 -0
  18. package/profiles/claude.json +7 -0
  19. package/profiles/claude_thinker.json +15 -0
  20. package/profiles/deepseek.json +7 -0
  21. package/profiles/defaults/_default.json +256 -0
  22. package/profiles/defaults/assistant.json +14 -0
  23. package/profiles/defaults/creative.json +14 -0
  24. package/profiles/defaults/god_mode.json +14 -0
  25. package/profiles/defaults/survival.json +14 -0
  26. package/profiles/freeguy.json +7 -0
  27. package/profiles/gemini.json +9 -0
  28. package/profiles/gpt.json +12 -0
  29. package/profiles/grok.json +7 -0
  30. package/profiles/llama.json +10 -0
  31. package/profiles/mercury.json +9 -0
  32. package/profiles/mistral.json +5 -0
  33. package/profiles/qwen.json +17 -0
  34. package/profiles/tasks/construction_profile.json +42 -0
  35. package/profiles/tasks/cooking_profile.json +11 -0
  36. package/profiles/tasks/crafting_profile.json +71 -0
  37. package/profiles/vllm.json +10 -0
  38. package/settings.js +64 -0
  39. package/src/agent/action_manager.js +177 -0
  40. package/src/agent/agent.js +561 -0
  41. package/src/agent/coder.js +229 -0
  42. package/src/agent/commands/actions.js +504 -0
  43. package/src/agent/commands/index.js +259 -0
  44. package/src/agent/commands/queries.js +347 -0
  45. package/src/agent/connection_handler.js +96 -0
  46. package/src/agent/conversation.js +353 -0
  47. package/src/agent/history.js +122 -0
  48. package/src/agent/library/full_state.js +89 -0
  49. package/src/agent/library/index.js +23 -0
  50. package/src/agent/library/lockdown.js +32 -0
  51. package/src/agent/library/skill_library.js +93 -0
  52. package/src/agent/library/skills.js +2093 -0
  53. package/src/agent/library/world.js +431 -0
  54. package/src/agent/memory_bank.js +25 -0
  55. package/src/agent/mindserver_proxy.js +136 -0
  56. package/src/agent/modes.js +446 -0
  57. package/src/agent/npc/build_goal.js +80 -0
  58. package/src/agent/npc/construction/dirt_shelter.json +38 -0
  59. package/src/agent/npc/construction/large_house.json +230 -0
  60. package/src/agent/npc/construction/small_stone_house.json +42 -0
  61. package/src/agent/npc/construction/small_wood_house.json +42 -0
  62. package/src/agent/npc/controller.js +261 -0
  63. package/src/agent/npc/data.js +50 -0
  64. package/src/agent/npc/item_goal.js +355 -0
  65. package/src/agent/npc/utils.js +126 -0
  66. package/src/agent/self_prompter.js +146 -0
  67. package/src/agent/settings.js +7 -0
  68. package/src/agent/speak.js +150 -0
  69. package/src/agent/tasks/construction_tasks.js +1104 -0
  70. package/src/agent/tasks/cooking_tasks.js +358 -0
  71. package/src/agent/tasks/tasks.js +594 -0
  72. package/src/agent/templates/execTemplate.js +6 -0
  73. package/src/agent/templates/lintTemplate.js +10 -0
  74. package/src/agent/vision/browser_viewer.js +8 -0
  75. package/src/agent/vision/camera.js +78 -0
  76. package/src/agent/vision/vision_interpreter.js +82 -0
  77. package/src/mindcraft/index.js +28 -0
  78. package/src/mindcraft/mcserver.js +154 -0
  79. package/src/mindcraft/mindcraft.js +111 -0
  80. package/src/mindcraft/mindserver.js +328 -0
  81. package/src/mindcraft/public/index.html +1253 -0
  82. package/src/mindcraft/public/settings_spec.json +145 -0
  83. package/src/mindcraft/userconfig.js +72 -0
  84. package/src/mindcraft-py/example.py +27 -0
  85. package/src/mindcraft-py/init-mindcraft.js +24 -0
  86. package/src/mindcraft-py/mindcraft.py +99 -0
  87. package/src/models/_model_map.js +89 -0
  88. package/src/models/azure.js +32 -0
  89. package/src/models/cerebras.js +61 -0
  90. package/src/models/claude.js +87 -0
  91. package/src/models/deepseek.js +59 -0
  92. package/src/models/gemini.js +176 -0
  93. package/src/models/glhf.js +71 -0
  94. package/src/models/gpt.js +147 -0
  95. package/src/models/grok.js +82 -0
  96. package/src/models/groq.js +95 -0
  97. package/src/models/huggingface.js +86 -0
  98. package/src/models/hyperbolic.js +114 -0
  99. package/src/models/lmstudio.js +74 -0
  100. package/src/models/mercury.js +95 -0
  101. package/src/models/mistral.js +94 -0
  102. package/src/models/novita.js +71 -0
  103. package/src/models/ollama.js +115 -0
  104. package/src/models/openrouter.js +77 -0
  105. package/src/models/prompter.js +366 -0
  106. package/src/models/qwen.js +80 -0
  107. package/src/models/replicate.js +60 -0
  108. package/src/models/vllm.js +81 -0
  109. package/src/process/agent_process.js +84 -0
  110. package/src/process/init_agent.js +54 -0
  111. package/src/utils/examples.js +83 -0
  112. package/src/utils/keys.js +34 -0
  113. package/src/utils/math.js +13 -0
  114. package/src/utils/mcdata.js +572 -0
  115. package/src/utils/text.js +78 -0
  116. package/src/utils/translator.js +30 -0
@@ -0,0 +1,561 @@
1
+ import { History } from './history.js';
2
+ import { Coder } from './coder.js';
3
+ import { Prompter } from '../models/prompter.js';
4
+ import { initModes } from './modes.js';
5
+ import { initBot } from '../utils/mcdata.js';
6
+ import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction, blacklistCommands } from './commands/index.js';
7
+ import { ActionManager } from './action_manager.js';
8
+ import { NPCContoller } from './npc/controller.js';
9
+ import { MemoryBank } from './memory_bank.js';
10
+ import { SelfPrompter } from './self_prompter.js';
11
+ import convoManager from './conversation.js';
12
+ import { handleTranslation, handleEnglishTranslation } from '../utils/translator.js';
13
+ import { addBrowserViewer } from './vision/browser_viewer.js';
14
+ import { serverProxy, sendOutputToServer } from './mindserver_proxy.js';
15
+ import settings from './settings.js';
16
+ import { Task } from './tasks/tasks.js';
17
+ import { speak } from './speak.js';
18
+ import { log, validateNameFormat, handleDisconnection } from './connection_handler.js';
19
+
20
+ export class Agent {
21
+ async start(load_mem=false, init_message=null, count_id=0) {
22
+ this.last_sender = null;
23
+ this.count_id = count_id;
24
+ this._disconnectHandled = false;
25
+
26
+ // Initialize components
27
+ this.actions = new ActionManager(this);
28
+ this.prompter = new Prompter(this, settings.profile);
29
+ this.name = (this.prompter.getName() || '').trim();
30
+ console.log(`Initializing agent ${this.name}...`);
31
+
32
+ // Validate Name Format
33
+ // connection_handler now ensures the message has [LoginGuard] prefix
34
+ const nameCheck = validateNameFormat(this.name);
35
+ if (!nameCheck.success) {
36
+ log(this.name, nameCheck.msg);
37
+ process.exit(1);
38
+ return;
39
+ }
40
+
41
+ this.history = new History(this);
42
+ this.coder = new Coder(this);
43
+ this.npc = new NPCContoller(this);
44
+ this.memory_bank = new MemoryBank();
45
+ this.self_prompter = new SelfPrompter(this);
46
+ convoManager.initAgent(this);
47
+ await this.prompter.initExamples();
48
+
49
+ // load mem first before doing task
50
+ let save_data = null;
51
+ if (load_mem) {
52
+ save_data = this.history.load();
53
+ }
54
+ let taskStart = null;
55
+ if (save_data) {
56
+ taskStart = save_data.taskStart;
57
+ } else {
58
+ taskStart = Date.now();
59
+ }
60
+ this.task = new Task(this, settings.task, taskStart);
61
+ this.blocked_actions = settings.blocked_actions.concat(this.task.blocked_actions || []);
62
+ blacklistCommands(this.blocked_actions);
63
+
64
+ console.log(this.name, 'logging into minecraft...');
65
+ this.bot = initBot(this.name);
66
+
67
+ // Connection Handler
68
+ const onDisconnect = (event, reason) => {
69
+ if (this._disconnectHandled) return;
70
+ this._disconnectHandled = true;
71
+
72
+ // Log and Analyze
73
+ // handleDisconnection handles logging to console and server
74
+ const { type } = handleDisconnection(this.name, reason);
75
+
76
+ process.exit(1);
77
+ };
78
+
79
+ // Bind events
80
+ this.bot.once('kicked', (reason) => onDisconnect('Kicked', reason));
81
+ this.bot.once('end', (reason) => onDisconnect('Disconnected', reason));
82
+ this.bot.on('error', (err) => {
83
+ if (String(err).includes('Duplicate') || String(err).includes('ECONNREFUSED')) {
84
+ onDisconnect('Error', err);
85
+ } else {
86
+ log(this.name, `[LoginGuard] Connection Error: ${String(err)}`);
87
+ }
88
+ });
89
+
90
+ initModes(this);
91
+
92
+ this.bot.on('login', () => {
93
+ console.log(this.name, 'logged in!');
94
+ serverProxy.login();
95
+
96
+ // Set skin for profile, requires Fabric Tailor. (https://modrinth.com/mod/fabrictailor)
97
+ if (this.prompter.profile.skin)
98
+ this.bot.chat(`/skin set URL ${this.prompter.profile.skin.model} ${this.prompter.profile.skin.path}`);
99
+ else
100
+ this.bot.chat(`/skin clear`);
101
+ });
102
+ const spawnTimeoutDuration = settings.spawn_timeout;
103
+ const spawnTimeout = setTimeout(() => {
104
+ const msg = `Bot has not spawned after ${spawnTimeoutDuration} seconds. Exiting.`;
105
+ log(this.name, msg);
106
+ process.exit(1);
107
+ }, spawnTimeoutDuration * 1000);
108
+ this.bot.once('spawn', async () => {
109
+ try {
110
+ clearTimeout(spawnTimeout);
111
+ addBrowserViewer(this.bot, count_id);
112
+ console.log('Initializing vision intepreter...');
113
+ // VisionInterpreter -> camera.js -> node-canvas-webgl, whose native
114
+ // deps (gl, canvas) are optionalDependencies and may not have built.
115
+ // Import lazily so the bot still works without screenshot vision.
116
+ try {
117
+ const { VisionInterpreter } = await import('./vision/vision_interpreter.js');
118
+ this.vision_interpreter = new VisionInterpreter(this, settings.allow_vision);
119
+ } catch (e) {
120
+ console.warn('Vision module unavailable (optional native deps not built):', e.message);
121
+ this.vision_interpreter = null;
122
+ }
123
+
124
+ // wait for a bit so stats are not undefined
125
+ await new Promise((resolve) => setTimeout(resolve, 1000));
126
+
127
+ console.log(`${this.name} spawned.`);
128
+ this.clearBotLogs();
129
+
130
+ this._setupEventHandlers(save_data, init_message);
131
+ this.startEvents();
132
+
133
+ if (!load_mem) {
134
+ if (settings.task) {
135
+ this.task.initBotTask();
136
+ this.task.setAgentGoal();
137
+ }
138
+ } else {
139
+ // set the goal without initializing the rest of the task
140
+ if (settings.task) {
141
+ this.task.setAgentGoal();
142
+ }
143
+ }
144
+
145
+ await new Promise((resolve) => setTimeout(resolve, 10000));
146
+ this.checkAllPlayersPresent();
147
+
148
+ } catch (error) {
149
+ console.error('Error in spawn event:', error);
150
+ process.exit(0);
151
+ }
152
+ });
153
+ }
154
+
155
+ async _setupEventHandlers(save_data, init_message) {
156
+ const ignore_messages = [
157
+ "Set own game mode to",
158
+ "Set the time to",
159
+ "Set the difficulty to",
160
+ "Teleported ",
161
+ "Set the weather to",
162
+ "Gamerule "
163
+ ];
164
+
165
+ const respondFunc = async (username, message) => {
166
+ if (message === "") return;
167
+ if (username === this.name) return;
168
+ if (settings.only_chat_with.length > 0 && !settings.only_chat_with.includes(username)) return;
169
+ try {
170
+ if (ignore_messages.some((m) => message.startsWith(m))) return;
171
+
172
+ this.shut_up = false;
173
+
174
+ console.log(this.name, 'received message from', username, ':', message);
175
+
176
+ if (convoManager.isOtherAgent(username)) {
177
+ console.warn('received whisper from other bot??')
178
+ }
179
+ else {
180
+ let translation = await handleEnglishTranslation(message);
181
+ this.handleMessage(username, translation);
182
+ }
183
+ } catch (error) {
184
+ console.error('Error handling message:', error);
185
+ }
186
+ }
187
+
188
+ this.respondFunc = respondFunc;
189
+
190
+ this.bot.on('whisper', respondFunc);
191
+
192
+ this.bot.on('chat', (username, message) => {
193
+ if (serverProxy.getNumOtherAgents() > 0) return;
194
+ // only respond to open chat messages when there are no other agents
195
+ respondFunc(username, message);
196
+ });
197
+
198
+ // Set up auto-eat
199
+ this.bot.autoEat.options = {
200
+ priority: 'foodPoints',
201
+ startAt: 14,
202
+ bannedFood: ["rotten_flesh", "spider_eye", "poisonous_potato", "pufferfish", "chicken"]
203
+ };
204
+
205
+ if (save_data?.self_prompt) {
206
+ if (init_message) {
207
+ this.history.add('system', init_message);
208
+ }
209
+ await this.self_prompter.handleLoad(save_data.self_prompt, save_data.self_prompting_state);
210
+ }
211
+ if (save_data?.last_sender) {
212
+ this.last_sender = save_data.last_sender;
213
+ if (convoManager.otherAgentInGame(this.last_sender)) {
214
+ const msg_package = {
215
+ message: `You have restarted and this message is auto-generated. Continue the conversation with me.`,
216
+ start: true
217
+ };
218
+ convoManager.receiveFromBot(this.last_sender, msg_package);
219
+ }
220
+ }
221
+ else if (init_message) {
222
+ await this.handleMessage('system', init_message, 2);
223
+ }
224
+ else {
225
+ this.openChat("Hello world! I am "+this.name);
226
+ }
227
+ }
228
+
229
+ checkAllPlayersPresent() {
230
+ if (!this.task || !this.task.agent_names) {
231
+ return;
232
+ }
233
+
234
+ const missingPlayers = this.task.agent_names.filter(name => !this.bot.players[name]);
235
+ if (missingPlayers.length > 0) {
236
+ console.log(`Missing players/bots: ${missingPlayers.join(', ')}`);
237
+ this.cleanKill('Not all required players/bots are present in the world. Exiting.', 4);
238
+ }
239
+ }
240
+
241
+ requestInterrupt() {
242
+ this.bot.interrupt_code = true;
243
+ this.bot.stopDigging();
244
+ this.bot.collectBlock.cancelTask();
245
+ this.bot.pathfinder.stop();
246
+ this.bot.pvp.stop();
247
+ }
248
+
249
+ clearBotLogs() {
250
+ this.bot.output = '';
251
+ this.bot.interrupt_code = false;
252
+ }
253
+
254
+ shutUp() {
255
+ this.shut_up = true;
256
+ if (this.self_prompter.isActive()) {
257
+ this.self_prompter.stop(false);
258
+ }
259
+ convoManager.endAllConversations();
260
+ }
261
+
262
+ async handleMessage(source, message, max_responses=null) {
263
+ await this.checkTaskDone();
264
+ if (!source || !message) {
265
+ console.warn('Received empty message from', source);
266
+ return false;
267
+ }
268
+
269
+ let used_command = false;
270
+ if (max_responses === null) {
271
+ max_responses = settings.max_commands === -1 ? Infinity : settings.max_commands;
272
+ }
273
+ if (max_responses === -1) {
274
+ max_responses = Infinity;
275
+ }
276
+
277
+ const self_prompt = source === 'system' || source === this.name;
278
+ const from_other_bot = convoManager.isOtherAgent(source);
279
+
280
+ if (!self_prompt && !from_other_bot) { // from user, check for forced commands
281
+ const user_command_name = containsCommand(message);
282
+ if (user_command_name) {
283
+ if (!commandExists(user_command_name)) {
284
+ this.routeResponse(source, `Command '${user_command_name}' does not exist.`);
285
+ return false;
286
+ }
287
+ this.routeResponse(source, `*${source} used ${user_command_name.substring(1)}*`);
288
+ if (user_command_name === '!newAction') {
289
+ // all user-initiated commands are ignored by the bot except for this one
290
+ // add the preceding message to the history to give context for newAction
291
+ this.history.add(source, message);
292
+ }
293
+ let execute_res = await executeCommand(this, message);
294
+ if (execute_res)
295
+ this.routeResponse(source, execute_res);
296
+ return true;
297
+ }
298
+ }
299
+
300
+ if (from_other_bot)
301
+ this.last_sender = source;
302
+
303
+ // Now translate the message
304
+ message = await handleEnglishTranslation(message);
305
+ console.log('received message from', source, ':', message);
306
+
307
+ const checkInterrupt = () => this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || convoManager.responseScheduledFor(source);
308
+
309
+ let behavior_log = this.bot.modes.flushBehaviorLog().trim();
310
+ if (behavior_log.length > 0) {
311
+ const MAX_LOG = 500;
312
+ if (behavior_log.length > MAX_LOG) {
313
+ behavior_log = '...' + behavior_log.substring(behavior_log.length - MAX_LOG);
314
+ }
315
+ behavior_log = 'Recent behaviors log: \n' + behavior_log;
316
+ await this.history.add('system', behavior_log);
317
+ }
318
+
319
+ // Handle other user messages
320
+ await this.history.add(source, message);
321
+ this.history.save();
322
+
323
+ if (!self_prompt && this.self_prompter.isActive()) // message is from user during self-prompting
324
+ max_responses = 1; // force only respond to this message, then let self-prompting take over
325
+ for (let i=0; i<max_responses; i++) {
326
+ if (checkInterrupt()) break;
327
+ let history = this.history.getHistory();
328
+ let res = await this.prompter.promptConvo(history);
329
+
330
+ console.log(`${this.name} full response to ${source}: ""${res}""`);
331
+
332
+ if (res.trim().length === 0) {
333
+ console.warn('no response')
334
+ break; // empty response ends loop
335
+ }
336
+
337
+ let command_name = containsCommand(res);
338
+
339
+ if (command_name) { // contains query or command
340
+ res = truncCommandMessage(res); // everything after the command is ignored
341
+ this.history.add(this.name, res);
342
+
343
+ if (!commandExists(command_name)) {
344
+ this.history.add('system', `Command ${command_name} does not exist.`);
345
+ console.warn('Agent hallucinated command:', command_name)
346
+ continue;
347
+ }
348
+
349
+ if (checkInterrupt()) break;
350
+ this.self_prompter.handleUserPromptedCmd(self_prompt, isAction(command_name));
351
+
352
+ if (settings.show_command_syntax === "full") {
353
+ this.routeResponse(source, res);
354
+ }
355
+ else if (settings.show_command_syntax === "shortened") {
356
+ // show only "used !commandname"
357
+ let pre_message = res.substring(0, res.indexOf(command_name)).trim();
358
+ let chat_message = `*used ${command_name.substring(1)}*`;
359
+ if (pre_message.length > 0)
360
+ chat_message = `${pre_message} ${chat_message}`;
361
+ this.routeResponse(source, chat_message);
362
+ }
363
+ else {
364
+ // no command at all
365
+ let pre_message = res.substring(0, res.indexOf(command_name)).trim();
366
+ if (pre_message.trim().length > 0)
367
+ this.routeResponse(source, pre_message);
368
+ }
369
+
370
+ let execute_res = await executeCommand(this, res);
371
+
372
+ console.log('Agent executed:', command_name, 'and got:', execute_res);
373
+ used_command = true;
374
+
375
+ if (execute_res)
376
+ this.history.add('system', execute_res);
377
+ else
378
+ break;
379
+ }
380
+ else { // conversation response
381
+ this.history.add(this.name, res);
382
+ this.routeResponse(source, res);
383
+ break;
384
+ }
385
+
386
+ this.history.save();
387
+ }
388
+
389
+ return used_command;
390
+ }
391
+
392
+ async routeResponse(to_player, message) {
393
+ if (this.shut_up) return;
394
+ let self_prompt = to_player === 'system' || to_player === this.name;
395
+ if (self_prompt && this.last_sender) {
396
+ // this is for when the agent is prompted by system while still in conversation
397
+ // so it can respond to events like death but be routed back to the last sender
398
+ to_player = this.last_sender;
399
+ }
400
+
401
+ if (convoManager.isOtherAgent(to_player) && convoManager.inConversation(to_player)) {
402
+ // if we're in an ongoing conversation with the other bot, send the response to it
403
+ convoManager.sendToBot(to_player, message);
404
+ }
405
+ else {
406
+ // otherwise, use open chat
407
+ this.openChat(message);
408
+ // note that to_player could be another bot, but if we get here the conversation has ended
409
+ }
410
+ }
411
+
412
+ async openChat(message) {
413
+ let to_translate = message;
414
+ let remaining = '';
415
+ let command_name = containsCommand(message);
416
+ let translate_up_to = command_name ? message.indexOf(command_name) : -1;
417
+ if (translate_up_to != -1) { // don't translate the command
418
+ to_translate = to_translate.substring(0, translate_up_to);
419
+ remaining = message.substring(translate_up_to);
420
+ }
421
+ message = (await handleTranslation(to_translate)).trim() + " " + remaining;
422
+ // newlines are interpreted as separate chats, which triggers spam filters. replace them with spaces
423
+ message = message.replaceAll('\n', ' ');
424
+
425
+ if (settings.only_chat_with.length > 0) {
426
+ for (let username of settings.only_chat_with) {
427
+ this.bot.whisper(username, message);
428
+ }
429
+ }
430
+ else {
431
+ if (settings.speak) {
432
+ speak(to_translate, this.prompter.profile.speak_model);
433
+ }
434
+ if (settings.chat_ingame) {this.bot.chat(message);}
435
+ sendOutputToServer(this.name, message);
436
+ }
437
+ }
438
+
439
+ startEvents() {
440
+ // Custom events
441
+ this.bot.on('time', () => {
442
+ if (this.bot.time.timeOfDay == 0)
443
+ this.bot.emit('sunrise');
444
+ else if (this.bot.time.timeOfDay == 6000)
445
+ this.bot.emit('noon');
446
+ else if (this.bot.time.timeOfDay == 12000)
447
+ this.bot.emit('sunset');
448
+ else if (this.bot.time.timeOfDay == 18000)
449
+ this.bot.emit('midnight');
450
+ });
451
+
452
+ let prev_health = this.bot.health;
453
+ this.bot.lastDamageTime = 0;
454
+ this.bot.lastDamageTaken = 0;
455
+ this.bot.on('health', () => {
456
+ if (this.bot.health < prev_health) {
457
+ this.bot.lastDamageTime = Date.now();
458
+ this.bot.lastDamageTaken = prev_health - this.bot.health;
459
+ }
460
+ prev_health = this.bot.health;
461
+ });
462
+ // Logging callbacks
463
+ this.bot.on('error' , (err) => {
464
+ console.error('Error event!', err);
465
+ });
466
+ // Use connection handler for runtime disconnects
467
+ this.bot.on('end', (reason) => {
468
+ if (!this._disconnectHandled) {
469
+ const { msg } = handleDisconnection(this.name, reason);
470
+ this.cleanKill(msg);
471
+ }
472
+ });
473
+ this.bot.on('death', () => {
474
+ this.actions.cancelResume();
475
+ this.actions.stop();
476
+ });
477
+ this.bot.on('kicked', (reason) => {
478
+ if (!this._disconnectHandled) {
479
+ const { msg } = handleDisconnection(this.name, reason);
480
+ this.cleanKill(msg);
481
+ }
482
+ });
483
+ this.bot.on('messagestr', async (message, _, jsonMsg) => {
484
+ if (jsonMsg.translate && jsonMsg.translate.startsWith('death') && message.startsWith(this.name)) {
485
+ console.log('Agent died: ', message);
486
+ let death_pos = this.bot.entity.position;
487
+ this.memory_bank.rememberPlace('last_death_position', death_pos.x, death_pos.y, death_pos.z);
488
+ let death_pos_text = null;
489
+ if (death_pos) {
490
+ death_pos_text = `x: ${death_pos.x.toFixed(2)}, y: ${death_pos.y.toFixed(2)}, z: ${death_pos.z.toFixed(2)}`;
491
+ }
492
+ let dimention = this.bot.game.dimension;
493
+ this.handleMessage('system', `You died at position ${death_pos_text || "unknown"} in the ${dimention} dimension with the final message: '${message}'. Your place of death is saved as 'last_death_position' if you want to return. Previous actions were stopped and you have respawned.`);
494
+ }
495
+ });
496
+ this.bot.on('idle', () => {
497
+ this.bot.clearControlStates();
498
+ this.bot.pathfinder.stop(); // clear any lingering pathfinder
499
+ this.bot.modes.unPauseAll();
500
+ setTimeout(() => {
501
+ if (this.isIdle()) {
502
+ this.actions.resumeAction();
503
+ }
504
+ }, 1000);
505
+ });
506
+
507
+ // Init NPC controller
508
+ this.npc.init();
509
+
510
+ // This update loop ensures that each update() is called one at a time, even if it takes longer than the interval
511
+ const INTERVAL = 300;
512
+ let last = Date.now();
513
+ setTimeout(async () => {
514
+ while (true) {
515
+ let start = Date.now();
516
+ await this.update(start - last);
517
+ let remaining = INTERVAL - (Date.now() - start);
518
+ if (remaining > 0) {
519
+ await new Promise((resolve) => setTimeout(resolve, remaining));
520
+ }
521
+ last = start;
522
+ }
523
+ }, INTERVAL);
524
+
525
+ this.bot.emit('idle');
526
+ }
527
+
528
+ async update(delta) {
529
+ await this.bot.modes.update();
530
+ this.self_prompter.update(delta);
531
+ await this.checkTaskDone();
532
+ }
533
+
534
+ isIdle() {
535
+ return !this.actions.executing;
536
+ }
537
+
538
+
539
+ cleanKill(msg='Killing agent process...', code=1) {
540
+ this.history.add('system', msg);
541
+ this.bot.chat(code > 1 ? 'Restarting.': 'Exiting.');
542
+ this.history.save();
543
+ process.exit(code);
544
+ }
545
+ async checkTaskDone() {
546
+ if (this.task.data) {
547
+ let res = this.task.isDone();
548
+ if (res) {
549
+ await this.history.add('system', `Task ended with score : ${res.score}`);
550
+ await this.history.save();
551
+ // await new Promise(resolve => setTimeout(resolve, 3000)); // Wait 3 second for save to complete
552
+ console.log('Task finished:', res.message);
553
+ this.killAll();
554
+ }
555
+ }
556
+ }
557
+
558
+ killAll() {
559
+ serverProxy.shutdown();
560
+ }
561
+ }