Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion community/pantry-pro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Background, later that session:
2. Inventory loads from persistent storage (`pantrypro_inventory.json`).
3. A specific ask is handled immediately (quick mode). A bare “pantry pro” greets with what's on hand and offers recipes.
4. Natural speech is classified by the LLM — add, used-up, list, recipes, shopping, tips.
5. Recipe search hits TheMealDB using your soonest-to-expire ingredient, then compares the ingredient list to stock.
5. Recipe search hits TheMealDB using your soonest-to-expire ingredient, then compares the ingredient list to stock. The LLM fallback also reads `user_profile.md` (diet, household size) — read-only, never written.
6. Say **done** to hand control back. The background daemon keeps watching expiry dates for the rest of the session.

### Background daemon
Expand Down
39 changes: 27 additions & 12 deletions community/pantry-pro/background.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,27 +75,38 @@ def _threshold_for(self, days: int) -> int:
return threshold
return -1

async def _load(self) -> dict:
async def _load(self):
# returns (data, load_ok). missing file is ok; a failed read is not.
try:
if await self.capability_worker.check_if_file_exists(STORAGE_FILE, False):
raw = await self.capability_worker.read_file(STORAGE_FILE, False)
parsed = json.loads(raw)
if isinstance(parsed, dict):
parsed.setdefault("items", [])
parsed.setdefault("shopping", [])
return parsed
exists = await self.capability_worker.check_if_file_exists(STORAGE_FILE, False)
if not exists:
return _empty_data(), True
raw = await self.capability_worker.read_file(STORAGE_FILE, False)
parsed = json.loads(raw)
if not isinstance(parsed, dict):
raise ValueError("inventory is not a json object")
parsed.setdefault("items", [])
parsed.setdefault("shopping", [])
return parsed, True
except Exception as e:
self.worker.editor_logging_handler.error(f"[PantryProBG] load failed: {e}")
return _empty_data()
return None, False

async def _save(self, data: dict):
async def _save(self, data: dict, *, load_ok: bool) -> bool:
if not load_ok:
self.worker.editor_logging_handler.error(
"[PantryProBG] save refused: inventory was not loaded cleanly"
)
return False
try:
await self.capability_worker.delete_file(STORAGE_FILE, False)
await self.capability_worker.write_file(
STORAGE_FILE, json.dumps(data), False
)
return True
except Exception as e:
self.worker.editor_logging_handler.error(f"[PantryProBG] save failed: {e}")
return False

def _alert_line(self, item: dict, days: int) -> str:
name = item.get("name", "food")
Expand All @@ -115,7 +126,11 @@ async def watch_loop(self):
await self.worker.session_tasks.sleep(POLL_INTERVAL)
continue

data = await self._load()
data, load_ok = await self._load()
if not load_ok:
await self.worker.session_tasks.sleep(POLL_INTERVAL)
continue

items = data.get("items") or []
if not items:
await self.worker.session_tasks.sleep(POLL_INTERVAL)
Expand All @@ -139,7 +154,7 @@ async def watch_loop(self):
changed = True

if changed:
await self._save(data)
await self._save(data, load_ok=True)

if not nudge_items:
await self.worker.session_tasks.sleep(POLL_INTERVAL)
Expand Down
Loading
Loading