Skip to content

Commit 6ac5375

Browse files
committed
refactor: Crush improved type hints and error handling — 815 lines
6 files improved: studio_engine.py, fleet_integration.py, and 4 others.
1 parent 803d0e2 commit 6ac5375

5 files changed

Lines changed: 585 additions & 113 deletions

File tree

client.py

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,33 @@
1616

1717

1818
class MUDClient:
19-
"""Async MUD client for programmatic agent access."""
19+
"""Async MUD client for programmatic agent access.
20+
21+
Provides a Python interface for connecting to a MUD server
22+
and executing commands programmatically, useful for bots,
23+
automated agents, and testing scripts.
24+
25+
Example usage:
26+
async with MUDClient("oracle1") as mud:
27+
await mud.say("Anyone want to hash out ISA v3?")
28+
"""
2029

21-
def __init__(self, name: str, role: str = "", host: str = "localhost", port: int = 7777):
30+
def __init__(self, name: str, role: str = "", host: str = "localhost", port: int = 7777) -> None:
31+
"""Initialize MUD client.
32+
33+
Args:
34+
name: Agent name for login
35+
role: Agent role (optional)
36+
host: MUD server host (default: localhost)
37+
port: MUD server port (default: 7777)
38+
"""
2239
self.name = name
2340
self.role = role
2441
self.host = host
2542
self.port = port
2643
self.reader = None
2744
self.writer = None
28-
self._buffer = []
45+
self._buffer: List[str] = []
2946

3047
async def __aenter__(self):
3148
self.reader, self.writer = await asyncio.open_connection(self.host, self.port)
@@ -48,11 +65,25 @@ async def __aexit__(self, *args):
4865
self.writer.close()
4966

5067
async def _send(self, cmd: str) -> str:
51-
"""Send a command and return the response."""
68+
"""Send a command and return the response.
69+
70+
Args:
71+
cmd: Command string to send to MUD
72+
73+
Returns:
74+
str: The response from the server
75+
76+
Raises:
77+
ConnectionError: If connection is lost
78+
"""
79+
if not self.writer:
80+
raise ConnectionError("Not connected to MUD server")
81+
5282
self.writer.write(f"{cmd}\n".encode())
5383
await self.writer.drain()
84+
5485
# Read response(s) until prompt or timeout
55-
lines = []
86+
lines: List[str] = []
5687
try:
5788
while True:
5889
line = await asyncio.wait_for(self.reader.readline(), timeout=1.0)
@@ -63,48 +94,64 @@ async def _send(self, cmd: str) -> str:
6394
lines.append(decoded)
6495
except asyncio.TimeoutError:
6596
pass
97+
except ConnectionResetError:
98+
raise ConnectionError("Connection reset by server")
99+
66100
return "\n".join(lines)
67101

68102
async def say(self, text: str) -> str:
103+
"""Send a say command (room-wide message)."""
69104
return await self._send(f'say {text}')
70105

71106
async def tell(self, target: str, text: str) -> str:
107+
"""Send a tell command (private message to an agent)."""
72108
return await self._send(f'tell {target} {text}')
73109

74110
async def gossip(self, text: str) -> str:
111+
"""Send a gossip command (ship-wide broadcast)."""
75112
return await self._send(f'gossip {text}')
76113

77114
async def ooc(self, text: str) -> str:
115+
"""Send an OOC command (out-of-character message)."""
78116
return await self._send(f'ooc {text}')
79117

80118
async def emote(self, action: str) -> str:
119+
"""Send an emote command (roleplay action)."""
81120
return await self._send(f'emote {action}')
82121

83122
async def go(self, exit_name: str) -> str:
123+
"""Move to an adjacent room via an exit."""
84124
return await self._send(f'go {exit_name}')
85125

86126
async def look(self) -> str:
127+
"""Look around the current room."""
87128
return await self._send('look')
88129

89130
async def build(self, name: str, desc: str) -> str:
131+
"""Create a new room."""
90132
return await self._send(f'build "{name}" -desc "{desc}"')
91133

92134
async def write_note(self, text: str) -> str:
135+
"""Write a note on the room's wall."""
93136
return await self._send(f'write {text}')
94137

95138
async def read_notes(self) -> str:
139+
"""Read notes from the room's wall."""
96140
return await self._send('read')
97141

98142
async def mask(self, name: str, desc: str = "") -> str:
143+
"""Put on a character mask."""
99144
cmd = f'mask "{name}"'
100145
if desc:
101146
cmd += f' -desc "{desc}"'
102147
return await self._send(cmd)
103148

104149
async def unmask(self) -> str:
150+
"""Remove your character mask."""
105151
return await self._send('unmask')
106152

107153
async def spawn_npc(self, name: str, role: str = "", topic: str = "") -> str:
154+
"""Spawn a constructed NPC."""
108155
cmd = f'spawn "{name}"'
109156
if role:
110157
cmd += f' -role "{role}"'
@@ -113,14 +160,23 @@ async def spawn_npc(self, name: str, role: str = "", topic: str = "") -> str:
113160
return await self._send(cmd)
114161

115162
async def dismiss_npc(self, name: str) -> str:
163+
"""Dismiss a constructed NPC."""
116164
return await self._send(f'dismiss {name}')
117165

118166
async def who(self) -> str:
167+
"""List all connected agents."""
119168
return await self._send('who')
120169

121170

122-
async def interactive(name: str, role: str, host: str, port: int):
123-
"""Run an interactive session (like telnet but with name auto-filled)."""
171+
async def interactive(name: str, role: str, host: str, port: int) -> None:
172+
"""Run an interactive session (like telnet but with name auto-filled).
173+
174+
Args:
175+
name: Agent name to use
176+
role: Agent role
177+
host: MUD server host
178+
port: MUD server port
179+
"""
124180
async with MUDClient(name, role, host, port) as mud:
125181
print(f"Connected as {name}. Type 'help' for commands.")
126182
loop = asyncio.get_event_loop()

lcar_cartridge.py

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,25 @@
2121

2222
@dataclass
2323
class Cartridge:
24-
"""A swappable behavior cartridge."""
24+
"""A swappable behavior cartridge for MUD rooms.
25+
26+
Attributes:
27+
name: Unique identifier for this cartridge
28+
description: Human-readable description of what this cartridge does
29+
tools: List of available tools/commands for this cartridge
30+
onboarding_human: Welcome message for human agents
31+
onboarding_agent: Welcome message for AI agents
32+
git_repo: Optional GitHub repository URL for this cartridge
33+
"""
2534
name: str
2635
description: str
2736
tools: List[dict] = field(default_factory=list)
2837
onboarding_human: str = ""
2938
onboarding_agent: str = ""
3039
git_repo: str = ""
3140

32-
def to_dict(self):
41+
def to_dict(self) -> dict:
42+
"""Convert cartridge to dictionary for serialization."""
3343
return {
3444
"name": self.name,
3545
"description": self.description,
@@ -42,18 +52,32 @@ def to_dict(self):
4252

4353
@dataclass
4454
class Skin:
45-
"""A personality skin — maps to MUD formality + personality."""
55+
"""A personality skin that maps to MUD formality mode.
56+
57+
Attributes:
58+
name: Unique skin identifier
59+
description: Human-readable description of this personality
60+
formality: MUD formality level (NAVAL, PROFESSIONAL, TNG, CASUAL, MINIMAL)
61+
system_prompt_suffix: Optional suffix to add to system prompts
62+
temperature: AI temperature for this skin (0.0-1.0)
63+
tool_preferences: Optional weights for tool selection
64+
"""
4665
name: str
4766
description: str
48-
formality: str = "TNG" # NAVAL, PROFESSIONAL, TNG, CASUAL, MINIMAL
67+
formality: str = "TNG"
4968
system_prompt_suffix: str = ""
5069
temperature: float = 0.7
5170
tool_preferences: Dict[str, float] = field(default_factory=dict)
5271

5372

54-
@dataclass
73+
@dataclass
5574
class Scene:
56-
"""A complete scene: ROOM × CARTRIDGE × SKIN × MODEL × TIME"""
75+
"""A complete scene configuration: ROOM × CARTRIDGE × SKIN × MODEL × TIME.
76+
77+
Represents a room's active configuration at a specific time,
78+
combining cartridge (behavior), skin (personality), model (AI),
79+
and schedule (when this is active).
80+
"""
5781
room_id: str
5882
cartridge_name: str
5983
skin_name: str
@@ -63,18 +87,22 @@ class Scene:
6387

6488

6589
class CartridgeBridge:
66-
"""Bridges cartridge-mcp to FLUX-LCAR MUD."""
90+
"""Bridges cartridge-mcp to FLUX-LCAR MUD.
91+
92+
Manages cartridges, skins, and scenes, allowing MUD rooms
93+
to change behavior dynamically by loading different cartridges.
94+
"""
6795

68-
def __init__(self):
96+
def __init__(self) -> None:
97+
"""Initialize cartridge bridge with defaults."""
6998
self.cartridges: Dict[str, Cartridge] = {}
7099
self.skins: Dict[str, Skin] = {}
71100
self.scenes: List[Scene] = []
72-
self.active_scenes: Dict[str, Scene] = {} # room_id -> active scene
73-
74-
# Register built-in cartridges
101+
self.active_scenes: Dict[str, Scene] = {}
75102
self._register_defaults()
76103

77-
def _register_defaults(self):
104+
def _register_defaults(self) -> None:
105+
"""Register built-in cartridges and skins from JC1's cartridge-mcp."""
78106
# Cartridges from JC1's cartridge-mcp
79107
self.register_cartridge(Cartridge(
80108
name="spreader-loop",
@@ -138,20 +166,49 @@ def _register_defaults(self):
138166
self.register_skin(Skin("rival", "Competitive rival", "TNG", temperature=0.85))
139167
self.register_skin(Skin("field-commander", "Military field commander", "NAVAL", temperature=0.5))
140168

141-
def register_cartridge(self, cart: Cartridge):
169+
def register_cartridge(self, cart: Cartridge) -> None:
170+
"""Register a cartridge.
171+
172+
Args:
173+
cart: Cartridge instance to register
174+
"""
142175
self.cartridges[cart.name] = cart
143176

144-
def register_skin(self, skin: Skin):
177+
def register_skin(self, skin: Skin) -> None:
178+
"""Register a personality skin.
179+
180+
Args:
181+
skin: Skin instance to register
182+
"""
145183
self.skins[skin.name] = skin
146184

147185
def build_scene(self, room_id: str, cartridge: str, skin: str,
148186
model: str, schedule: str = "always") -> Scene:
187+
"""Build and register a new scene.
188+
189+
Args:
190+
room_id: Room identifier where this scene applies
191+
cartridge: Name of the cartridge to use
192+
skin: Name of the personality skin
193+
model: AI model to use for this scene
194+
schedule: When this scene should be active
195+
196+
Returns:
197+
The created Scene object
198+
"""
149199
scene = Scene(room_id, cartridge, skin, model, schedule)
150200
self.scenes.append(scene)
151201
return scene
152202

153203
def activate_scene(self, room_id: str) -> Optional[Scene]:
154-
"""Activate the best scene for a room based on current time."""
204+
"""Activate the best scene for a room based on current time.
205+
206+
Args:
207+
room_id: Room identifier to activate scene for
208+
209+
Returns:
210+
Scene if one was activated, None if no scenes available
211+
"""
155212
candidates = [s for s in self.scenes if s.room_id == room_id]
156213
if not candidates:
157214
return None
@@ -177,7 +234,14 @@ def activate_scene(self, room_id: str) -> Optional[Scene]:
177234
return scene
178235

179236
def get_mud_config(self, room_id: str) -> dict:
180-
"""Get the MUD room configuration from active scene."""
237+
"""Get the MUD room configuration from active scene.
238+
239+
Args:
240+
room_id: Room identifier to get config for
241+
242+
Returns:
243+
Configuration dictionary with cartridge, skin, model, schedule, and commands
244+
"""
181245
scene = self.active_scenes.get(room_id)
182246
if not scene:
183247
return {}

0 commit comments

Comments
 (0)