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
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ TELEGRAM_ENABLED=false
# KOKORO_TTS_BASE_URL=http://100.x.y.z:8880
# KOKORO_TTS_VOICE=af_heart

# Optional Google Health read-only connector for private Telegram chats.
# Optional Google Health connector for private Telegram chats. It provides
# read-only summaries and, when both nutrition scopes are granted, exports
# future meal logs, edits, and deletions. Existing connections must reconnect
# to grant the nutrition scopes; older meals are never backfilled.
# Configure a Google Cloud OAuth web client and enable Google Health first.
# Production must use an HTTPS callback reachable by the user:
# GOOGLE_HEALTH_REDIRECT_URI=https://api.example.com/integrations/google-health/callback
Expand Down
35 changes: 26 additions & 9 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,15 @@ The HTTPS callback consumes the state, exchanges the code, resolves Google's
server-side identity, and stores only an encrypted refresh token plus safe
connection metadata. A bounded background job refreshes and reads recent data,
normalizes it into daily SQLite records, and the Telegram commands and
`get_health_summary` tool read those normalized records. Tokens, raw provider
payloads, and provider identifiers never travel through Telegram messages.
`get_health_summary` tool read those normalized records. When both nutrition
scopes are granted, meal mutations from eligible private chats also enqueue
durable, account-bound Google `nutrition-log` revisions. The export worker
retries pending work independently of health imports, preserves operation
ordering per meal, and exposes safe pending, synced, failed, and
authorization-required counts. Tokens, raw provider payloads, meal
descriptions, and provider identifiers never enter application logs or traces;
the user's own meal text can still appear in the private Telegram conversation
as the normal result of logging a meal.

Long polling is outbound. It does not require a public webhook, domain, TLS
certificate, or inbound application port.
Expand Down Expand Up @@ -93,6 +100,7 @@ Blacki uses different stores for different responsibilities:
| Optional Mem0 memory with Qdrant Cloud | Managed Qdrant | Provider-managed |
| Zepto OAuth credentials | `/app/data/credentials/zepto-mcp-remote/` | Yes with the Compose volume |
| Google Health refresh tokens and normalized summaries | SQLite (`tools.db`), tokens encrypted at rest | Yes with the Compose volume |
| Google Health meal export revisions and retry state | SQLite (`tools.db`), payloads account-bound and sent over HTTPS | Yes with the Compose volume |
| Application logs and traces | JSON files under `/app/logs` | Yes with the Compose volume |

Compose maps `.adk_state/`, `data/`, and `logs/` from the host. Back up the
Expand Down Expand Up @@ -138,13 +146,22 @@ permissions; they are not encrypted. Shopping prompts, tool calls, and results
remain in the local ADK session database and are sent to the configured model
as part of normal agent execution.

Google Health is a separate read-only boundary. It uses the current Google
Health API, not the legacy Fitbit Web API. The connector requests only current
read-only activity/fitness, measurements, and sleep scopes; it handles missing
or partially imported categories as unavailable. Health commands reject group
chats, and the summary tool requires private Telegram session state.
`/disconnect_health` requires an explicit inline-button confirmation before
local deletion.
Google Health summaries are a separate read-only boundary. The connector uses
the current Google Health API, not the legacy Fitbit Web API. It requests the
current read-only activity/fitness, measurements, and sleep scopes plus
`googlehealth.nutrition.readonly` and `googlehealth.nutrition.writeonly` for
optional meal export. It handles missing or partially imported categories as
unavailable. Health commands reject group chats, and the summary tool requires
private Telegram session state. Meal export has no historical backfill and
keeps local save status separate from remote sync status. `/disconnect_health`
requires an explicit inline-button confirmation, cancels future meal sync,
retains local calorie logs, and does not purge records already sent to Google;
requests already submitted may still complete.

The import and meal-export jobs share the existing scheduler process but remain
independent: health imports use the configured interval and meal export runs
every minute. Run only one active scheduler process per `tools.db`; the
deployment does not claim cross-process dispatch leases.

### Sandbox credential threat model

Expand Down
30 changes: 25 additions & 5 deletions docs/base-infra/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,31 @@ identities.
| `GOOGLE_HEALTH_OAUTH_STATE_TTL_SECONDS` | `600` | Lifetime of one-time OAuth state |

Blacki requests the current Google Health read-only activity/fitness,
health-metrics/measurements, and sleep scopes. Do not paste the client secret
or Fernet key into logs, chat, or source control. The callback URL must exactly
match the Google Cloud OAuth client configuration. The Apple Health-to-Google
Health or Fitbit import step is configured separately by the user and may be
incomplete; Blacki does not access HealthKit directly.
health-metrics/measurements, and sleep scopes plus
`googlehealth.nutrition.readonly` and `googlehealth.nutrition.writeonly`. The
read-only scopes support summaries. Both nutrition scopes are
required for automatic export of future private-chat meal logs, edits, and
deletions. Existing connections must reconnect to request the added scopes;
Blacki never backfills meals logged before consent. Do not paste the client
secret or Fernet key into logs, chat, or source control. The callback URL must
exactly match the Google Cloud OAuth client configuration. The Apple
Health-to-Google Health or Fitbit import step is configured separately by the
user and may be incomplete; Blacki does not access HealthKit directly.

The v4 discovery document lists `nutrition-log` as the write data type. Blacki
exports only the fields already present in a meal entry: food name, kcal,
available protein/carbohydrate/fat values, meal type, and the selected local
date. Unknown nutrients are omitted and no food database lookup is performed.
This contract was checked against Google's [Health scopes][health-scopes],
[v4 discovery document][health-discovery], [nutrition data type][health-nutrition],
and [data point REST reference][health-datapoints] before rollout. Do not
enable live health-record writes until the OAuth project has the nutrition
scopes configured and the resulting consent is verified.

[health-scopes]: https://developers.google.com/health/scopes
[health-discovery]: https://health.googleapis.com/$discovery/rest?version=v4
[health-nutrition]: https://developers.google.com/health/data-types/nutrition
[health-datapoints]: https://developers.google.com/health/reference/rest/v4/users.dataTypes.dataPoints

## Search and browser tools

Expand Down
52 changes: 39 additions & 13 deletions docs/telegram-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,30 +87,56 @@ retains the model's tool call and arguments.
### Optional Connect Google Health

Blacki can read normalized health summaries after a user completes Google OAuth
from a private Telegram chat. This is intentionally named **Connect Google
Health**: Blacki does not request Apple ID credentials, access HealthKit, scrape
Fitbit, or receive arbitrary Apple Health records. The user must first configure
an Apple Health-to-Google Health/Fitbit-compatible import path if their account
and app version support it.
from a private Telegram chat. If the user grants both nutrition permissions,
Blacki also exports future meal logs, edits, and deletions from that private
chat. This is intentionally named **Connect Google Health**: Blacki does not
request Apple ID credentials, access HealthKit, scrape Fitbit, or receive
arbitrary Apple Health records. The user must first configure an Apple
Health-to-Google Health/Fitbit-compatible import path if their account and app
version support it.

Configure the Google Cloud OAuth web client and the `GOOGLE_HEALTH_*` values in
[Configuration](base-infra/environment-variables.md), then set the callback URL
to the exact public HTTPS URL. In Telegram:

1. Send `/connect_health` in a private chat.
2. Open the one-time Google authorization link and grant only the requested
read-only categories.
2. Open the one-time Google authorization link. Grant the read-only categories
for summaries. Grant both `googlehealth.nutrition.readonly` and
`googlehealth.nutrition.writeonly` if you want future meal export. Existing
connections must reconnect to add these nutrition permissions. The nutrition
read permission lets Blacki verify records it created by exact data point
ID; it does not import unrelated food logs.
3. Return to Telegram and use `/health_refresh` for an on-demand sync or
`/health_summary` for the latest stored records.
4. Use `/disconnect_health`, then confirm the button, to revoke the token
best-effort and delete Blacki's stored token, normalized records, and pending
OAuth state.
4. Log meals normally. Eligible new meals show a `google_health_sync` status;
`pending` is retried in the background, `synced` confirms the remote write,
`authorization_required` asks you to reconnect, and `failed` remains visible
for follow-up. A local Blacki save is still successful when remote sync is
pending or fails, and the meal must not be logged again.
5. Use `/disconnect_health`, then confirm the button, to revoke the token
best-effort, cancel pending meal sync, and remove Blacki's stored token and
normalized health summaries. Local calorie logs remain. Blacki does not
delete records already sent to Google Health, and requests already submitted
may still finish.

The background sync runs every 12 hours by default and fetches a bounded recent
window so late device imports can replace earlier daily records. Missing values
are omitted rather than guessed. Stored data is limited to normalized daily
activity, workout, sleep, heart-rate, weight, and body-fat summaries; raw
Google payloads and provider IDs are not persisted in the summary table.
Google payloads and provider IDs are not persisted in the summary table. Meal
exports are persisted separately with retry state and opaque data point IDs;
there is no historical backfill. The meal export worker runs every minute
independently of health imports. Run only one active scheduler process per
`tools.db` so a deployment does not dispatch duplicate work.

Google's v4 discovery document currently lists `nutrition-log` as a supported
data type. Blacki writes only the local meal description, kcal, available
macros, meal type, and selected local date; it omits unknown nutrients and does
not substitute food-database estimates. See Google's [nutrition data type]
and [data point REST reference] for the provider contract.

[nutrition data type]: https://developers.google.com/health/data-types/nutrition
[data point REST reference]: https://developers.google.com/health/reference/rest/v4/users.dataTypes.dataPoints

Google Health availability does not prove that a particular Apple Health metric
was imported. Test the desired categories on a non-production account before
Expand Down Expand Up @@ -145,10 +171,10 @@ Open the bot in Telegram and send:
| `/model` | Open the model and thinking settings panel |
| `/thinking` | Open the supported reasoning-effort choices for the active model |
| `/reset` | Start a fresh conversation session |
| `/connect_health` | Send a Google Health authorization link |
| `/connect_health` | Send a Google Health authorization link and consent for optional meal sync |
| `/health_refresh` | Fetch recent Google Health data (rate limited) |
| `/health_summary` | Show normalized daily records and trends |
| `/disconnect_health` | Confirm disconnection and local health-data deletion |
| `/disconnect_health` | Confirm disconnection and cancellation of meal sync |

Then send a normal message and confirm the model responds. Blacki does not
currently implement a `/clear` command.
Expand Down
Loading
Loading