managarten/services/mana-ai/src/config.ts
Till JS 23b8cc13fb feat(ai-tools): server-side web-research + contacts for agents
Two major tool expansions — the Recherche-Agent and Today-Agent can
now research the web autonomously (no browser needed), and a future
Meeting-Prep agent can read + create contacts.

=== research_news (server-side execution) ===

The biggest addition: mana-ai can now call mana-api's news-research
endpoints (POST /discover + /search) directly, without a browser.

Infrastructure:
- services/mana-ai/src/planner/news-research-client.ts — full HTTP
  client with discover→search pipeline. 15s/30s timeouts. Graceful
  null on any failure (network, mana-api down, bad response) so the
  tick never crashes from research errors.
- config.manaApiUrl added (default http://localhost:3060); wired in
  docker-compose.macmini.yml as http://mana-api:3060 + depends_on
  mana-api with service_healthy condition.

Pre-planning research step (cron/tick.ts):
- Before the planner prompt is built, the tick checks if the
  mission's objective or conceptMarkdown matches research keywords
  (same RESEARCH_TRIGGER regex the webapp uses). When it matches:
  * NewsResearchClient.research(objective) runs discovery + search
  * Results are injected as a synthetic ResolvedInput with id
    '__web-research__' and a formatted markdown context block
  * The Planner then sees real article URLs/titles/excerpts and can
    reference them in create_note / save_news_article steps
  * Log line: "pre-research: N feeds, M articles"

Tool registration:
- research_news added to AI_PROPOSABLE_TOOL_NAMES + mana-ai tools.ts
  with params (query, language?, limit?). This lets the planner also
  explicitly propose a research step as a PlanStep (in addition to
  the pre-planning auto-injection).

=== create_contact ===

- Added to AI_PROPOSABLE_TOOL_NAMES + mana-ai tools.ts with params
  (firstName required, lastName/email/phone/company/notes optional).
- Contacts are encrypted at rest; server planner can plan the step
  but execution stays on the webapp (same as all propose tools).
  Full server-side contact resolution via Key-Grant is a future
  enhancement.
- get_contacts added to webapp AUTO_TOOLS so agents can inspect
  existing contacts without nagging (read-only, auto-policy).

Module coverage now:
   todo (5)   calendar (2)   notes (5)   places (4)
   drink (3)  food (2)       news (1)    journal (1)
   habits (3)  news-research (1)  contacts (1)

  11 modules, 28 tools total (17 propose, 11 auto).

Tests: mana-ai 41/41 (drift-guard passes), shared-ai type-check
clean, webapp svelte-check 0 errors, 0 warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:25:45 +02:00

63 lines
2.4 KiB
TypeScript

/**
* Env-driven config for the mana-ai service.
*
* Only references the secrets/URLs the tick loop needs. Auth is
* service-to-service via MANA_SERVICE_KEY (same pattern as mana-credits,
* mana-user); no end-user JWTs reach this service.
*/
export interface Config {
port: number;
/** mana_sync DB — source of Mission rows (via sync_changes replay). */
syncDatabaseUrl: string;
/** mana-llm HTTP endpoint (OpenAI-compatible). */
manaLlmUrl: string;
/** Unified mana-api (Hono/Bun, port 3060). Hosts module-specific
* compute endpoints including news-research. Used by the pre-planning
* research step to feed web-research context into the planner prompt
* before it produces plan steps. */
manaApiUrl: string;
/** Shared key for service-to-service calls. */
serviceKey: string;
/** How often the background tick scans for due Missions, in ms. */
tickIntervalMs: number;
/** Flip to false to boot the HTTP surface without the background tick
* — useful for local smoke-tests + Docker image build verification. */
tickEnabled: boolean;
/**
* PEM-encoded RSA-OAEP-2048 private key for unwrapping Mission Grants.
* Paired with the public key pinned in mana-auth's config. Provision
* via Docker secret / out-of-band env; never commit.
*
* Optional at boot so the service can start without grant support
* (development, legacy deployments). When absent, Missions that
* carry a Grant are skipped with state='grant-missing'.
*
* Generate with:
* openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out priv.pem
* openssl pkey -in priv.pem -pubout -out pub.pem
*/
missionGrantPrivateKeyPem?: string;
}
function requireEnv(key: string, fallback?: string): string {
const value = process.env[key] ?? fallback;
if (!value) throw new Error(`Missing required env var: ${key}`);
return value;
}
export function loadConfig(): Config {
return {
port: parseInt(process.env.PORT ?? '3067', 10),
syncDatabaseUrl: requireEnv(
'SYNC_DATABASE_URL',
'postgresql://mana:devpassword@localhost:5432/mana_sync'
),
manaLlmUrl: requireEnv('MANA_LLM_URL', 'http://localhost:3020'),
manaApiUrl: requireEnv('MANA_API_URL', 'http://localhost:3060'),
serviceKey: requireEnv('MANA_SERVICE_KEY', 'dev-service-key'),
tickIntervalMs: parseInt(process.env.TICK_INTERVAL_MS ?? '60000', 10),
tickEnabled: process.env.TICK_ENABLED !== 'false',
missionGrantPrivateKeyPem: process.env.MANA_AI_PRIVATE_KEY_PEM || undefined,
};
}