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.
- package/FAQ.md +38 -0
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/andy.json +6 -0
- package/bin/mindcraft.js +80 -0
- package/keys.example.json +19 -0
- package/main.js +80 -0
- package/package.json +78 -0
- package/patches/minecraft-data+3.97.0.patch +13 -0
- package/patches/mineflayer+4.33.0.patch +54 -0
- package/patches/mineflayer-pathfinder+2.4.5.patch +265 -0
- package/patches/mineflayer-pvp+1.3.2.patch +13 -0
- package/patches/prismarine-viewer+1.33.0.patch +13 -0
- package/patches/protodef+1.19.0.patch +15 -0
- package/profiles/andy-4-reasoning.json +14 -0
- package/profiles/andy-4.json +7 -0
- package/profiles/azure.json +19 -0
- package/profiles/claude.json +7 -0
- package/profiles/claude_thinker.json +15 -0
- package/profiles/deepseek.json +7 -0
- package/profiles/defaults/_default.json +256 -0
- package/profiles/defaults/assistant.json +14 -0
- package/profiles/defaults/creative.json +14 -0
- package/profiles/defaults/god_mode.json +14 -0
- package/profiles/defaults/survival.json +14 -0
- package/profiles/freeguy.json +7 -0
- package/profiles/gemini.json +9 -0
- package/profiles/gpt.json +12 -0
- package/profiles/grok.json +7 -0
- package/profiles/llama.json +10 -0
- package/profiles/mercury.json +9 -0
- package/profiles/mistral.json +5 -0
- package/profiles/qwen.json +17 -0
- package/profiles/tasks/construction_profile.json +42 -0
- package/profiles/tasks/cooking_profile.json +11 -0
- package/profiles/tasks/crafting_profile.json +71 -0
- package/profiles/vllm.json +10 -0
- package/settings.js +64 -0
- package/src/agent/action_manager.js +177 -0
- package/src/agent/agent.js +561 -0
- package/src/agent/coder.js +229 -0
- package/src/agent/commands/actions.js +504 -0
- package/src/agent/commands/index.js +259 -0
- package/src/agent/commands/queries.js +347 -0
- package/src/agent/connection_handler.js +96 -0
- package/src/agent/conversation.js +353 -0
- package/src/agent/history.js +122 -0
- package/src/agent/library/full_state.js +89 -0
- package/src/agent/library/index.js +23 -0
- package/src/agent/library/lockdown.js +32 -0
- package/src/agent/library/skill_library.js +93 -0
- package/src/agent/library/skills.js +2093 -0
- package/src/agent/library/world.js +431 -0
- package/src/agent/memory_bank.js +25 -0
- package/src/agent/mindserver_proxy.js +136 -0
- package/src/agent/modes.js +446 -0
- package/src/agent/npc/build_goal.js +80 -0
- package/src/agent/npc/construction/dirt_shelter.json +38 -0
- package/src/agent/npc/construction/large_house.json +230 -0
- package/src/agent/npc/construction/small_stone_house.json +42 -0
- package/src/agent/npc/construction/small_wood_house.json +42 -0
- package/src/agent/npc/controller.js +261 -0
- package/src/agent/npc/data.js +50 -0
- package/src/agent/npc/item_goal.js +355 -0
- package/src/agent/npc/utils.js +126 -0
- package/src/agent/self_prompter.js +146 -0
- package/src/agent/settings.js +7 -0
- package/src/agent/speak.js +150 -0
- package/src/agent/tasks/construction_tasks.js +1104 -0
- package/src/agent/tasks/cooking_tasks.js +358 -0
- package/src/agent/tasks/tasks.js +594 -0
- package/src/agent/templates/execTemplate.js +6 -0
- package/src/agent/templates/lintTemplate.js +10 -0
- package/src/agent/vision/browser_viewer.js +8 -0
- package/src/agent/vision/camera.js +78 -0
- package/src/agent/vision/vision_interpreter.js +82 -0
- package/src/mindcraft/index.js +28 -0
- package/src/mindcraft/mcserver.js +154 -0
- package/src/mindcraft/mindcraft.js +111 -0
- package/src/mindcraft/mindserver.js +328 -0
- package/src/mindcraft/public/index.html +1253 -0
- package/src/mindcraft/public/settings_spec.json +145 -0
- package/src/mindcraft/userconfig.js +72 -0
- package/src/mindcraft-py/example.py +27 -0
- package/src/mindcraft-py/init-mindcraft.js +24 -0
- package/src/mindcraft-py/mindcraft.py +99 -0
- package/src/models/_model_map.js +89 -0
- package/src/models/azure.js +32 -0
- package/src/models/cerebras.js +61 -0
- package/src/models/claude.js +87 -0
- package/src/models/deepseek.js +59 -0
- package/src/models/gemini.js +176 -0
- package/src/models/glhf.js +71 -0
- package/src/models/gpt.js +147 -0
- package/src/models/grok.js +82 -0
- package/src/models/groq.js +95 -0
- package/src/models/huggingface.js +86 -0
- package/src/models/hyperbolic.js +114 -0
- package/src/models/lmstudio.js +74 -0
- package/src/models/mercury.js +95 -0
- package/src/models/mistral.js +94 -0
- package/src/models/novita.js +71 -0
- package/src/models/ollama.js +115 -0
- package/src/models/openrouter.js +77 -0
- package/src/models/prompter.js +366 -0
- package/src/models/qwen.js +80 -0
- package/src/models/replicate.js +60 -0
- package/src/models/vllm.js +81 -0
- package/src/process/agent_process.js +84 -0
- package/src/process/init_agent.js +54 -0
- package/src/utils/examples.js +83 -0
- package/src/utils/keys.js +34 -0
- package/src/utils/math.js +13 -0
- package/src/utils/mcdata.js +572 -0
- package/src/utils/text.js +78 -0
- package/src/utils/translator.js +30 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { sendOutputToServer } from './mindserver_proxy.js';
|
|
2
|
+
|
|
3
|
+
// Definitions of error types, keywords, and full human-readable messages.
|
|
4
|
+
const ERROR_DEFINITIONS = {
|
|
5
|
+
'name_conflict': {
|
|
6
|
+
keywords: ['name_taken', 'duplicate_login', 'already connected', 'already logged in', 'username is already'],
|
|
7
|
+
msg: 'Name Conflict: The name is already in use or you are already logged in.',
|
|
8
|
+
isFatal: true
|
|
9
|
+
},
|
|
10
|
+
'access_denied': {
|
|
11
|
+
keywords: ['whitelist', 'not white-listed', 'banned', 'suspended', 'verify'],
|
|
12
|
+
msg: 'Access Denied: You are not whitelisted or banned.',
|
|
13
|
+
isFatal: true
|
|
14
|
+
},
|
|
15
|
+
'server_full': {
|
|
16
|
+
keywords: ['server is full', 'full server'],
|
|
17
|
+
msg: 'Connection Failed: The server is full.',
|
|
18
|
+
isFatal: false
|
|
19
|
+
},
|
|
20
|
+
'version_mismatch': {
|
|
21
|
+
keywords: ['outdated', 'version', 'client'],
|
|
22
|
+
msg: 'Version Mismatch: Client and server versions do not match.',
|
|
23
|
+
isFatal: true
|
|
24
|
+
},
|
|
25
|
+
'maintenance': {
|
|
26
|
+
keywords: ['maintenance', 'updating', 'closed', 'restarting'],
|
|
27
|
+
msg: 'Connection Failed: Server is under maintenance or restarting.',
|
|
28
|
+
isFatal: false
|
|
29
|
+
},
|
|
30
|
+
'network_error': {
|
|
31
|
+
keywords: ['timeout', 'timed out', 'connection lost', 'reset', 'refused', 'keepalive'],
|
|
32
|
+
msg: 'Network Error: Connection timed out or was lost.',
|
|
33
|
+
isFatal: false
|
|
34
|
+
},
|
|
35
|
+
'behavior': {
|
|
36
|
+
keywords: ['flying', 'spam', 'speed'],
|
|
37
|
+
msg: 'Kicked: Removed from server due to flying, spamming, or invalid movement.',
|
|
38
|
+
isFatal: true
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Helper to log messages to console (once) and MindServer.
|
|
43
|
+
export const log = (agentName, msg) => {
|
|
44
|
+
// Use console.error for visibility in terminal
|
|
45
|
+
console.error(msg);
|
|
46
|
+
try { sendOutputToServer(agentName || 'system', msg); } catch (_) {}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Analyzes the kick reason and returns a full, human-readable sentence.
|
|
50
|
+
export function parseKickReason(reason) {
|
|
51
|
+
if (!reason) return { type: 'unknown', msg: 'Unknown reason (Empty)', isFatal: true };
|
|
52
|
+
|
|
53
|
+
const raw = (typeof reason === 'string' ? reason : JSON.stringify(reason)).toLowerCase();
|
|
54
|
+
|
|
55
|
+
// Search for keywords in definitions
|
|
56
|
+
for (const [type, def] of Object.entries(ERROR_DEFINITIONS)) {
|
|
57
|
+
if (def.keywords.some(k => raw.includes(k))) {
|
|
58
|
+
console.error(`Disconnected: ${raw}`);
|
|
59
|
+
return { type, msg: def.msg, isFatal: def.isFatal };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Fallback: Extract text from JSON
|
|
64
|
+
let fallback = raw;
|
|
65
|
+
try {
|
|
66
|
+
const obj = typeof reason === 'string' ? JSON.parse(reason) : reason;
|
|
67
|
+
fallback = obj.translate || obj.text || (obj.value?.translate) || raw;
|
|
68
|
+
} catch (_) {}
|
|
69
|
+
|
|
70
|
+
return { type: 'other', msg: `Disconnected: ${fallback}`, isFatal: true };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Centralized handler for disconnections.
|
|
74
|
+
export function handleDisconnection(agentName, reason) {
|
|
75
|
+
const { type, msg } = parseKickReason(reason);
|
|
76
|
+
|
|
77
|
+
// Format: [LoginGuard] Error Message
|
|
78
|
+
const finalMsg = `[LoginGuard] ${msg}`;
|
|
79
|
+
|
|
80
|
+
// Only call log once (it handles console printing)
|
|
81
|
+
log(agentName, finalMsg);
|
|
82
|
+
|
|
83
|
+
return { type, msg: finalMsg };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Validates name format.
|
|
87
|
+
export function validateNameFormat(name) {
|
|
88
|
+
if (!name || !/^[a-zA-Z0-9_]{3,16}$/.test(name)) {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
// Added [LoginGuard] prefix here for consistency
|
|
92
|
+
msg: `[LoginGuard] Invalid name '${name}'. Must be 3-16 alphanumeric/underscore characters.`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { success: true };
|
|
96
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import settings from './settings.js';
|
|
2
|
+
import { containsCommand } from './commands/index.js';
|
|
3
|
+
import { sendBotChatToServer } from './mindserver_proxy.js';
|
|
4
|
+
|
|
5
|
+
let agent;
|
|
6
|
+
let agent_names = [];
|
|
7
|
+
let agents_in_game = [];
|
|
8
|
+
|
|
9
|
+
class Conversation {
|
|
10
|
+
constructor(name) {
|
|
11
|
+
this.name = name;
|
|
12
|
+
this.active = false;
|
|
13
|
+
this.ignore_until_start = false;
|
|
14
|
+
this.blocked = false;
|
|
15
|
+
this.in_queue = [];
|
|
16
|
+
this.inMessageTimer = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
reset() {
|
|
20
|
+
this.active = false;
|
|
21
|
+
this.ignore_until_start = false;
|
|
22
|
+
this.in_queue = [];
|
|
23
|
+
this.inMessageTimer = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
end() {
|
|
27
|
+
this.active = false;
|
|
28
|
+
this.ignore_until_start = true;
|
|
29
|
+
this.inMessageTimer = null;
|
|
30
|
+
const full_message = _compileInMessages(this);
|
|
31
|
+
if (full_message.message.trim().length > 0)
|
|
32
|
+
agent.history.add(this.name, full_message.message);
|
|
33
|
+
// add the full queued messages to history, but don't respond
|
|
34
|
+
|
|
35
|
+
if (agent.last_sender === this.name)
|
|
36
|
+
agent.last_sender = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
queue(message) {
|
|
40
|
+
this.in_queue.push(message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const WAIT_TIME_START = 30000;
|
|
45
|
+
class ConversationManager {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.convos = {};
|
|
48
|
+
this.activeConversation = null;
|
|
49
|
+
this.awaiting_response = false;
|
|
50
|
+
this.connection_timeout = null;
|
|
51
|
+
this.wait_time_limit = WAIT_TIME_START;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
initAgent(a) {
|
|
55
|
+
agent = a;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_getConvo(name) {
|
|
59
|
+
if (!this.convos[name])
|
|
60
|
+
this.convos[name] = new Conversation(name);
|
|
61
|
+
return this.convos[name];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_startMonitor() {
|
|
65
|
+
clearInterval(this.connection_monitor);
|
|
66
|
+
let wait_time = 0;
|
|
67
|
+
let last_time = Date.now();
|
|
68
|
+
this.connection_monitor = setInterval(() => {
|
|
69
|
+
if (!this.activeConversation) {
|
|
70
|
+
this._stopMonitor();
|
|
71
|
+
return; // will clean itself up
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let delta = Date.now() - last_time;
|
|
75
|
+
last_time = Date.now();
|
|
76
|
+
let convo_partner = this.activeConversation.name;
|
|
77
|
+
|
|
78
|
+
if (this.awaiting_response && agent.isIdle()) {
|
|
79
|
+
wait_time += delta;
|
|
80
|
+
if (wait_time > this.wait_time_limit) {
|
|
81
|
+
agent.handleMessage('system', `${convo_partner} hasn't responded in ${this.wait_time_limit/1000} seconds, respond with a message to them or your own action.`);
|
|
82
|
+
wait_time = 0;
|
|
83
|
+
this.wait_time_limit*=2;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (!this.awaiting_response){
|
|
87
|
+
this.wait_time_limit = WAIT_TIME_START;
|
|
88
|
+
wait_time = 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!this.otherAgentInGame(convo_partner) && !this.connection_timeout) {
|
|
92
|
+
this.connection_timeout = setTimeout(() => {
|
|
93
|
+
if (this.otherAgentInGame(convo_partner)){
|
|
94
|
+
this._clearMonitorTimeouts();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (!agent.self_prompter.isPaused()) {
|
|
98
|
+
this.endConversation(convo_partner);
|
|
99
|
+
agent.handleMessage('system', `${convo_partner} disconnected, conversation has ended.`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.endConversation(convo_partner);
|
|
103
|
+
}
|
|
104
|
+
}, 10000);
|
|
105
|
+
}
|
|
106
|
+
}, 1000);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_stopMonitor() {
|
|
110
|
+
clearInterval(this.connection_monitor);
|
|
111
|
+
this.connection_monitor = null;
|
|
112
|
+
this._clearMonitorTimeouts();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_clearMonitorTimeouts() {
|
|
116
|
+
this.awaiting_response = false;
|
|
117
|
+
clearTimeout(this.connection_timeout);
|
|
118
|
+
this.connection_timeout = null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async startConversation(send_to, message) {
|
|
122
|
+
const convo = this._getConvo(send_to);
|
|
123
|
+
convo.reset();
|
|
124
|
+
|
|
125
|
+
if (agent.self_prompter.isActive()) {
|
|
126
|
+
await agent.self_prompter.pause();
|
|
127
|
+
}
|
|
128
|
+
if (convo.active)
|
|
129
|
+
return;
|
|
130
|
+
convo.active = true;
|
|
131
|
+
this.activeConversation = convo;
|
|
132
|
+
this._startMonitor();
|
|
133
|
+
this.sendToBot(send_to, message, true, false);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
startConversationFromOtherBot(name) {
|
|
137
|
+
const convo = this._getConvo(name);
|
|
138
|
+
convo.active = true;
|
|
139
|
+
this.activeConversation = convo;
|
|
140
|
+
this._startMonitor();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
sendToBot(send_to, message, start=false, open_chat=true) {
|
|
144
|
+
if (!this.isOtherAgent(send_to)) {
|
|
145
|
+
console.warn(`${agent.name} tried to send bot message to non-bot ${send_to}`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const convo = this._getConvo(send_to);
|
|
149
|
+
|
|
150
|
+
if (settings.chat_bot_messages && open_chat)
|
|
151
|
+
agent.openChat(`(To ${send_to}) ${message}`);
|
|
152
|
+
|
|
153
|
+
if (convo.ignore_until_start)
|
|
154
|
+
return;
|
|
155
|
+
convo.active = true;
|
|
156
|
+
|
|
157
|
+
const end = message.includes('!endConversation');
|
|
158
|
+
const json = {
|
|
159
|
+
'message': message,
|
|
160
|
+
start,
|
|
161
|
+
end,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
this.awaiting_response = true;
|
|
165
|
+
sendBotChatToServer(send_to, json);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async receiveFromBot(sender, received) {
|
|
169
|
+
const convo = this._getConvo(sender);
|
|
170
|
+
|
|
171
|
+
if (convo.ignore_until_start && !received.start)
|
|
172
|
+
return;
|
|
173
|
+
|
|
174
|
+
// check if any convo is active besides the sender
|
|
175
|
+
if (this.inConversation() && !this.inConversation(sender)) {
|
|
176
|
+
this.sendToBot(sender, `I'm talking to someone else, try again later. !endConversation("${sender}")`, false, false);
|
|
177
|
+
this.endConversation(sender);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (received.start) {
|
|
182
|
+
convo.reset();
|
|
183
|
+
this.startConversationFromOtherBot(sender);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this._clearMonitorTimeouts();
|
|
187
|
+
convo.queue(received);
|
|
188
|
+
|
|
189
|
+
// responding to conversation takes priority over self prompting
|
|
190
|
+
if (agent.self_prompter.isActive()){
|
|
191
|
+
await agent.self_prompter.pause();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
_scheduleProcessInMessage(sender, received, convo);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
responseScheduledFor(sender) {
|
|
198
|
+
if (!this.isOtherAgent(sender) || !this.inConversation(sender))
|
|
199
|
+
return false;
|
|
200
|
+
const convo = this._getConvo(sender);
|
|
201
|
+
return !!convo.inMessageTimer;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
isOtherAgent(name) {
|
|
205
|
+
return agent_names.some((n) => n === name);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
otherAgentInGame(name) {
|
|
209
|
+
return agents_in_game.some((n) => n === name);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
updateAgents(agents) {
|
|
213
|
+
agent_names = agents.map(a => a.name);
|
|
214
|
+
agents_in_game = agents.filter(a => a.in_game).map(a => a.name);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
getInGameAgents() {
|
|
218
|
+
return agents_in_game;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
inConversation(other_agent=null) {
|
|
222
|
+
if (other_agent)
|
|
223
|
+
return this.convos[other_agent]?.active;
|
|
224
|
+
return Object.values(this.convos).some(c => c.active);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
endConversation(sender) {
|
|
228
|
+
if (this.convos[sender]) {
|
|
229
|
+
this.convos[sender].end();
|
|
230
|
+
if (this.activeConversation.name === sender) {
|
|
231
|
+
this._stopMonitor();
|
|
232
|
+
this.activeConversation = null;
|
|
233
|
+
if (agent.self_prompter.isPaused() && !this.inConversation()) {
|
|
234
|
+
_resumeSelfPrompter();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
endAllConversations() {
|
|
241
|
+
for (const sender in this.convos) {
|
|
242
|
+
this.endConversation(sender);
|
|
243
|
+
}
|
|
244
|
+
if (agent.self_prompter.isPaused()) {
|
|
245
|
+
_resumeSelfPrompter();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
forceEndCurrentConversation() {
|
|
250
|
+
if (this.activeConversation) {
|
|
251
|
+
let sender = this.activeConversation.name;
|
|
252
|
+
this.sendToBot(sender, '!endConversation("' + sender + '")', false, false);
|
|
253
|
+
this.endConversation(sender);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const convoManager = new ConversationManager();
|
|
259
|
+
export default convoManager;
|
|
260
|
+
|
|
261
|
+
/*
|
|
262
|
+
This function controls conversation flow by deciding when the bot responds.
|
|
263
|
+
The logic is as follows:
|
|
264
|
+
- If neither bot is busy, respond quickly with a small delay.
|
|
265
|
+
- If only the other bot is busy, respond with a long delay to allow it to finish short actions (ex check inventory)
|
|
266
|
+
- If I'm busy but other bot isn't, let LLM decide whether to respond
|
|
267
|
+
- If both bots are busy, don't respond until someone is done, excluding a few actions that allow fast responses
|
|
268
|
+
- New messages received during the delay will reset the delay following this logic, and be queued to respond in bulk
|
|
269
|
+
*/
|
|
270
|
+
const talkOverActions = ['stay', 'followPlayer', 'mode:']; // all mode actions
|
|
271
|
+
const fastDelay = 200;
|
|
272
|
+
const longDelay = 5000;
|
|
273
|
+
async function _scheduleProcessInMessage(sender, received, convo) {
|
|
274
|
+
if (convo.inMessageTimer)
|
|
275
|
+
clearTimeout(convo.inMessageTimer);
|
|
276
|
+
let otherAgentBusy = containsCommand(received.message);
|
|
277
|
+
|
|
278
|
+
const scheduleResponse = (delay) => convo.inMessageTimer = setTimeout(() => _processInMessageQueue(sender), delay);
|
|
279
|
+
|
|
280
|
+
if (!agent.isIdle() && otherAgentBusy) {
|
|
281
|
+
// both are busy
|
|
282
|
+
let canTalkOver = talkOverActions.some(a => agent.actions.currentActionLabel.includes(a));
|
|
283
|
+
if (canTalkOver)
|
|
284
|
+
scheduleResponse(fastDelay)
|
|
285
|
+
// otherwise don't respond
|
|
286
|
+
}
|
|
287
|
+
else if (otherAgentBusy)
|
|
288
|
+
// other bot is busy but I'm not
|
|
289
|
+
scheduleResponse(longDelay);
|
|
290
|
+
else if (!agent.isIdle()) {
|
|
291
|
+
// I'm busy but other bot isn't
|
|
292
|
+
let canTalkOver = talkOverActions.some(a => agent.actions.currentActionLabel.includes(a));
|
|
293
|
+
if (canTalkOver) {
|
|
294
|
+
scheduleResponse(fastDelay);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
let shouldRespond = await agent.prompter.promptShouldRespondToBot(received.message);
|
|
298
|
+
console.log(`${agent.name} decided to ${shouldRespond?'respond':'not respond'} to ${sender}`);
|
|
299
|
+
if (shouldRespond)
|
|
300
|
+
scheduleResponse(fastDelay);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
// neither are busy
|
|
305
|
+
scheduleResponse(fastDelay);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function _processInMessageQueue(name) {
|
|
310
|
+
const convo = convoManager._getConvo(name);
|
|
311
|
+
_handleFullInMessage(name, _compileInMessages(convo));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function _compileInMessages(convo) {
|
|
315
|
+
let pack = {};
|
|
316
|
+
let full_message = '';
|
|
317
|
+
while (convo.in_queue.length > 0) {
|
|
318
|
+
pack = convo.in_queue.shift();
|
|
319
|
+
full_message += pack.message;
|
|
320
|
+
}
|
|
321
|
+
pack.message = full_message;
|
|
322
|
+
return pack;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function _handleFullInMessage(sender, received) {
|
|
326
|
+
console.log(`${agent.name} responding to "${received.message}" from ${sender}`);
|
|
327
|
+
|
|
328
|
+
const convo = convoManager._getConvo(sender);
|
|
329
|
+
convo.active = true;
|
|
330
|
+
|
|
331
|
+
let message = _tagMessage(received.message);
|
|
332
|
+
if (received.end) {
|
|
333
|
+
convoManager.endConversation(sender);
|
|
334
|
+
message = `Conversation with ${sender} ended with message: "${message}"`;
|
|
335
|
+
sender = 'system'; // bot will respond to system instead of the other bot
|
|
336
|
+
}
|
|
337
|
+
else if (received.start)
|
|
338
|
+
agent.shut_up = false;
|
|
339
|
+
convo.inMessageTimer = null;
|
|
340
|
+
agent.handleMessage(sender, message);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
function _tagMessage(message) {
|
|
345
|
+
return "(FROM OTHER BOT)" + message;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function _resumeSelfPrompter() {
|
|
349
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
350
|
+
if (agent.self_prompter.isPaused() && !convoManager.inConversation()) {
|
|
351
|
+
agent.self_prompter.start();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { writeFileSync, readFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
|
+
import { NPCData } from './npc/data.js';
|
|
3
|
+
import settings from './settings.js';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export class History {
|
|
7
|
+
constructor(agent) {
|
|
8
|
+
this.agent = agent;
|
|
9
|
+
this.name = agent.name;
|
|
10
|
+
this.bot_dir = `${settings.data_dir}/${this.name}`;
|
|
11
|
+
this.memory_fp = `${this.bot_dir}/memory.json`;
|
|
12
|
+
this.full_history_fp = undefined;
|
|
13
|
+
|
|
14
|
+
mkdirSync(`${this.bot_dir}/histories`, { recursive: true });
|
|
15
|
+
|
|
16
|
+
this.turns = [];
|
|
17
|
+
|
|
18
|
+
// Natural language memory as a summary of recent messages + previous memory
|
|
19
|
+
this.memory = '';
|
|
20
|
+
|
|
21
|
+
// Maximum number of messages to keep in context before saving chunk to memory
|
|
22
|
+
this.max_messages = settings.max_messages;
|
|
23
|
+
|
|
24
|
+
// Number of messages to remove from current history and save into memory
|
|
25
|
+
this.summary_chunk_size = 5;
|
|
26
|
+
// chunking reduces expensive calls to promptMemSaving and appendFullHistory
|
|
27
|
+
// and improves the quality of the memory summary
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getHistory() { // expects an Examples object
|
|
31
|
+
return JSON.parse(JSON.stringify(this.turns));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async summarizeMemories(turns) {
|
|
35
|
+
console.log("Storing memories...");
|
|
36
|
+
this.memory = await this.agent.prompter.promptMemSaving(turns);
|
|
37
|
+
|
|
38
|
+
if (this.memory.length > 500) {
|
|
39
|
+
this.memory = this.memory.slice(0, 500);
|
|
40
|
+
this.memory += '...(Memory truncated to 500 chars. Compress it more next time)';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log("Memory updated to: ", this.memory);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async appendFullHistory(to_store) {
|
|
47
|
+
if (this.full_history_fp === undefined) {
|
|
48
|
+
const string_timestamp = new Date().toLocaleString().replace(/[/:]/g, '-').replace(/ /g, '').replace(/,/g, '_');
|
|
49
|
+
this.full_history_fp = `${this.bot_dir}/histories/${string_timestamp}.json`;
|
|
50
|
+
writeFileSync(this.full_history_fp, '[]', 'utf8');
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const data = readFileSync(this.full_history_fp, 'utf8');
|
|
54
|
+
let full_history = JSON.parse(data);
|
|
55
|
+
full_history.push(...to_store);
|
|
56
|
+
writeFileSync(this.full_history_fp, JSON.stringify(full_history, null, 4), 'utf8');
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error(`Error reading ${this.name}'s full history file: ${err.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async add(name, content) {
|
|
63
|
+
let role = 'assistant';
|
|
64
|
+
if (name === 'system') {
|
|
65
|
+
role = 'system';
|
|
66
|
+
}
|
|
67
|
+
else if (name !== this.name) {
|
|
68
|
+
role = 'user';
|
|
69
|
+
content = `${name}: ${content}`;
|
|
70
|
+
}
|
|
71
|
+
this.turns.push({role, content});
|
|
72
|
+
|
|
73
|
+
if (this.turns.length >= this.max_messages) {
|
|
74
|
+
let chunk = this.turns.splice(0, this.summary_chunk_size);
|
|
75
|
+
while (this.turns.length > 0 && this.turns[0].role === 'assistant')
|
|
76
|
+
chunk.push(this.turns.shift()); // remove until turns starts with system/user message
|
|
77
|
+
|
|
78
|
+
await this.summarizeMemories(chunk);
|
|
79
|
+
await this.appendFullHistory(chunk);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async save() {
|
|
84
|
+
try {
|
|
85
|
+
const data = {
|
|
86
|
+
memory: this.memory,
|
|
87
|
+
turns: this.turns,
|
|
88
|
+
self_prompting_state: this.agent.self_prompter.state,
|
|
89
|
+
self_prompt: this.agent.self_prompter.isStopped() ? null : this.agent.self_prompter.prompt,
|
|
90
|
+
taskStart: this.agent.task.taskStartTime,
|
|
91
|
+
last_sender: this.agent.last_sender
|
|
92
|
+
};
|
|
93
|
+
writeFileSync(this.memory_fp, JSON.stringify(data, null, 2));
|
|
94
|
+
console.log('Saved memory to:', this.memory_fp);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Failed to save history:', error);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
load() {
|
|
102
|
+
try {
|
|
103
|
+
if (!existsSync(this.memory_fp)) {
|
|
104
|
+
console.log('No memory file found.');
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const data = JSON.parse(readFileSync(this.memory_fp, 'utf8'));
|
|
108
|
+
this.memory = data.memory || '';
|
|
109
|
+
this.turns = data.turns || [];
|
|
110
|
+
console.log('Loaded memory:', this.memory);
|
|
111
|
+
return data;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('Failed to load history:', error);
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
clear() {
|
|
119
|
+
this.turns = [];
|
|
120
|
+
this.memory = '';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getPosition,
|
|
3
|
+
getBiomeName,
|
|
4
|
+
getNearbyPlayerNames,
|
|
5
|
+
getInventoryCounts,
|
|
6
|
+
getNearbyEntityTypes,
|
|
7
|
+
getBlockAtPosition,
|
|
8
|
+
getFirstBlockAboveHead
|
|
9
|
+
} from "./world.js";
|
|
10
|
+
import convoManager from '../conversation.js';
|
|
11
|
+
|
|
12
|
+
export function getFullState(agent) {
|
|
13
|
+
const bot = agent.bot;
|
|
14
|
+
|
|
15
|
+
const pos = getPosition(bot);
|
|
16
|
+
const position = {
|
|
17
|
+
x: Number(pos.x.toFixed(2)),
|
|
18
|
+
y: Number(pos.y.toFixed(2)),
|
|
19
|
+
z: Number(pos.z.toFixed(2))
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
let weather = 'Clear';
|
|
23
|
+
if (bot.thunderState > 0) weather = 'Thunderstorm';
|
|
24
|
+
else if (bot.rainState > 0) weather = 'Rain';
|
|
25
|
+
|
|
26
|
+
let timeLabel = 'Night';
|
|
27
|
+
if (bot.time.timeOfDay < 6000) timeLabel = 'Morning';
|
|
28
|
+
else if (bot.time.timeOfDay < 12000) timeLabel = 'Afternoon';
|
|
29
|
+
|
|
30
|
+
const below = getBlockAtPosition(bot, 0, -1, 0).name;
|
|
31
|
+
const legs = getBlockAtPosition(bot, 0, 0, 0).name;
|
|
32
|
+
const head = getBlockAtPosition(bot, 0, 1, 0).name;
|
|
33
|
+
|
|
34
|
+
let players = getNearbyPlayerNames(bot);
|
|
35
|
+
let bots = convoManager.getInGameAgents().filter(b => b !== agent.name);
|
|
36
|
+
players = players.filter(p => !bots.includes(p));
|
|
37
|
+
|
|
38
|
+
const helmet = bot.inventory.slots[5];
|
|
39
|
+
const chestplate = bot.inventory.slots[6];
|
|
40
|
+
const leggings = bot.inventory.slots[7];
|
|
41
|
+
const boots = bot.inventory.slots[8];
|
|
42
|
+
|
|
43
|
+
const state = {
|
|
44
|
+
name: agent.name,
|
|
45
|
+
gameplay: {
|
|
46
|
+
position,
|
|
47
|
+
dimension: bot.game.dimension,
|
|
48
|
+
gamemode: bot.game.gameMode,
|
|
49
|
+
health: Math.round(bot.health),
|
|
50
|
+
hunger: Math.round(bot.food),
|
|
51
|
+
biome: getBiomeName(bot),
|
|
52
|
+
weather,
|
|
53
|
+
timeOfDay: bot.time.timeOfDay,
|
|
54
|
+
timeLabel
|
|
55
|
+
},
|
|
56
|
+
action: {
|
|
57
|
+
current: agent.isIdle() ? 'Idle' : agent.actions.currentActionLabel,
|
|
58
|
+
isIdle: agent.isIdle()
|
|
59
|
+
},
|
|
60
|
+
surroundings: {
|
|
61
|
+
below,
|
|
62
|
+
legs,
|
|
63
|
+
head,
|
|
64
|
+
firstBlockAboveHead: getFirstBlockAboveHead(bot, null, 32)
|
|
65
|
+
},
|
|
66
|
+
inventory: {
|
|
67
|
+
counts: getInventoryCounts(bot),
|
|
68
|
+
stacksUsed: bot.inventory.items().length,
|
|
69
|
+
totalSlots: bot.inventory.slots.length,
|
|
70
|
+
equipment: {
|
|
71
|
+
helmet: helmet ? helmet.name : null,
|
|
72
|
+
chestplate: chestplate ? chestplate.name : null,
|
|
73
|
+
leggings: leggings ? leggings.name : null,
|
|
74
|
+
boots: boots ? boots.name : null,
|
|
75
|
+
mainHand: bot.heldItem ? bot.heldItem.name : null
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
nearby: {
|
|
79
|
+
humanPlayers: players,
|
|
80
|
+
botPlayers: bots,
|
|
81
|
+
entityTypes: getNearbyEntityTypes(bot).filter(t => t !== 'player' && t !== 'item'),
|
|
82
|
+
},
|
|
83
|
+
modes: {
|
|
84
|
+
summary: bot.modes.getMiniDocs()
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return state;
|
|
89
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as skills from './skills.js';
|
|
2
|
+
import * as world from './world.js';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function docHelper(functions, module_name) {
|
|
6
|
+
let docArray = [];
|
|
7
|
+
for (let skillFunc of functions) {
|
|
8
|
+
let str = skillFunc.toString();
|
|
9
|
+
if (str.includes('/**')) {
|
|
10
|
+
let docEntry = `${module_name}.${skillFunc.name}\n`;
|
|
11
|
+
docEntry += str.substring(str.indexOf('/**') + 3, str.indexOf('**/')).trim();
|
|
12
|
+
docArray.push(docEntry);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return docArray;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getSkillDocs() {
|
|
19
|
+
let docArray = [];
|
|
20
|
+
docArray = docArray.concat(docHelper(Object.values(skills), 'skills'));
|
|
21
|
+
docArray = docArray.concat(docHelper(Object.values(world), 'world'));
|
|
22
|
+
return docArray;
|
|
23
|
+
}
|