From e6d4c8d377999820da72533aac9dac659265a6f4 Mon Sep 17 00:00:00 2001 From: roberts Date: Mon, 30 Mar 2026 17:18:41 -0500 Subject: [PATCH] Add equip/inventory commands, report results without AI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New commands: - "Doug, equip your sword" → equips item, reports "Equipped sword." - "Doug, what do you have?" → lists actual inventory items - "Doug, check your inventory" → same Key change: command results now report DIRECTLY to chat without going through AI. No more hallucinated responses about items Doug doesn't have. Commands execute → report real result. AI is ONLY used for conversation, not for task responses. Co-Authored-By: Claude Opus 4.6 (1M context) --- dougbot/core/brain.py | 48 ++++++++++++++++++++++++ dougbot/core/command_parser.py | 68 ++++++++++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 3 deletions(-) diff --git a/dougbot/core/brain.py b/dougbot/core/brain.py index e417df5..f68b04a 100644 --- a/dougbot/core/brain.py +++ b/dougbot/core/brain.py @@ -592,6 +592,12 @@ class DougBrain(QObject): self._tasks.complete() return + # Callbacks that need special response handling + if task.callback in ("on_equip_result", "on_inventory_report"): + # Execute then report result directly to chat (no AI) + self._execute_action_with_report(task) + return + # Skip placeholder actions if task.action == "status": self._tasks.complete() @@ -648,6 +654,48 @@ class DougBrain(QObject): self._ws.send_request(task.action, task.params, on_response) + def _execute_action_with_report(self, task: Task): + """Execute an action and report the result directly to chat (no AI).""" + self._state = BrainState.EXECUTING_TASK + self._action_sent_time = time.time() + + if task.description and task.priority >= Priority.LOW: + log.info(f"[{task.priority.name}] {task.description}") + + def on_response(resp: ResponseMessage): + self._state = BrainState.IDLE + + if task.callback == "on_equip_result": + if resp.status == "success": + item = task.params.get("name", "item").replace("_", " ") + self._ws.send_request("send_chat", {"message": f"Equipped {item}."}) + self._tasks.complete() + else: + error = resp.error or "I don't have that item." + self._ws.send_request("send_chat", {"message": error}) + self._tasks.cancel() + + elif task.callback == "on_inventory_report": + if resp.status == "success": + items = resp.data.get("items", []) + if items: + # List items concisely + item_strs = [f"{i['count']}x {i['name'].replace('_',' ')}" for i in items[:8]] + msg = "I have: " + ", ".join(item_strs) + if len(items) > 8: + msg += f" and {len(items) - 8} more items" + self._ws.send_request("send_chat", {"message": msg}) + else: + self._ws.send_request("send_chat", {"message": "My inventory is empty."}) + self._tasks.complete() + else: + self._ws.send_request("send_chat", {"message": "Can't check inventory right now."}) + self._tasks.cancel() + else: + self._tasks.complete() + + self._ws.send_request(task.action, task.params, on_response) + # ── Helpers ── def _handle_idle_chat(self, task: Task): diff --git a/dougbot/core/command_parser.py b/dougbot/core/command_parser.py index d799b68..35ee58d 100644 --- a/dougbot/core/command_parser.py +++ b/dougbot/core/command_parser.py @@ -69,7 +69,19 @@ class CommandParser: r"(?:give|hand|pass|toss)\s+(?:me|us)\s+(?:a\s+|an\s+|some\s+)?(.+)", ] - # Social commands + EQUIP_PATTERNS = [ + r"(?:equip|hold|use|grab|wield|switch to|pull out|get out)\s+(?:your\s+|the\s+|a\s+|my\s+)?(.+)", + r"(?:put on|wear)\s+(?:your\s+|the\s+|a\s+)?(.+)", + ] + + INVENTORY_PATTERNS = [ + r"what(?:'s| is| do you have)\s+in\s+(?:your\s+)?(?:inventory|bag|pocket|backpack)", + r"what\s+(?:do you have|are you carrying|items do you have)", + r"show\s+(?:me\s+)?(?:your\s+)?(?:inventory|items|stuff)", + r"check\s+(?:your\s+)?(?:inventory|items|stuff)", + ] + + # Social/combat commands ATTACK_PATTERNS = [ r"(?:attack|fight|kill|hit)\s+(?:that|this|the)?\s*(.+)", ] @@ -115,6 +127,12 @@ class CommandParser: cmd = self._try_give(msg, sender) if cmd: return cmd + cmd = self._try_equip(msg, sender) + if cmd: return cmd + + cmd = self._try_inventory(msg, sender) + if cmd: return cmd + cmd = self._try_attack(msg, sender) if cmd: return cmd @@ -236,6 +254,30 @@ class CommandParser: ) return None + def _try_equip(self, msg: str, sender: str) -> Optional[ParsedCommand]: + for pattern in self.EQUIP_PATTERNS: + match = re.search(pattern, msg, re.IGNORECASE) + if match: + raw_item = match.group(1).strip() if match.lastindex else "" + # Clean up item name + filler = {"your", "the", "a", "my", "that"} + words = [w for w in raw_item.split() if w.lower().rstrip(".,!?") not in filler] + if not words: + return None + item = "_".join(w.lower().rstrip(".,!?") for w in words[:3]) + return ParsedCommand( + action="equip", + target=item, + raw_message=msg, + ) + return None + + def _try_inventory(self, msg: str, sender: str) -> Optional[ParsedCommand]: + for pattern in self.INVENTORY_PATTERNS: + if re.search(pattern, msg, re.IGNORECASE): + return ParsedCommand(action="check_inventory", raw_message=msg) + return None + def _try_attack(self, msg: str, sender: str) -> Optional[ParsedCommand]: for pattern in self.ATTACK_PATTERNS: match = re.search(pattern, msg, re.IGNORECASE) @@ -348,9 +390,29 @@ def command_to_task(cmd: ParsedCommand, behaviors) -> Optional[Task]: callback="on_look_around_report", ) + elif cmd.action == "equip": + return Task( + name=f"equip_{cmd.target}", + priority=Priority.HIGH, + action="equip_item", + params={"name": cmd.target, "destination": "hand"}, + description=f"Equipping {cmd.target.replace('_', ' ')}", + timeout=10, + callback="on_equip_result", + ) + + elif cmd.action == "check_inventory": + return Task( + name="check_inventory", + priority=Priority.HIGH, + action="get_inventory", + params={}, + description="Checking inventory", + timeout=10, + callback="on_inventory_report", + ) + elif cmd.action == "go_to": - # For named destinations, we'd need a memory system - # For now, try to interpret as a player name return Task( name=f"go_to_{cmd.target}", priority=Priority.HIGH,