Conversation
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@v4...v6) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](actions/setup-python@v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the npm-minor-patch group in /frontend-svelte with 3 updates: [svelte-spa-router](https://github.com/ItalyPaleAle/svelte-spa-router), [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) and [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite). Updates `svelte-spa-router` from 5.0.0 to 5.0.1 - [Release notes](https://github.com/ItalyPaleAle/svelte-spa-router/releases) - [Changelog](https://github.com/ItalyPaleAle/svelte-spa-router/blob/main/CHANGELOG.md) - [Commits](ItalyPaleAle/svelte-spa-router@v5.0.0...v5.0.1) Updates `svelte` from 5.55.0 to 5.55.4 - [Release notes](https://github.com/sveltejs/svelte/releases) - [Changelog](https://github.com/sveltejs/svelte/blob/main/packages/svelte/CHANGELOG.md) - [Commits](https://github.com/sveltejs/svelte/commits/svelte@5.55.4/packages/svelte) Updates `vite` from 8.0.3 to 8.0.8 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v8.0.8/packages/vite) --- updated-dependencies: - dependency-name: svelte-spa-router dependency-version: 5.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: npm-minor-patch - dependency-name: svelte dependency-version: 5.55.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: npm-minor-patch - dependency-name: vite dependency-version: 8.0.8 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: npm-minor-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat(presence): server-side last_seen stamping for non-CC agents (#279) Non-CC agents (Codex, GLM) never call the send_heartbeat MCP tool, so their presence showed stale despite active streaming sessions. Stamp agents.last_seen_at server-side on every evidence of a working pipe: broker delivery (inbound + inter-agent), and turn completion/error in both StreamingSession and CodexSession. - Add last_seen_at column + Agent.last_seen_at field + stamp_last_seen() - _agent_presence takes max(server_ts, heartbeat_ts) — heartbeat path preserved so CC agents keep working unchanged - Plumb AgentRegistry into StreamingSession/CodexSession via new `registry` kwarg (mirrors analytics_store pattern) Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> * fix(presence): honor server stamp in status + stamp on codex error event Addresses murzik's PR #280 review findings: 1. _agent_presence() status branch previously returned "unknown" for any agent without a heartbeat — even when last_seen_at was recently stamped by server-side activity. Status now falls through to server_ts when no fresher heartbeat exists, using the same online/idle/offline age bands. Heartbeat logic still wins when hb_ts >= server_ts so dead/non-alive heartbeats aren't masked by an older server stamp. 2. codex_session "error" event path now stamps last_seen. Previously only turn.completed and turn.failed stamped, so transport/runtime errors surfaced via the generic error event missed a real liveness signal. Tests: - tests/test_api.py — 5 new presence tests (server-stamped online/idle/ offline, no-stamp-no-hb unknown, heartbeat-wins-when-fresher) - tests/test_codex_session.py — 3 new stamp tests (error event stamps, plus regression guards for turn.completed and turn.failed) 1637 passed, ruff clean. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> --------- Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
…#285) (#297) search_by_embedding silently returned [] when the query embedding had zero norm, making it impossible for callers to distinguish a broken embedding from a legitimate 'no matches found' result. - Add InvalidQueryEmbeddingError(ValueError) in pinky_memory.store. - search_by_embedding_scored raises on empty or zero-norm queries. - Inner _search_by_numpy keeps a defense-in-depth raise on zero-norm. - MCP recall() in pinky_memory.server catches and logs the error before falling back to keyword search, so broken embeddings surface in logs instead of vanishing. - Add regression tests covering zero-norm and empty query embeddings. Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
) * fix(outreach): let httpx set Discord multipart content type (#283) Discord file uploads crashed because the adapter set `Content-Type: application/json` as a Client-level default, then tried to override it per-request with `headers={"Content-Type": None}`. httpx rejects `None` as a header value and raises before sending, so DiscordAdapter.send_file() never worked. - Drop the Client-level Content-Type default; httpx now sets the correct content type per request (application/json for json= bodies, multipart/form-data with boundary for files= bodies). - Remove the invalid `Content-Type: None` override in send_file(). - Add a regression test that exercises the real send_file() path via httpx.MockTransport and asserts a proper multipart request is emitted. Co-Authored-By: Murzik <noreply@pinkybot.ai> Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> * style: ruff isort fix for test_discord.py Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> --------- Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Murzik <noreply@pinkybot.ai> Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
… (#299) * fix(memory): validate and log embed() failures on reflect/recall (#291) embed() failures (empty returns or raised exceptions) previously stored empty embeddings silently. Downstream semantic search quietly missed those memories with no signal that anything was wrong. - Add _safe_embed() helper in pinky_memory.server that: - Skips the call entirely for NoOpEmbeddingClient (expected downgrade) - Catches exceptions from real embedders and logs with type + message - Logs a "degraded" warning when a non-NoOp embedder returns [] - reflect() now surfaces `embedded: bool` in its JSON response so callers can see when a memory was stored without a usable vector. - recall() routes through the same helper — failures no longer masquerade as "no matches"; keyword fallback proceeds with a visible log entry. - Add regression tests for: NoOp embedder, real embedder returning value, real embedder returning [] (degraded log), and embedder raising. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> * fix(memory): treat zero-norm and non-finite embeddings as degraded (#291) Murzik review: a real embedder returning a non-empty but zero-norm (e.g., [0.0, ..., 0.0]) or non-finite (NaN/inf) vector passed the previous `if not embedding` check, so reflect() reported embedded=True and stored an unusable vector. Downstream search then silently skipped those rows via the existing zero-norm row guard in _search_by_numpy — reproducing the original silent-degrade bug with different inputs. - Extend _safe_embed() to validate non-empty vectors: zero-norm or any non-finite component → log "degenerate" and return [] so reflect() reports embedded=False and stores no vector. - Add regression tests for [0.0]*8 and NaN-containing embeddings. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> Co-Authored-By: Murzik <noreply@pinkybot.ai> --------- Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Claude Opus 4 <noreply@anthropic.com> Co-authored-by: Murzik <noreply@pinkybot.ai>
… (#300) sqlite-vec query exceptions were swallowed and _search_by_numpy was invoked silently. Users got slower, possibly differently ordered search with no signal that vec search was broken. - Log a WARNING via pinky_memory.store logger with exception type and message before falling back to numpy. - Track a per-instance `_vec_fallback_count` bumped on every fallback. - Surface `vec_available` and `vec_fallback_count` in introspect() so health checks / dashboards can detect a degraded vec backend. - Regression test simulates a vec failure via a connection wrapper and asserts the warning is logged, counter incremented, introspect reports it, and numpy fallback still returns results. Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
WhatsAppAdapter.download_file() wrote the raw response body to disk without checking the second-stage media GET status. 401/404 responses (HTML or Graph API error JSON) were persisted as wa_<media_id>.<ext> files, silently corrupting the media cache. - Raise WhatsAppError on any status_code >= 400 from the media download. - Extract Graph API error message/code when the response is JSON; fall back to the raw HTTP status otherwise. - Existing happy-path test now asserts status_code=200 explicitly so the new guard is exercised. - New regression asserts a 401 Expired-token response raises with the correct error_code/message and leaves no wa_<id>.* file behind. Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Murzik <noreply@pinkybot.ai> Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
Telegram media senders (send_photo, send_document, send_animation) now filter None-valued fields from multipart form data. Passing reply_to_message_id=None was being serialized as the literal string "None" in the multipart body, which Telegram's API rejected or treated as an invalid message id. Patch authored by Murzik; applied + committed with attribution. Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Murzik <noreply@anthropic.com>
KB self-tools now URL-encode search query params and path variables: - `kb_search` builds its querystring via urllib.parse.urlencode so that queries containing `&`, `#`, `?`, or spaces round-trip correctly instead of silently injecting into the request URI. - `kb_get_wiki` / `kb_save_wiki` / `kb_delete_wiki` quote the slug with `safe="/"` — nested slugs like `topics/llm-knowledge-bases` still work, but spaces / specials inside a segment are escaped. - `kb_delete_raw` / `kb_update_raw` quote the raw source id as a single path segment (`safe=""`), so ids containing `/` or other specials no longer break routing. Added regressions for query injection, wiki slug specials, and raw source IDs containing `/` + space. Patch authored by Murzik; applied + committed with attribution. Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Murzik <noreply@anthropic.com>
`get_events()` now parses start_date / end_date inside its error
boundary and returns JSON `{error: ...}` for invalid ISO dates
instead of letting `ValueError` escape the MCP tool (which surfaces
as an unhandled exception to the caller).
Added a new `tests/test_calendar_server.py` with a regression for
invalid `start_date`.
Patch authored by Murzik; applied + committed with attribution.
Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local>
Co-authored-by: Murzik <noreply@anthropic.com>
…es (#286) (#307) `voice_routes.voice_websocket` was handing `_voice_store.get_session(...)` directly to `finalize_call()` in the WS disconnect path. `get_session()` can legitimately return None under session-expiry races (session was cleaned up, or was never committed after an earlier failure), and `finalize_call()`'s first line accesses `session.call_sid` — boom, `AttributeError: 'NoneType' object has no attribute 'call_sid'`, and the whole finalize path (Opus review + artifact + owner notification) breaks. Two changes, belt + suspenders: 1. Call site (`voice_routes.py`): look up the session before scheduling `finalize_call`, skip + log if it's gone. This prevents the task from being scheduled in the first place. 2. Callee (`voice_engine.py::finalize_call`): accept None defensively and early-return with a log line. Any future caller that forgets the guard gets a clean no-op instead of a crash. Added tests/test_voice_engine.py covering: - `finalize_call(None, ...)` no-ops and does not touch voice_store / agents / broker - `finalize_call(real_session, ...)` still proceeds past the guard Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
#294) (#308) `datetime.utcnow()` is deprecated as of Python 3.12 (will emit DeprecationWarning and eventually be removed). Naive `datetime.now()` silently picks up local tz, which breaks any cross-tz serialization or comparison and — in the case of streaming_session.py:300 — labelled a local-time string as "UTC". Five call sites swept: - `pinky_daemon/api.py:2946` — version string now uses `now(timezone.utc)` so the built version is deterministic regardless of host tz. - `pinky_daemon/api.py:6966` — web/DM timestamp fallback uses `now(utc)`. - `pinky_daemon/api.py:7026` — broker-message timestamp fallback uses `now(utc)`. - `pinky_daemon/streaming_session.py:300` — fallback labelled UTC now actually uses UTC (was implicit local-tz bug). - `pinky_memory/kg_extractor.py:449` — hoisted out of the per-triple loop into a single cached `_now_ymd` value per `_resolve_functional_conflict` call. Already tz-aware; this is just the "cache once per call" cleanup flagged in the issue. Verified: `grep -n 'datetime\\.utcnow\\|datetime\\.now()'` returns only comments. Full test suite for touched areas (test_api, test_kg_extractor, test_streaming_session) — 305 passed. Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
#289) (#301) _detect_embedding_dimensions() and _backfill_vec() both called json.loads() on stored embedding blobs with no guard. A single corrupted row crashed dimension detection (blocking startup) or logged nothing in backfill. - _detect_embedding_dimensions() now scans up to 50 newest-first rows, skips malformed JSON with a logger.warning carrying the reflection id, and returns the first valid embedding's length. - _backfill_vec() catches ValueError/TypeError on json.loads, logs the reflection id, counts corrupt rows, and reports them in the completion message. The bare `except Exception: pass` on the insert step is replaced with a targeted sqlite3.Error catch + debug log. - Regression test corrupts one row's embedding, makes it the newest, asserts _detect_embedding_dimensions still returns 8 from the good row and that the bad row id appears in the warning log. Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
…295) (#309) Closes #295. Replaces 10 `except:` / `except Exception:` sites with specific exception classes (mostly `sqlite3.OperationalError`) plus logger calls at appropriate severity levels. Preserves fallback semantics — callers still get numpy/LIKE fallbacks, migrations stay idempotent. The one site calling user-supplied `llm_classify` keeps a broad catch with a noqa BLE001 + justification since the callable can raise anything. Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Murzik <noreply@anthropic.com>
…287) (#306) * fix(oauth): validate state nonce on legacy Google Calendar callback (#287) **Security fix (high).** The legacy `/calendar/google/callback` endpoint accepted `state` but never compared it against any stored value before exchanging the authorization code and saving tokens. A crafted callback hitting the owner's browser with an attacker-controlled code could bind the wrong Google account. Changes: - New `/calendar/google/direct-auth-url` endpoint that calls the existing `pinky_calendar.oauth.get_auth_url()` *and* persists the returned state nonce with an ISO timestamp under `GOOGLE_OAUTH_STATE_{nonce}` (TTL 10m). - `/calendar/google/callback` now requires + validates + single-use-consumes the state nonce before any client-credential lookup or code exchange. Missing / unknown / replayed / expired / corrupt / future-dated states are all hard-rejected with HTTP 400 + a readable HTML error page. - State is deleted immediately on consumption (even on exchange failure or replay attempt), preventing TOCTOU reuse. - `pinky_calendar.oauth.exchange_code`: updated the misleading "validated by caller" marker to a real SECURITY note that spells out the caller's obligation. Tests (`TestGoogleOAuthStateValidation`): missing state, unknown/forged state, expired state (purged afterward), replay rejection, state consumption on exchange failure, direct-auth-url state persistence, direct-auth-url credential gate. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> * fix(oauth): fail closed on state consume + reject naive timestamps (#287) Addresses Murzik's review on PR #306: **High — consume must fail closed.** `_consume_google_oauth_state()` was ignoring both the return value of `delete_setting()` and any exception it raised. That meant: - If a concurrent consumer won the race (e.g. legitimate callback + attacker replay fired in parallel), *both* could proceed past the validation check. - If deletion itself failed (DB locked, disk full, any OperationalError), the request still proceeded to token exchange. DELETE is now the authoritative consume step: - Returns False (row not deleted) → "unknown or replayed state" - Raises → "could not consume state" Either way, `exchange_code()` is never invoked. **Medium — naive timestamps.** `datetime.fromisoformat()` accepts ISO strings without a tz suffix and returns a naive datetime. Subtracting a naive dt from an aware `now()` raises TypeError → the endpoint 500s instead of returning the promised 400. We only ever *issue* aware UTC timestamps, so naive records now hard-reject as "corrupt state record". Regressions added: - `test_consume_fails_closed_when_delete_returns_false` — simulates concurrent consumer, asserts callback 400 + `exchange_code` not called. - `test_consume_fails_closed_when_delete_raises` — simulates DB failure, asserts callback 400 + `exchange_code` not called. - `test_callback_rejects_naive_timestamp_state` — seeds a naive ISO string, asserts 400 + row purged. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com> --------- Co-authored-by: Oleg <oleg@Olegs-Mac-mini.local> Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
| assert resp.status_code == 200 | ||
| body = resp.json() | ||
| assert body["state"] == "xyz" | ||
| assert "accounts.google.com" in body["auth_url"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Beta → stable release. 14-bug reliability sweep + presence + dependabot housekeeping.
Bug Sweep (13 fixes, #306 is the 14th)
Memory / embeddings
Outreach / messaging
Voice / calendar / self
Features
Housekeeping
Test plan
🤖 Opened by Barsik