feat(mana-ai): wire shared-ai planner + real mana-llm calls (v0.2)

Service now produces plans end-to-end for due missions. Takes the
shared prompt/parser from @mana/shared-ai, calls mana-llm's
OpenAI-compatible endpoint, parses + validates the response against a
server-side tool allow-list.

- `src/planner/tools.ts` — hardcoded subset of webapp tools where
  policy === 'propose'. Mirror of `DEFAULT_AI_POLICY` in the webapp;
  drift just means the server doesn't suggest newly-added tools
  (graceful degradation). Contract test between the two lists is a
  sensible follow-up.
- `src/cron/tick.ts`
  - Iterates due missions, builds the shared Planner prompt per mission,
    parses the LLM response, logs the resulting plan
  - Per-mission try/catch so one flaky LLM response doesn't abort the
    queue; stats now track `plansProduced` + `parseFailures`
  - `serverMissionToSharedMission()` converts the projection shape to
    the shared-ai Mission type at the boundary
- `resolvedInputs: []` today — the Planner sees concept + objective +
  iteration history only. Full resolvers (notes/kontext/goals via
  Postgres replay) land alongside write-back in the next PR.
- No write-back yet: the plan is logged but not persisted to
  `sync_changes`. Write-back needs an RLS-scoped helper mirroring
  mana-sync's `withUser` pattern — tracked explicitly as the remaining
  open piece in CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-15 00:06:22 +02:00
parent 0d90b12d1c
commit 203fe3ef05
4 changed files with 185 additions and 27 deletions

View file

@ -0,0 +1,68 @@
/**
* Hardcoded allow-list of tools the server-side Planner may propose.
*
* The webapp owns the full tool registry (in
* `apps/mana/apps/web/src/lib/data/tools/registry.ts`) and the policy
* (`DEFAULT_AI_POLICY` in `data/ai/policy.ts`). This file mirrors the
* subset where policy === 'propose' so the mana-ai Bun service can
* build a valid prompt without importing Dexie-bound code.
*
* Drift risk: if the webapp adds a new proposable tool and this file
* isn't updated, the mana-ai Planner simply won't suggest it graceful
* degradation. A contract test that compares both lists would be a
* sensible follow-up.
*/
import type { AvailableTool } from '@mana/shared-ai';
export const AI_AVAILABLE_TOOLS: readonly AvailableTool[] = [
{
name: 'create_task',
module: 'todo',
description: 'Erstellt einen neuen Task mit optionalem Faelligkeitsdatum und Prioritaet',
parameters: [
{ name: 'title', type: 'string', description: 'Titel des Tasks', required: true },
{
name: 'dueDate',
type: 'string',
description: 'Faelligkeitsdatum (YYYY-MM-DD)',
required: false,
},
{
name: 'priority',
type: 'string',
description: 'Prioritaet',
required: false,
enum: ['low', 'medium', 'high'],
},
{ name: 'description', type: 'string', description: 'Beschreibung', required: false },
],
},
{
name: 'complete_task',
module: 'todo',
description: 'Markiert einen Task als erledigt',
parameters: [{ name: 'taskId', type: 'string', description: 'ID des Tasks', required: true }],
},
{
name: 'create_event',
module: 'calendar',
description: 'Erstellt einen Kalender-Event',
parameters: [
{ name: 'title', type: 'string', description: 'Event-Titel', required: true },
{ name: 'startIso', type: 'string', description: 'Start (ISO)', required: true },
{ name: 'endIso', type: 'string', description: 'Ende (ISO)', required: false },
],
},
{
name: 'create_place',
module: 'places',
description: 'Fügt einen neuen Ort hinzu',
parameters: [
{ name: 'name', type: 'string', description: 'Name des Ortes', required: true },
{ name: 'category', type: 'string', description: 'Kategorie', required: false },
],
},
];
export const AI_AVAILABLE_TOOL_NAMES = new Set<string>(AI_AVAILABLE_TOOLS.map((t) => t.name));