chore: snapshot current state before cleanup

This commit is contained in:
kacper 2026-03-14 12:10:39 -04:00
parent db4ce8b14f
commit 94e62c9456
14 changed files with 489 additions and 3929 deletions

View file

@ -62,6 +62,7 @@ class NanobotApiProcess:
self._reader: asyncio.StreamReader | None = None
self._writer: asyncio.StreamWriter | None = None
self._read_task: asyncio.Task | None = None
self._socket_inode: int | None = None
@property
def running(self) -> bool:
@ -72,6 +73,16 @@ class NanobotApiProcess:
and not self._read_task.done()
)
def matches_current_socket(self) -> bool:
if self._socket_inode is None:
return False
try:
return self._socket_path.stat().st_ino == self._socket_inode
except FileNotFoundError:
return False
except OSError:
return False
async def start(self) -> None:
if self.running:
await self._bus.publish(WisperEvent(role="system", text="Already connected to nanobot."))
@ -95,6 +106,7 @@ class NanobotApiProcess:
self._reader, self._writer = await asyncio.open_unix_connection(
path=str(self._socket_path)
)
self._socket_inode = self._socket_path.stat().st_ino
except OSError as exc:
await self._bus.publish(
WisperEvent(role="system", text=f"Could not connect to nanobot API socket: {exc}")
@ -107,7 +119,7 @@ class NanobotApiProcess:
async def send(self, text: str, metadata: dict[str, Any] | None = None) -> None:
if not self.running or self._writer is None:
await self._bus.publish(WisperEvent(role="system", text="Not connected to nanobot."))
return
raise RuntimeError("Not connected to nanobot.")
try:
await self._send_notification(
"message.send",
@ -120,10 +132,11 @@ class NanobotApiProcess:
except OSError as exc:
await self._bus.publish(WisperEvent(role="system", text=f"Send failed: {exc}"))
await self._cleanup()
raise RuntimeError(f"Send failed: {exc}") from exc
async def send_card_response(self, card_id: str, value: str) -> None:
if not self.running or self._writer is None:
return
raise RuntimeError("Not connected to nanobot.")
try:
await self._send_notification(
"card.respond",
@ -135,11 +148,12 @@ class NanobotApiProcess:
except OSError as exc:
await self._bus.publish(WisperEvent(role="system", text=f"Send failed: {exc}"))
await self._cleanup()
raise RuntimeError(f"Send failed: {exc}") from exc
async def send_command(self, command: str) -> None:
if not self.running or self._writer is None:
await self._bus.publish(WisperEvent(role="system", text="Not connected to nanobot."))
return
raise RuntimeError("Not connected to nanobot.")
try:
await self._send_notification(
"command.execute",
@ -151,6 +165,7 @@ class NanobotApiProcess:
except OSError as exc:
await self._bus.publish(WisperEvent(role="system", text=f"Send failed: {exc}"))
await self._cleanup()
raise RuntimeError(f"Send failed: {exc}") from exc
async def stop(self) -> None:
await self._cleanup()
@ -173,6 +188,7 @@ class NanobotApiProcess:
pass
self._writer = None
self._reader = None
self._socket_inode = None
async def _send_notification(self, method: str, params: dict[str, Any]) -> None:
assert self._writer is not None
@ -264,26 +280,35 @@ class SuperTonicGateway:
self._process = NanobotApiProcess(bus=self.bus, socket_path=self._socket_path)
await self._process.start()
async def _ensure_connected_process(self) -> NanobotApiProcess:
if self._process and self._process.running and self._process.matches_current_socket():
return self._process
if self._process:
await self._process.stop()
self._process = NanobotApiProcess(bus=self.bus, socket_path=self._socket_path)
await self._process.start()
if not self._process.running or not self._process.matches_current_socket():
raise RuntimeError("Not connected to nanobot.")
return self._process
async def send_user_message(self, text: str, metadata: dict[str, Any] | None = None) -> None:
message = text.strip()
if not message:
return
await self.bus.publish(WisperEvent(role="user", text=message))
async with self._lock:
if not self._process:
await self.bus.publish(WisperEvent(role="system", text="Not connected to nanobot."))
return
await self._process.send(message, metadata=metadata)
process = await self._ensure_connected_process()
await process.send(message, metadata=metadata)
async def send_card_response(self, card_id: str, value: str) -> None:
async with self._lock:
if self._process:
await self._process.send_card_response(card_id, value)
process = await self._ensure_connected_process()
await process.send_card_response(card_id, value)
async def send_command(self, command: str) -> None:
async with self._lock:
if self._process:
await self._process.send_command(command)
process = await self._ensure_connected_process()
await process.send_command(command)
async def disconnect_nanobot(self) -> None:
async with self._lock: