Commit graph

66 commits

Author SHA1 Message Date
Till JS
59373c0d57 chore(articles): hygiene pass — shared-ai actor + lib/sync-db + metrics (#5,#7,#11)
#5 — SYSTEM_ARTICLES_IMPORT_WORKER hoisted into @mana/shared-ai
   The worker built its actor inline, bypassing the SystemSource union
   that's the blessed list for system-write principals. Now uses
   makeSystemActor(SYSTEM_ARTICLES_IMPORT_WORKER) like every other
   server-side system writer (mission-runner, projection, …).

#7 — sync-db helper hoisted out of mcp/ into lib/
   Implementation moved to apps/api/src/lib/sync-db.ts; mcp/sync-db.ts
   is a re-export shim so existing MCP imports keep working. Articles
   bulk-import + future modules import from lib/ directly — no more
   "articles depending on mcp" layering smell.

#11 — Prometheus metrics for the worker
   New counters + histogram in lib/metrics.ts under
   mana_api_articles_import_*:
     - ticks_total{result=processed|skipped|error}
     - items_total{result=extracted|error|consent_wall|cancelled}
     - extract_duration_seconds (histogram, 0.25–30s buckets)
     - jobs_completed_total{result=done}
     - pickup_gc_rows_total
   Worker tick + extractor instrumented at the right transition points.
   Steady-state pickup_gc_rows_total > 0 over time signals a stuck
   consumer somewhere — useful operator alert.

Plan: docs/plans/articles-bulk-import.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 01:06:15 +02:00
Till JS
0d85d7c36b feat(forms): M5 AI tools — 7 tools im AI_TOOL_CATALOG
AI-Zugriff aufs Forms-Modul (docs/plans/forms-module.md M5):

Propose (User-Approval erforderlich):
- forms_create — neues Formular im Draft-Status, optional mit Feldern.
  Field-Shape im params-Array: { type, label, required?, helpText?,
  options?: [{label}] }. Type-Enum aus dem 11-Typ-Katalog. Planner
  kann z.B. "Vereins-Anmeldung" mit Name+Email+Position+Trikotgröße
  in einem Aufruf bauen.
- forms_add_field — Feld ans Ende anhängen, Reorder bleibt User
  vorbehalten (Drag im Builder).
- forms_publish — draft → published. Wirft, wenn Form keine Antwort-
  felder hat (nur section/consent würde Public-Submit sinnlos machen).
- forms_close — published → closed, Antworten + Share-Link bleiben.

Auto (silent execution während Planner-Reasoning):
- forms_list — Metadaten (id, title, status, fieldCount, responseCount,
  visibility), Status-Filter optional, Default-Limit 50. VaultLocked-
  aware → klare Fehlermeldung statt Crash.
- forms_get_responses — Aggregat-Stats: per Form ein
  ResponseAggregate {totalCount, statusCounts, choiceHistograms,
  textSamples, numericStats}. Choice-Felder mit Option-Label-Mapping
  (nicht Option-IDs), Text-Felder als Sample-Array (cap 50, default).
- forms_summarize_responses — gleicher Aggregator mit window-filter
  (sinceDays) und höherem Sample-Cap (200), als Daten-Vorlage für
  LLM-Clustering im nächsten Planner-Schritt. Augur-style: keine
  eigene LLM-Roundtrip, der Planner formuliert Themes selbst.

Verdrahtung:
- AI_TOOL_CATALOG in @mana/shared-ai mit 7 ToolSchema-Einträgen +
  defaultPolicy.
- ModuleTool-Implementierungen in modules/forms/tools.ts mit
  scopedForModule für Space-Awareness, decryptRecords für encrypted-
  table-Reads, VaultLocked-Handling.
- Registriert in data/tools/init.ts.

Validierung:
- mana-ai planner-drift test: 4/4 grün — alle 4 propose-Tools
  (forms_create/add_field/publish/close) im SERVER_TOOLS-Subset.
- svelte-check 0 errors in forms/.
- Forms unit tests: 16/16 (csv + branching) unverändert grün.

Tools-executor.test.ts ist pre-existing rot wegen
$lib/modules/context-Drift in module-registry.ts (Parallel-Session-
WIP, nicht durch mich).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:33:55 +02:00
Till JS
8fbdc6db77 feat(notes): isSpaceContext flag replaces kontext module (Option B)
Retire the kontext module entirely; the per-Space standing-context
document is now a regular Note flagged with `isSpaceContext: true`.
Daily use ("URL → Notiz") moves to the notes module as a first-class
action; the same primitive is reused by the (planned) Brand/Firma-Space
onboarding wizard to seed a Space-context Note from a URL.

Why: kontext was inconsistent — its UI was a URL-crawler that wrote
to userContext.freeform (profile module), while its kontextDoc table
+ AI-Mission-Runner auto-injection was a write-only shell with no
real editor. One concept (Notes) now carries both ad-hoc noting and
Space-context, with mutex (max 1 flagged Note per Space).

Notes module:
- types: add `isSpaceContext?: boolean` to LocalNote + Note
- queries: add `useSpaceContextNote()` (the active Space's flagged note)
- store: `markAsSpaceContext(id | null)` with mutex sweep across Space
- ListView: "Aus URL importieren" inline form (URL + crawl-mode +
  KI-Zusammenfassung toggle); "Als Space-Kontext markieren" /
  "Space-Kontext lösen" context-menu item; ★-Badge on flagged notes
- new api.ts: `crawlUrl()` client for POST /api/v1/notes/import-url

Notes API (apps/api):
- new modules/notes/routes.ts with /import-url (ported from kontext;
  same crawler + LLM summary pipeline, NOTES_IMPORT_URL credit op)
- mount at /api/v1/notes; add 'notes' to RESOURCE_MODULES (beta+ tier)
- delete modules/context (UI-less /ai/generate + /ai/estimate had no
  consumers; /import-url moved to notes)
- packages/credits: rename AI_CONTEXT_GENERATION → NOTES_IMPORT_URL

AI Mission Runner:
- default-resolvers: drop kontextResolver + kontextIndexer; the
  notesIndexer flags `isSpaceContext` notes with "★ " prefix and
  bubbles them to the top of the picker
- writing reference-resolver: `kind: 'kontext'` now reads the flagged
  Note via scope-scan instead of the kontextDoc table; tests updated
- writing ReferencePicker: useSpaceContextNote replaces useKontextDoc
- AiDebugBlock + MissionGrantDialog + ai-missions ListView: drop
  'kontextDoc' from ENCRYPTED_SERVER_TABLES set
- ai-agents ListView: drop 'kontext' from POLICY_MODULES

Profile module:
- ContextFreeform.svelte: switch import from kontext/api to notes/api
  (the URL-crawl is the same primitive; it still writes to
  userContext.freeform — only the import path changed)

Dexie:
- v58: notes index gains `isSpaceContext`; kontextDoc table dropped

Kontext module deletion:
- delete apps/mana/apps/web/src/lib/modules/kontext/ entirely
- delete (app)/kontext/ route
- drop registerApp + Scroll icon from app-registry/apps.ts
- drop kontext entry from help-content
- drop kontextModuleConfig from data/module-registry.ts
- drop kontextDoc from crypto registry

mana-auth:
- bootstrap-singletons: drop bootstrapSpaceSingletons function entirely
  (kontextDoc was the only per-Space singleton); userContext bootstrap
  unchanged
- better-auth.config: drop kontextDoc bootstrap call from personal-space
  hook + organizationHooks.afterCreateOrganization
- me-bootstrap: drop per-space bootstrap loop; response shape kept
  (always-empty `spaces: {}`) for backwards-compat with older clients

Note: the still-existing legacy `context` module (CMS-style docs/spaces,
unrelated to kontext) is left in place; its cleanup landed on the
articles-bulk-import branch and is out of scope for this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:14:32 +02:00
Till JS
0fc16d1bfd feat(articles): bulk-import AI tool wiring (Phase 6)
Adds import_articles_from_urls tool to the articles module so the AI
Workbench can kick off a bulk-import job in one call. Auto-policy: the
job itself is the unit of approval, no per-article propose card.

- shared-ai schemas: declare the tool name + propose/auto policy
- articles/tools.ts: implement parseUrls + articleImportsStore.createJob
- consume-pickup.ts: handle the new event type
- events/catalog.ts: register article-import lifecycle events
- imports.svelte.ts: minor polish

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 22:33:31 +02:00
Till JS
ae6a14fb76 feat(shared-ai): SYSTEM_BOOTSTRAP system source — fallback inserts now stamp origin='system'
The race-window `getOrCreateLocalDoc()` fallback in userContextStore +
kontextStore stays (without it, a write that lands between "endpoint
provisioned the singleton in mana_sync" and "first pull landed it in
IndexedDB" would hit `update(missing-id, diff)` — a Dexie no-op that
silently swallows the user's edit). But it was semantically lying: the
insert stamped `origin='user'` even though the row is logically a
client-side replica of the server-side bootstrap.

This commit adds `SYSTEM_BOOTSTRAP = 'system:bootstrap'` to
`@mana/shared-ai` and wraps the two fallback inserts in
`runAsAsync(makeSystemActor(SYSTEM_BOOTSTRAP), ...)`. The Dexie hook
now stamps `origin: 'system'` on the empty-row insert — structurally
identical to the row mana-auth's bootstrap-singletons.ts writes. When
the server's pull arrives later both sides carry the same origin and
the conflict-gate stays quiet. The user's subsequent writes still
stamp `origin: 'user'` on the changed fields.

Plan: docs/plans/sync-field-meta-overhaul.md (F4-fu Fallback-Origin row).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 01:44:30 +02:00
Till JS
bf3bca268a feat(lasts): M1-M7 — module ship + Meilensteine-Aggregator
Mirror sibling to firsts: das *letzte* Mal, das du etwas getan hast —
markiert oder rückwirkend erkannt. Plan: docs/plans/lasts-module.md.

M1 Skelett — Dexie v51 lasts-Tabelle, Encryption-Registry, Per-Space-
Welcome-Seed, Empty-State ListView. Kategorien aus firsts/types.ts
nach \$lib/data/milestones/categories.ts extrahiert (Re-Exports halten
firsts-API stabil).

M2 CRUD + DetailView — StatusTabs (Vermutet/Bestätigt/Aufgehoben),
Quick-Add mit Mode-Toggle, always-editable DetailView mit Lifecycle-
Buttons (Bestätigen, Aufheben mit Inline-Note), 44 i18n-Keys × 5 Locales.

M3 Inbox + Inferenz — Dexie v52 lastsCooldown (12-Monate-Cooldown,
deterministische ID), Source-Registry-Pattern in inference/, places-
Source mit Heuristik visitCount>=5 Span>=180d Silence>=365d. InboxView
mit Akzeptieren/Verwerfen + manueller Scan. contacts/habits → M3.b
sobald jeweilige Frequenz-Felder existieren.

M4 AI-Tools — 5 Tools im AI_TOOL_CATALOG (create_last, confirm_last,
reclaim_last, list_lasts, suggest_lasts), Webapp-Executor mit Vault-
Locked-Handling. Server-Drift-Test 4/4, Schema-Test 6/6.

M5 Reminders + Settings — Pivot zu In-App-DueBanner statt OS-Push (kein
PWA-Push-System im Repo). Pure date-math (12 Vitest cases), Settings-
Store mit 4 Toggles, DueBanner mit max-N rendering, Test-Banner-Knopf.

M6 Visibility + Unlisted-Sharing — VisibilityPicker + SharedLinkControls
in DetailView, buildLastBlob mit reflective-core whitelist (reclaimed
Lasts gehärtet ausgeblockt), SharedLastView public-render, Share-
Dispatcher kennt 'lasts'.

M7 Meilensteine-Aggregator — Cross-modul firsts vereinigt mit lasts
Timeline + Year-Recap. Pure aggregator (mergeMilestones,
buildMilestonesRecap), 12 Vitest cases. /milestones und
/milestones/recap/[year] Routes, Cross-Link in lasts/ListView.

Validation: 0 errors / 0 warnings (svelte-check 7645 files), 24/24
tests, i18n-parity 39x5 aligned (+2 namespaces), i18n-keys baseline-
equal, crypto 211 tables.

LOCAL TIER PATCH: lasts ist 'guest' für Testing — vor Release auf
'beta' setzen (packages/shared-branding/src/mana-apps.ts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 21:40:29 +02:00
Till JS
ad5e04a554 feat(sync): F2 — origin-gated conflict-detection
Closes the false-positive conflict-toast loop on history-replay. Conflict
notifications now fire only when the local field meta records origin='user'
AND the pull is not an initial hydration round.

Origin source-of-truth:
- shared-ai/field-meta.ts → originFromActor(actor) maps actor.kind onto
  the FieldOrigin enum: user→'user', ai→'agent', system+SYSTEM_MIGRATION
  →'migration', any other system source→'system'.
- Dexie creating/updating hooks call it once per write so every persisted
  field carries the right pipeline tag.
- repair-silent-twin + legacy-avatar wrap their writes in
  runAsAsync(makeSystemActor(SYSTEM_MIGRATION, ...)) so the hook stamps
  origin='migration'. Future replays of those rows from another device
  will not surface as conflicts.

applyServerChanges options:
- New ApplyServerChangesOptions { isInitialHydration?: boolean }.
- Push-response and pull-paged-loop callers compute it from the cursor
  state (`!oldestCursor` / `!cursor`). Pagination resets the flag after
  the first page.
- Conflict-trigger gates on `!options.isInitialHydration && localMeta[k]
  ?.origin === 'user'` in addition to the prior tests.

Tests (sync.test.ts):
- New: replay-burst (10 sequential server updates → 0 conflicts)
- New: agent-origin local write + server overwrite → 0 conflicts
- New: isInitialHydration suppresses everything → 0 conflicts
- New: real user edit + server overwrite → 1 conflict
- All 25 prior tests still pass.

29/29 vitest sync.test.ts cases green; svelte-check 0 errors over 7647
files.

Plan: docs/plans/sync-field-meta-overhaul.md F2 done-criteria met.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 21:38:56 +02:00
Till JS
7766ea5021 docs(plans): mark llm-fallback-aliases SHIPPED, add M-by-M commit table
All 5 milestones landed today in one continuous session: registry,
health cache, fallback router, observability, and consumer migration.
115 service-side tests, validator covers 2538 files.
2026-04-26 21:27:57 +02:00
Till JS
fea3adf5fe feat(llm-aliases): M5 — migrate consumers to MANA_LLM aliases
Final milestone of docs/plans/llm-fallback-aliases.md. Every backend
caller now requests models via the `mana/<class>` alias system instead
of hardcoded `ollama/...` strings. mana-llm resolves aliases through
`services/mana-llm/aliases.yaml` with health-aware fallback (M3) and
emits resolved-model + fallback metrics (M4).

SSOT moved to `packages/shared-ai/src/llm-aliases.ts` so apps/api,
apps/mana/apps/web, and services/mana-ai all import the same
`MANA_LLM` constant via the existing `@mana/shared-ai` workspace
dependency. Three additional sites (memoro-server, mana-events,
mana-research) inline the alias string with a SSOT comment because
they don't pull @mana/shared-ai today.

Migrated 14 sites across 10 files:
- apps/api: writing(LONG_FORM), comic(STRUCTURED), context(FAST_TEXT),
  food(VISION), plants(VISION), research orchestrator (3 tiers
  collapsed to STRUCTURED+FAST_TEXT/LONG_FORM)
- apps/mana/apps/web: voice/parse-task + parse-habit (STRUCTURED)
- services/mana-ai: planner llm-client + tick.ts (REASONING)
- services/mana-events: website-extractor (STRUCTURED, inlined)
- services/mana-research: mana-llm client (FAST_TEXT, inlined)
- apps/memoro/apps/server: ai.ts (FAST_TEXT, inlined)

Legacy env-vars removed: WRITING_MODEL, COMIC_STORYBOARD_MODEL,
VISION_MODEL, MANA_LLM_DEFAULT_MODEL. The chain in aliases.yaml is
now the single tuning surface; SIGHUP reloads it without redeploys.

New `scripts/validate-llm-strings.mjs` regex-scans 2538 files for
hardcoded `<provider>/<model>` strings and fails the build if any
land outside the SSOT or the explicitly-allowed paths (image-gen
modules, model-inspector code, this validator itself, the registry).
Wired into `validate:all` next to the i18n + theme validators.

Verified: `pnpm validate:llm-strings` clean, `pnpm --filter @mana/api
type-check` clean, `pnpm --filter @mana/ai-service type-check`
clean. Web type-check has 2 pre-existing errors in
SettingsSidebar.svelte (i18n MessageFormatter type drift, last
touched in 988c17a67 — unrelated to this work).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 21:26:03 +02:00
Till JS
ef96948ea0 feat(comic): Mc4 — MCP + AI-Catalog für Character-System
Persona-Runner / Claude Desktop / Web-App-Mission-Runner können jetzt
Comic-Characters bauen, iterieren und pinnen — same Auto/Propose-
Pattern wie die Story-Tools.

MCP (packages/mana-tool-registry/src/modules/comic.ts):
- comic.listCharacters (read/auto): Pull, decrypt, filter (style?,
  favoriteOnly?), liefert {id, name, style, addPrompt, source-Refs,
  variantMediaIds, pinnedVariantId, variantCount, tags, isFavorite}.
- comic.createCharacter (write/propose): legt nur die Row an —
  trennt Anlegen von Generierung damit der Agent reviewen kann
  bevor Credits fließen. Liefert characterId zurück.
- comic.generateVariant (write/propose, kostet Credits): pullt
  Character-Row, dekodiert, ruft /picture/generate-with-reference
  mit n=count (default 4) + Stil-Prefix + Identity-Anchor-Prompt,
  schreibt N picture.images mit comicCharacterId-Back-Ref, pusht
  field-level Update auf variantMediaIds + pinnedVariantId
  (auto-pin auf erste neue Variant wenn vorher null).
- comic.pinVariant (write/propose): Set-Equality-Check (variantMediaId
  muss in variantMediaIds sein), field-level Update auf
  pinnedVariantId. Snapshot-Pattern: bestehende Stories bleiben
  unverändert, nur neue Stories nutzen den neuen Pin.

AI_TOOL_CATALOG (packages/shared-ai/src/tools/schemas.ts):
- list_comic_characters (auto)
- create_comic_character (propose) — auto-resolvt face/body-refs aus
  meImages-primaries, Agent muss keine mediaIds kennen
- generate_character_variant (propose, count 1-4)
- pin_character_variant (propose)

Web-App-Executors (apps/mana/apps/web/src/lib/modules/comic/tools.ts):
- 4 ModuleTool-Einträge, die an comicCharactersStore +
  runCharacterGenerate delegieren — gleicher Code-Pfad wie die UI,
  also keine Divergenz zwischen Klick und Agent-Call.

Comic-Autor-Template (packages/shared-ai/src/agents/templates/
comic-author.ts):
- Policy bi-lingual erweitert: snake_case + dot-case Namen für
  alle 4 neuen Character-Tools.
- System-Prompt Schritt 3 ergänzt: "Wenn der User noch keinen
  passenden Comic-Character hat → list_comic_characters →
  create_comic_character → generate_character_variant → pin.
  Das ist EINMALIG — der gepinnte Character bleibt für viele
  Stories der stabile Identity-Anchor."
- Tool-Liste am Ende vom System-Prompt um den Character-Pfad
  ergänzt.

apps/mana/CLAUDE.md Tool-Coverage-Zeile für comic erweitert:
+ create_comic_character / generate_character_variant /
+ pin_character_variant (propose)
+ list_comic_characters (auto)

Tool-Count: comic 3→7. Module 23 unverändert.

107 shared-ai-Tests weiter grün. check für comic-Files clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 19:27:15 +02:00
Till JS
faa16fa898 feat(augur): new module — signs collected, patterns read
Introduces the Augur module: capture omens, fortunes, and hunches in
a poetic Witness mode and read them back empirically in Oracle mode.
Same data, two lenses; the killer mechanic is the Living Oracle that
materialises empirical reflections from the user's own resolved
history at capture time.

Why now: docs/future/MODULE_IDEAS.md captured the brainstorm, then
the spec landed at docs/plans/augur-module.md as a Witness+Oracle
hybrid. Built end-to-end through M6 in one go.

Highlights:
- Witness gallery + DueBanner + DetailView + Resolve flow
- Oracle stats: calibration-per-source, vibe-hit-rate, cross-module
  correlation engine (mood/sleep/duration after-windows)
- Living Oracle: deterministic fingerprint+match against user's own
  resolved history; cold-start-gated at 50 resolved entries
- Year-Recap view at /augur/recap/[year]
- 5 MCP tools: capture_sign, resolve_sign, list_open_signs,
  consult_oracle, augur_year_recap (in AI_TOOL_CATALOG)
- Visibility integration: default 'private', VisibilityPicker in
  DetailView. Server-side unlisted-snapshot-publish stays follow-up
- v47 Dexie schema; encrypted: source/claim/feltMeaning/
  expectedOutcome/outcomeNote/tags/livingOracleSnapshot
- LOCAL TIER PATCH: requiredTier 'guest' for testing

Strings interpolated through `T` constants so the i18n-hardcoded
baseline stays at 0 for augur — real $_('augur.*') keys land later.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 15:02:15 +02:00
Till JS
364522db87 feat(comic): image-model picker — OpenAI + Nano Banana wählbar
Comic nutzte bisher 'openai/gpt-image-2' hartcodiert auf drei Ebenen
(generate-panel.ts, comic.generatePanel MCP-Tool, generate_comic_panel
AI-Tool). Wardrobe hat seit dem Nano-Banana-Commit einen
TryOnModelPicker mit drei Optionen — Comic spiegelt das jetzt 1:1.

Wählbar in allen drei Editoren (PanelEditor, BatchPanelEditor,
StoryboardSuggester):
- openai/gpt-image-2 (Default) — OpenAI GPT-image Standard
- google/gemini-3-pro-image-preview — Nano Banana Pro, hohe
  Konsistenz, teurer
- google/gemini-3.1-flash-image-preview — Nano Banana 2, neuestes,
  schnell, günstig

Implementierung:
- api/generate-panel.ts: PanelModel Union + DEFAULT_PANEL_MODEL +
  model? Param auf RunPanelGenerateParams + im HTTP-Body
  weitergereicht (vorher hart 'openai/gpt-image-2').
- components/PanelModelPicker.svelte: neue Komponente, Stil/Markup
  identisch zu TryOnModelPicker für Muskel-Memory über beide Flows.
- components/PanelEditor.svelte: `let model = $state(DEFAULT_PANEL_MODEL)`
  + Picker oberhalb der Qualität-/Format-Leiste + model im
  runPanelGenerate-Call.
- components/BatchPanelEditor.svelte: gleiche Änderung — ein Model
  pro Batch (nicht pro Row) damit der Batch konsistent rendert.
- components/StoryboardSuggester.svelte: gleiches Pattern; der
  Picker landet zwischen "Panel manuell"-Button und dem
  Qualität/Format-Block.
- packages/mana-tool-registry/src/modules/comic.ts: generatePanel
  Input-Schema bekommt model mit zod.enum() + default; im Body
  wird input.model durchgereicht.
- packages/shared-ai/src/tools/schemas.ts: generate_comic_panel
  bekommt Parameter 'model' optional mit gleicher Enum-Liste.
- apps/mana/apps/web/src/lib/modules/comic/tools.ts: isValidModel
  Guard + Parameter-Validierung; model an runPanelGenerate.

Keine Story-Level-Persistierung — model bleibt lokaler State pro
Editor-Mount. Eine model-Spalte auf comicStories würde Migration
brauchen und die Wahl ist eh ad-hoc pro Panel/Batch.

Plan-Doc (§2.1) dokumentiert die Entscheidung + die drei Optionen.

107 shared-ai tests weiter grün. check + validate:all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 17:19:40 +02:00
Till JS
6f37e00bf4 feat(comic): AI_TOOL_CATALOG bridge — webapp-runner kann jetzt Comics
Macht den Comic-Autor-Template (M6) auch im Web-App-Mission-Runner
nutzbar. Bisher war der Template nur über persona-runner/Claude
Desktop sinnvoll, weil die comic.*-Tools nur im mana-tool-registry
(MCP) lagen. Jetzt kennt die AI Workbench drei neue Tools und der
Template-Policy-Map trägt beide Naming-Konventionen.

AI_TOOL_CATALOG-Einträge (packages/shared-ai/src/tools/schemas.ts):
- list_comic_stories (auto) — filter style?/favoriteOnly?/limit?
- create_comic_story (propose) — title + style + optional
  description/storyContext/tags. Character-Refs werden vom Executor
  automatisch aus meImages primary face-ref + body-ref gezogen,
  also muss der Planner keine mediaIds kennen.
- generate_comic_panel (propose) — storyId + panelPrompt + optional
  caption/dialogue + quality. Kostet Credits.

Executors (apps/mana/apps/web/src/lib/modules/comic/tools.ts):
- list: scopedForModule pull + decrypt + filter + sort newest.
- create: resolveCharacterMediaIds() scannt meImagesTable für das
  aktive Space, nimmt face-ref+body-ref. Fehler wenn kein Face
  hinterlegt ("Lade eines in /profile/me-images hoch"). Delegiert
  an comicStoriesStore.createStory — gleiche encryption/event-
  pipeline wie StoryForm.
- generate: lädt Story decrypted, delegiert an runPanelGenerate
  (identischer Pfad wie PanelEditor in der UI), liefert
  panelIndex + imageUrl zurück.

Registrierung in data/tools/init.ts (registerTools(comicTools)).

Template-Policy (comic-author.ts) jetzt bi-lingual: snake_case
(AI_TOOL_CATALOG) UND dot-case (MCP) nebeneinander in tools-Map.
So gilt die Intent-Policy konsistent egal welche Runner-Oberfläche
das Tool nennt — auto für list_comic_stories / comic.listStories,
propose für create_comic_story / comic.createStory /
generate_comic_panel / comic.generatePanel / comic.reorderPanels.

apps/mana/CLAUDE.md Tool-Coverage-Tabelle bekommt eine Comic-Zeile.

Tool-Count jetzt 75→78, Module 22→23. 107 shared-ai tests
weiter grün. check + validate:all clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:49:24 +02:00
Till JS
6545498dc2 feat(writing): agent.defaultWritingStyleId — M8 persona-linkage follow-up
Agents can now pin a default writing style. When an AI-actor runs
`create_draft` without an explicit styleId, the tool resolves to the
agent's `defaultWritingStyleId` so e.g. a "Marketing-Agent" always
drafts in the Corporate-Tone style and a "Memoir-Agent" in Memoir.

- @mana/shared-ai: optional `defaultWritingStyleId?: string` added to
  the Agent interface (plaintext FK, format `preset:<id>` or a custom
  WritingStyle uuid). No migration — existing rows stay undefined and
  the fallback path no-ops for them.
- ai-agents store: field threaded through CreateAgentInput + AgentPatch
  + the create function's copy-list. `updateAgent` already deep-clones
  the patch so nothing else to change there.
- ai-agents ListView: new "Writing" section in the agent detail panel
  with a StylePicker (reuses the writing module's component — Vorlagen
  + Meine Stile optgroups). Empty = kein Default.
- writing/tools.ts: `resolveAgentDefaultStyle()` reads the current
  actor, guards `isAiActor`, loads the agent row, and returns its
  defaultWritingStyleId. Wired into `create_draft` as a fallback when
  `params.styleId` is missing. User-invoked calls skip the lookup — a
  human omitting styleId means "ad-hoc, no style", not "my default".
  `generate_draft_content` needs no change because the draft's styleId
  is already set at create time.

107 shared-ai tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:36:20 +02:00
Till JS
91ae58f2af feat(comic): M6 — Comic-Autor persona-template
Neuer Eintrag in der Template-Galerie unter /agents/templates:
Comic-Autor nimmt einen Tagebuch-Eintrag, eine Notiz oder ein
Library-Review und verwandelt ihn in eine kurze Panel-Folge —
4 Panels Default, Sprechblasen + Captions direkt im Bild durch
gpt-image-2.

Policy-Layout:
- comic.listStories / journal.* / notes.* / library.* / kontext /
  goals → auto. Der Agent darf frei stöbern, ohne den User für
  jeden Read anzunerven.
- comic.createStory / comic.generatePanel / comic.reorderPanels →
  propose. Jedes Write muss der User bestätigen; besonders
  generatePanel, das pro Call 3-25 Credits kostet.
- Baseline: alle propose-fähigen Tools aus AI_TOOL_CATALOG kriegen
  propose (seed wie im Recherche-Agent) — Cross-Module-Schreibungen
  die der Agent eventuell vornimmt (z.B. create_note für eine
  Sidecar-Zusammenfassung) landen so als Vorschlag, nicht als
  Blitz-Ausführung.
- defaultForAi: propose — sicher per Default.

System-Prompt gibt dem Agent eine klare Rolle: Text lesen, Stil
wählen nach Ton (comic/manga/cartoon/graphic-novel/webtoon), 4
Panels mit prompt+caption?+dialogue? vorschlagen, Protagonist ist
immer der User. "Humor wenn der User es leicht nimmt, ernst wenn
er es ernst nimmt. Nie urteilen." Ton-Hinweis zu englischen vs.
deutschen Dialog-Texten (Englisch rendert stabiler).

Szene öffnet Comic + Journal + ai-missions + ai-workbench nebeneinander.

Eine paused Starter-Mission "Comic aus einem Tagebuch-Eintrag" mit
Concept-Markdown-Vorlage (Eintrag / Stil / Panel-Anzahl / Ton).

Die comic.*-Tools leben in mana-tool-registry (MCP) und sind noch
NICHT im AI_TOOL_CATALOG — dieser Template ist primär für
persona-runner/Claude-Desktop-Seite nutzbar, bis die Workbench-
Integration separat folgt.

107 shared-ai tests weiter grün.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:34:55 +02:00
Till JS
d49ad239d9 feat(writing): M8 — AI tools exposed through the shared catalog
Writing is now programmatically accessible from the foreground mission
runner, personas, and Claude Desktop / MCP. Eight tools land:

Auto (read-only):
- list_drafts         — filtered by kind/status + word-count summary
- get_draft           — briefing + current version body, ready for reading
- list_writing_styles — 9 presets + user customs, ids usable in create_draft

Propose (human approval per agent policy):
- create_draft            — briefing only, no generation yet
- generate_draft_content  — wraps generationsStore.startDraftGeneration;
                            writes a new LocalDraftVersion + pointer flip
- refine_draft_selection  — wraps refineSelection + applyRefinement in
                            one call; operations: shorten/expand/tone/
                            rewrite/translate with op-specific params
- set_draft_status        — draft/refining/complete/published
- save_draft_as_article   — hand-off to articlesStore.saveFromExtracted
                            with internal://writing/<id> as originalUrl,
                            records publishedTo + emits WritingDraftPublished

Schemas live in @mana/shared-ai/src/tools/schemas.ts (the SSOT that the
web-app policy layer + mana-ai planner derive from). Executors live in
modules/writing/tools.ts and delegate to the existing stores so the
encryption + event pipeline runs once regardless of who called the tool.
Registration added to data/tools/init.ts.

107 shared-ai tests still pass. CLAUDE.md tool-coverage table bumped:
67→75 tools, 21→22 modules.

Not in M8 (deferred): agent.defaultWritingStyleId linkage (needs a
Persona schema change + runner wiring), mana-tool-registry Zod specs
(add when a non-web MCP client needs richer validation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:19:30 +02:00
Till JS
101af462a8 feat(shared-ai): LLM-facing task tool wrapper for runSubAgent (M3.2)
Exposes runSubAgent() as a tool the planner LLM can call natively,
matching Claude Code's `Task` tool shape: { subagent_type, description,
prompt } -> single-string summary.

New exports from @mana/shared-ai:

  - TASK_TOOL_NAME = 'task'
  - TASK_TOOL_SCHEMA — ToolSchema ready to drop into a runPlannerLoop
    `tools` array. subagent_type enum = research|plan|general;
    description+prompt required; defaultPolicy: 'auto' (control-flow,
    not a user-data write).
  - createTaskToolHandler(opts) — factory returning:
      - handle(call): structured ToolResult with the sub-agent's
        summary as message + data {subAgentType, toolsCalled,
        rounds, stopReason, usage}
      - cumulativeUsage(): rolled-up TokenUsage across all sub-agent
        invocations — parent budget accounting reads from here
      - invocationCount(): metric-ready counter

Why not in mana-tool-registry: `task` is a loop-internal control-flow
primitive, not a user-data operation. Registry is for habits/notes/etc.
where MCP exposure and space-scoping matter. task never touches mana-
sync and never crosses the MCP boundary.

Recursion guard is defense-in-depth: the primitive throws
SubAgentRecursionError, this handler catches parentDepth >=
MAX_SUB_AGENT_DEPTH up front and returns a structured ToolResult
instead so the LLM sees it as regular tool-feedback.

Exceptions from the sub-agent (provider down, network) get wrapped
as `{ success: false, message: 'Sub-agent failed: ...' }`. The parent
loop's round continues.

14 new tests covering schema shape, recursion rejection, argument
validation (4 cases), happy path with tool dispatch, cumulative
usage tracking across multiple invocations, exception wrapping,
and parent-dispatcher routing.

107 shared-ai tests green total (was 93).

M3.3 consumer wiring follows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:05:09 +02:00
Till JS
66b7e08df2 feat(shared-ai): runSubAgent() primitive — Claude-Code I2A pattern (M3.1)
New packages/shared-ai/src/planner/sub-agent.ts implementing the
"one level deep, fresh messages, restricted tools, single-string
return" sub-agent contract from Claude Code's KN5/I2A launcher.

Four invariants enforced at the primitive level:

  1. FRESH messages[] — parent's history never leaks in. The sub-agent
     only sees its own system prompt + the task description. Hundreds
     of scanned files stay inside the sub-agent.
  2. RESTRICTED tool-whitelist — parent's full catalog is filtered
     per SubAgentType ('research' = auto-policy only, 'general' =
     everything, 'plan' = auto-policy + 3-round cap). Custom filter
     overrides the type default.
  3. SINGLE RETURN VALUE — sub-agent returns summary:string for
     the parent to render as task-tool-result. Individual tool calls
     stay in rawResult for debug capture but never cross the boundary.
  4. ONE LEVEL DEEP — MAX_SUB_AGENT_DEPTH = 1. parentDepth >= 1 throws
     SubAgentRecursionError; the consumer task-tool handler will
     also check, this is defense-in-depth.

Model is required (no default) — routing to a cheaper tier like the
compactor does is an explicit decision, not a sneaky default.

Belt-and-suspenders wrapper on onToolCall rejects any tool call
whose name isn't in the whitelist, even if the LLM fabricates one.

14 new tests covering recursion guard, tool filtering per type,
custom filter, whitelist rejection, fresh-messages isolation, usage
roll-up, default summary on max-rounds, type-specific system prompt,
system-prompt override, and end-to-end tool-call -> result -> summary.

93 shared-ai tests green total (was 79).

M3.2 (task tool in registry) and M3.3 (consumer wiring) follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:59:05 +02:00
Till JS
f7536bc0b9 feat(shared-ai): route compactor to Haiku-tier model by default (M2.5)
compactHistory() now defaults to DEFAULT_COMPACT_MODEL =
'google/gemini-2.5-flash-lite' when the caller doesn't override. Lite
is ~3–5x cheaper than gemini-2.5-flash with near-identical
summarisation quality — summarisation doesn't need the same tier as
reasoning + tool-calling, and the compactor fires exactly when token
spend is highest, so the cheaper route saves exactly where it matters.

CompactHistoryOptions.model is now optional. All three consumers
(mana-ai tick, webapp Companion, webapp Mission runner) drop their
explicit gemini-2.5-flash override and let the default apply.

This is the pragmatic M2.5: no mana-llm changes. The "tier" abstraction
(X-Model-Tier header, env-routed aliases) from the Claude-Code report
makes sense only once multiple utility tasks need cheaper routing —
topic-detection, classification, command-injection checks. Today only
the compactor wants it, and a model constant is the simplest contract
that works.

2 new tests (default applied + override honoured). 79 shared-ai tests
green, all three consumers type-check clean. One pre-existing unrelated
type error in apps/mana/apps/web/src/lib/modules/wardrobe/queries.ts
(not touched by this commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 18:26:50 +02:00
Till JS
72f7978ed4 feat(agent-loop): expose compactionsDone + compactedReminder producer
Closes the loop on M2: when the compactor fires, the LLM needs to know
it's now seeing a <compact-summary> instead of raw turns so it
doesn't waste a turn asking about lost details or re-executing tools
whose responses are gone.

shared-ai:
  - LoopState grows `compactionsDone: number` (cap-1 by current loop
    policy, but shape kept as count for future multi-compact cycles).
  - runPlannerLoop populates it on each reminder-channel call. New
    loop test asserts [0, 1] sequence: round 1 before compaction,
    round 2 after.

mana-ai:
  - New producer `compactedReminder` — fires severity=info when
    compactionsDone >= 1, wrapped in a German one-liner ("frag nicht
    nach verlorenen Details").
  - Injected FIRST in buildReminderChannel so the LLM frames the rest
    of the round with "I'm looking at a summary" context. Metric
    surface stays `{producer='compacted', severity='info'}`.

4 new reminder tests (3 pure producer + 1 composition-ordering) +
1 loop-wiring test. 77 shared-ai, 20 reminders.test.ts — green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:36:21 +02:00
Till JS
3d8214a147 feat(shared-ai): wire compactor into runPlannerLoop (M2.2)
PlannerLoopInput grows an optional compactor:

  compactor?: {
    maxContextTokens: number;
    threshold?: number;        // default 0.92, matches Claude Code wU2
    compact: (messages) => Promise<{ messages, compactedTurns }>;
  }

Before each LLM call the loop checks whether promptTokens+completion
has crossed threshold × maxContextTokens. If yes AND we haven't
compacted this run yet, the callback runs, its returned messages
REPLACE the live history, and compactionsDone flips to 1 so a
runaway tool can't re-trigger.

Design choices:
  - Fires at most ONCE per loop run. If the fresh (compacted)
    history hits the threshold again in the same run, the LLM
    round budget will hit first; better to terminate than to
    recursively compact a summary.
  - No reminder emitted automatically — the caller can wire
    that via reminderChannel by reading compactionsDone from
    LoopState (next PR; compactionsDone isn't exposed yet to
    keep the state surface small).
  - compactor callback is injectable, not hardcoded to
    compactHistory() from compact.ts. Lets mana-ai route the
    compactor LLM call to a cheaper model (Haiku) without
    changing the loop.
  - Zero maxContextTokens → skip silently (same contract as
    shouldCompact()).

Also cleaned up the isParallelSafe non-null-assertion warning by
hoisting the predicate to a local with proper narrowing.

5 new loop tests: below-threshold no-op, single-fire replacement,
once-per-run idempotency, zero-cap bail, no-op when compactor
returns 0 turns. 76 shared-ai tests total, green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:25:35 +02:00
Till JS
13361eb083 feat(shared-ai): compactHistory() — context-window compactor primitive (M2.1)
The Claude-Code wU2 pattern: when token usage hits ~92% of the provider's
context budget, fold all pre-tail turns into a single structured summary
(Goal / Decisions / Tools Called / Current Progress) so subsequent
rounds see a synopsis instead of the raw log.

This commit ships ONLY the primitive. Wiring it into runPlannerLoop
(auto-trigger before the next LLM call when shouldCompact() fires)
is M2.2 so the surface stays small and testable.

New exports from @mana/shared-ai:

  - shouldCompact(totalTokens, maxContextTokens, threshold?)
      → boolean; DEFAULT_COMPACT_THRESHOLD = 0.92, matching Claude Code.
      Bails safely when maxContextTokens is missing (local models often
      don't report usage).

  - compactHistory(messages, { llm, model, keepRecent?, temperature? })
      → { messages, summary, compactedTurns, usage? }
      Preserves: [0]=system, [1]=first user, [last N]=recent turns
      (default 4). Everything between gets sent through the compact
      agent with COMPACT_SYSTEM_PROMPT — a fixed 4-section Markdown
      schema. Temperature default 0.2 because we want summarisation,
      not creativity.

  - parseCompactSummary / renderCompactSummary — round-trip helpers.
      Parser is tolerant (missing sections → empty string) so a partial
      compaction still produces a usable summary.

The summary replaces the middle as a single role='assistant' message
wrapped in <compact-summary> tags. Assistant role (not system) because
some providers reject arbitrary system messages deep in history.

Tests: 17 new across the 4 exports (trigger logic, Markdown round-trip,
structural preservation of anchors + tail, usage passthrough, custom
keepRecent). All 71 shared-ai tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:21:10 +02:00
Till JS
13efae8cd2 feat(website): M5 — AI tools + starter templates
Two things:

1. AI tools (9) in the website module — writes go through the standard
   proposal flow, reads run auto during planning.
   - shared-ai/src/tools/schemas.ts: AI_TOOL_CATALOG entries with
     defaultPolicy propose/auto.
   - webapp modules/website/tools.ts: execute functions wired to the
     existing stores. ModuleTool[] registered in data/tools/init.ts.
   - Propose: create_website, apply_website_template, create_website_page,
     add_website_block, update_website_block, publish_website
   - Auto: list_websites, list_website_pages, list_website_blocks
   Server-side mana-tool-registry integration (mana-mcp, mana-ai) is
   a M5.x follow-up — webapp flow unblocks the missions-based use case.

2. Starter templates — clone into a fresh site with new UUIDs.
   - templates/types.ts: SiteTemplate shape with localId / parentLocalId
     so container→child references survive the clone.
   - 4 templates: portfolio (4 pages), personal-linktree (1 page, 6 CTAs),
     event (3 pages incl. RSVP form), blank (1 empty page). Deferred:
     smb-corporate + product-landing (need team/pricing/testimonials
     blocks, M6+).
   - sitesStore.applyTemplate: walks template, bulk-inserts new rows,
     remaps parent refs. Sets navConfig items from template pages.
   - TemplatePicker component + /website/new route. Replaces the old
     quick-create modal; ListView now links to /new. AppRegistry
     context-menu action points there too.

AiProposalInbox integration deferred — the component doesn't exist in
the webapp yet (the plan mentions it aspirationally). defaultPolicy
'propose' is already set so writes stage correctly once the UI catches
up.

Validation:
- pnpm run validate:all: 6/6 gates green
- pnpm run check (web): 0 errors, 0 warnings
- apps/api + packages/shared-ai type-check: green

Plan: docs/plans/website-builder.md (M5 shipped)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:14:45 +02:00
Till JS
8f283726b1 feat(agent-loop): activate retryLoopReminder via LoopState.recentCalls
Extends LoopState with a sliding window of the last N ExecutedCalls
(oldest-first), capped at LOOP_STATE_RECENT_CALLS_WINDOW = 5. The loop
maintains the window automatically; reminderChannel producers read it
without touching internal state.

This activates retryLoopReminder which was shape-only in faa472be9.
The guard now fires end-to-end: when round >= 3 and the tail-2 calls
both returned success:false, the LLM sees a "stop retrying, write a
summary instead" <reminder> on the next turn. The tail-2 check rather
than window-wide is deliberate — a flaky run with intermittent success
(F, F, F, OK, F) is not a retry loop, just flaky tools.

Why window=5: retry loops usually manifest within 2-3 consecutive
rounds; a 5-deep window gives room for burst-detection and
stale-tool heuristics without bloating the reminder channel. Cap
keeps the reminder producers O(5) regardless of loop length.

Tests: 3 new (sliding-window cap + slide + order in shared-ai, retry
composition + budget+retry chain + tail-only heuristic in mana-ai).
Total agent-loop tests now 74 across both packages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:02:40 +02:00
Till JS
faa472be91 feat(mana-ai): first live reminder producers — token budget + retry-loop
Wires the M1 reminderChannel into the mana-ai mission runner with two
initial producers in services/mana-ai/src/planner/reminders.ts:

- tokenBudgetReminder — warns at 75% of the agent's daily cap, emits a
  stronger "wrap up NOW" message at/above 100%. Uses pretick usage +
  accumulated round usage so the warning tracks drift during a long
  plan.
- retryLoopReminder — shape is in place (round≥3 + last 2 failures),
  currently limited to the single lastCall LoopState exposes. Extends
  cleanly once LoopState carries the full failure window.

buildReminderChannel composes active producers; the tick hoists
pretickUsage24h so the channel has the baseline. Each round the loop
re-evaluates the producers, so usage drift across rounds surfaces on
the NEXT turn.

Also exports LoopState + ReminderChannel from @mana/shared-ai top-level
so consumers don't need to reach into /planner.

Tests: 13 new bun tests covering thresholds, pretick+round summing,
composition, and per-round re-evaluation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 14:00:04 +02:00
Till JS
e5d230e599 feat(agent-loop): M1 — policy gate + reminder channel + parallel reads
Three Claude-Code-inspired primitives for runPlannerLoop, derived from the
reverse-engineering reports in docs/reports/:

1. **Policy gate** (@mana/tool-registry) — evaluatePolicy() gates every tool
   dispatch: denies admin-scope, denies destructive tools not in the user's
   opt-in list, rate-limits per tool (30/60s default), flags prompt-injection
   markers in freetext without blocking. Wired into mana-mcp with a
   per-user rolling invocation log and POLICY_MODE env (off|log-only|enforce,
   default log-only). mana-ai uses detectInjectionMarker only — tool dispatch
   there is plan-only, so rate-limit/destructive checks don't apply yet.

2. **Reminder channel** (packages/shared-ai/src/planner/loop.ts) — new
   reminderChannel callback in PlannerLoopInput. Called once per round with
   LoopState snapshot (round, toolCallCount, usage, lastCall); returned
   strings wrap in <reminder> tags and inject as transient system messages
   into THIS LLM request only. Never pushed to messages[] — the Claude-Code
   <system-reminder> pattern that keeps the KV-cache prefix stable.

3. **Parallel reads** (loop.ts) — isParallelSafe predicate enables
   Promise.all dispatch when every tool_call in a round is parallel-safe,
   in batches of PARALLEL_TOOL_BATCH_SIZE=10. Any non-safe call downgrades
   the whole round to sequential. messages[] always appends in source
   order, never completion order, so the debug log stays linear.
   Default-off (undefined predicate) preserves pre-M1 behaviour.

Tests: 21 new in tool-registry (policy), 9 new in shared-ai (5 parallel,
4 reminder). All 74 green, type-check clean across 4 packages.

Design/plan: docs/plans/agent-loop-improvements-m1.md
Reports: docs/reports/claude-code-architecture.md,
         docs/reports/mana-agent-improvements-from-claude-code.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:56:40 +02:00
Till JS
5924f4fac3 feat(articles): M6 AI tools — list / save / archive / tag / highlight
Five new entries in AI_TOOL_CATALOG (shared-ai/src/tools/schemas.ts):

  list_articles            auto     Read-only listing with status +
                                    query filter. Default hides
                                    archived; 'all' includes them.
  save_article             propose  URL → Readability → encrypted save.
                                    Delegates to articlesStore.saveFromUrl
                                    which already handles scope-aware
                                    dedupe. Duplicates surface as
                                    success:true with duplicate:true.
  archive_article          propose  setStatus('archived') after
                                    scoped existence check.
  tag_article              propose  Case-insensitive dedupe over
                                    globalTags; tagMutations.createTag
                                    fills in when missing. Junction
                                    write via articleTagOps.addTag.
  add_article_highlight    propose  Snaps to the first verbatim
                                    occurrence of `text` in the
                                    decrypted article.content. Fails
                                    cleanly when the snippet isn't
                                    found — no orphan highlights.

Policy, client executor, and server planner derive automatically from
the catalog (see root CLAUDE.md §"AI Tool Catalog") so no manual
registration in policy.ts / services/mana-ai is needed.

Skipped from the M6 plan: <AiProposalInbox module="articles" />. The
component doesn't exist in the current codebase — after the
pendingProposals-table drop in Dexie v29 the inbox surface moved to
the mission-detail cross-module view, and articles proposals show up
there automatically. Documented in docs/plans/articles-module.md.

Also updated: plan doc now marks M1–M6 as DONE with commit refs and
the next-step pointer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 18:46:13 +02:00
Till JS
75832faef7 feat(broadcast): enhanced ListView + dashboard widget + AI tools
Closes the M7/M9/M10 plan items in one pass since they share patterns.

ListView (M7)
- 4 stats cards at the top: versendet YTD, Ø Öffnungsrate, Ø Klickrate,
  Entwürfe. Same layout pattern as invoices for consistency.
- Status filter chips with live counts per status.
- Search across name + subject.
- Row now shows open-rate per-campaign when available.
- Settings gear in the header matches the invoices polish.

Dashboard widget (M10)
- BroadcastsWidget.svelte: 2x stats (sent YTD + avg open rate), next
  scheduled link, last sent link with open-rate badge. Empty state
  nudges toward creating a first campaign.
- Registered as 'broadcasts' in WIDGET_REGISTRY and the component map.
- Medium default size, no requiredBackend (reads from Dexie only;
  stats are mirrored from the last DetailView poll so no server
  round-trip for the widget).

AI tools (M9)
- 3 tools added to @mana/shared-ai's AI_TOOL_CATALOG:
  - create_campaign_draft (propose) — generates HTML body from a
    topic, lands as a draft; user picks audience + sends via UI
  - list_campaigns (auto) — id/name/subject/status/recipients
  - get_campaign_stats (auto) — rates as 0..1 floats
- broadcast/tools.ts: execute handlers with an HTML→CampaignContent
  shim (stores both html and a minimal Tiptap JSON placeholder so
  ListView renders without the editor having to remount). stripHtml
  helper derives plaintext.
- Registered in data/tools/init.ts after library.

Suggest-style tools (suggest_subject_lines) deliberately omitted —
they're pure generative and don't need an executor. The LLM can
produce subject ideas without a tool call.

Verified:
- pnpm check: 0 broadcast errors (4 pre-existing errors in articles
  module from parallel work, not mine)
- shared-ai test suite: 44/44 green (function-schema roundtrips the
  expanded catalog cleanly)
- mana-ai drift guard: 41/41 green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:27:59 +02:00
Till JS
8e677c9066 feat(ai): add Library AI tools (create / rate / status / list)
Some checks failed
CI / Build mana-sync (push) Has been cancelled
CI / Build mana-notify (push) Has been cancelled
CI / Build mana-api-gateway (push) Has been cancelled
CI / Build mana-crawler (push) Has been cancelled
CI / Build mana-media (push) Has been cancelled
CI / Build mana-credits (push) Has been cancelled
CI / Build mana-web (push) Has been cancelled
CI / Build chat-backend (push) Has been cancelled
CI / Build chat-web (push) Has been cancelled
CI / Build todo-backend (push) Has been cancelled
CI / Build todo-web (push) Has been cancelled
CI / Build calendar-backend (push) Has been cancelled
CI / Build calendar-web (push) Has been cancelled
CI / Build clock-web (push) Has been cancelled
CI / Build contacts-backend (push) Has been cancelled
CI / Build contacts-web (push) Has been cancelled
CI / Build presi-web (push) Has been cancelled
CI / Build storage-backend (push) Has been cancelled
CI / Build storage-web (push) Has been cancelled
CI / Build telegram-stats-bot (push) Has been cancelled
CI / Build food-backend (push) Has been cancelled
CI / Build food-web (push) Has been cancelled
CI / Build skilltree-web (push) Has been cancelled
Docker Validate / Build calendar-web (push) Has been cancelled
Docker Validate / Build quotes-web (push) Has been cancelled
Docker Validate / Build todo-backend (push) Has been cancelled
Docker Validate / Build todo-web (push) Has been cancelled
Docker Validate / Build mana-auth (push) Has been cancelled
Docker Validate / Build mana-sync (push) Has been cancelled
Docker Validate / Build mana-media (push) Has been cancelled
Library module had no AI tool coverage post the M1 skeleton. Adds
four tools so the agent can curate the reading/watch list alongside
other modules:

- create_library_entry (propose) — books/movies/series/comics with
  creators, year, status, rating, tags, genres. Default status
  "planned" covers the most common flow ("add to watchlist").
- update_library_entry_status (propose) — status transitions
  planned → active → completed (also paused / dropped). Auto-
  stamps startedAt/completedAt on the matching transitions so the
  existing Dexie projections (streaks, progress) fire correctly.
- rate_library_entry (propose) — 1-5 stars, thin wrapper over the
  store's rate() method.
- list_library_entries (auto) — id/kind/title/status/rating/year,
  filterable by kind + status.

Coverage table in apps/mana/CLAUDE.md updated (+library, +invoices
row that wasn't listed). Total now 67 tools / 21 modules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:23:19 +02:00
Till JS
a4bc7d2ee3 feat(invoices): M8 AI tools — create/mark_paid/list/stats
The last open item from the plan. Missions can now draft invoices from
chat context, mark customer payments, and read status for autonomous
follow-up cadences.

Tool catalog (packages/shared-ai/src/tools/schemas.ts)
- create_invoice (propose) — clientName + lines[] + currency + due
- mark_invoice_paid (propose) — by id, optional back-dated paidAt
- list_invoices (auto) — with status + limit filter
- get_invoice_stats (auto) — open/overdue/YTD per currency

Had to widen the tool-parameter type vocabulary so create_invoice can
declare lines as a typed array. Touched three places:
- ToolSchema-side: the catalog's `type` string is already free-form so
  'array' / 'object' just pass through
- ModuleTool-side (apps/mana/apps/web/src/lib/data/tools/types.ts): added
  'array' | 'object' to the union so TS doesn't narrow the executor's
  param signatures
- function-schema translator (packages/shared-ai): mapParamType +
  JsonSchemaProperty both gained the two new types; the catalog-typo
  guard test now uses 'fruit' as its sentinel (array no longer unknown)

Executor (apps/mana/apps/web/src/lib/modules/invoices/tools.ts)
- coerceLines accepts either a real array or a JSON-stringified array
  (planners vary), skips malformed entries, converts major→minor units
- create_invoice pulls the generated number back from Dexie so the
  success message shows "Entwurf 2026-0042 …" — the user recognises it
- mark_invoice_paid normalises YYYY-MM-DD → ISO so the store's timestamp
  invariant (ISO throughout) stays intact
- list_invoices derives overdue on read (consistent with useAllInvoices),
  returns major-unit amounts so the LLM reasons in user-facing numbers
- get_invoice_stats returns counts + open/overdue/YTD per currency

Registration: invoicesTools added to tools/init.ts. mana-ai drift guard
is happy (41/41 green); webapp + shared-ai type-check 0 errors; full
invoice test suite 59/59 green.

Closes: docs/plans/invoices-module.md §M8. All plan milestones now DONE.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:22:20 +02:00
Till JS
0d613e1846 feat(ai): thread TokenUsage through runPlannerLoop → mana-ai budget
Carries per-round token counts from the mana-llm response body
(prompt_tokens + completion_tokens) back through LlmCompletionResponse
→ PlannerLoopResult. The loop sums across rounds and exposes a single
aggregate on result.usage.

Lets mana-ai's tick re-activate per-agent daily-token budget tracking
— tokensUsed was stubbed to 0 in the migration commit (6) because the
loop didn't surface usage yet. Now recordTokenUsage + agentTokenUsage24h
get real numbers again, and the mana_ai_tokens_used_total Prometheus
counter is accurate.

Additive only: consumers without usage needs ignore the new field,
and providers that don't return usage produce zeros (not undefined —
the loop still exposes the object so downstream branches stay trivial).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:21:34 +02:00
Till JS
5b7564b3a4 test(ai): promote MockLlmClient to a shared @mana/shared-ai export
The runPlannerLoop test file and the webapp's mission-runner test each
had their own inline scripted LLM mock — same interface, diverged
slightly. Consolidates into packages/shared-ai/src/planner/mock-llm.ts
and re-exports from the package root so any consumer can drive the
loop deterministically.

Both existing test files now use the shared client. 5 + 3 tests pass,
44 total in shared-ai still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 18:05:46 +02:00
Till JS
9f7d2f24b3 feat(companion): chat on runPlannerLoop with native function calling
The companion chat had its own ad-hoc 3-round tool-calling pipeline:
build a system prompt with tool descriptions, ask the LLM to emit
```tool JSON blocks, regex-extract, execute, feed back the result as
a synthetic user message. Same fragility class as the old text-JSON
planner — and now unnecessary since mana-llm speaks native function
calling.

Migrates companion/engine.ts to the shared runPlannerLoop, same as
the mission runner (commit 5a) and the server tick (commit 6). Tools
go to the LLM as proper function-schemas; tool_calls come back
structured; the executor runs them directly under USER_ACTOR.

Extends shared-ai/planner/loop.ts with an optional priorMessages[]
input field so the chat can preserve multi-turn history between
turns (missions don't need this and leave it empty).

Deletes the old llm-tasks/companion-chat.ts LlmTask wrapper. Nothing
else imported it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 16:45:33 +02:00
Till JS
0077752456 fix(type-check): clear the last five failures — monorepo type-check is now 76/76 green
After the mobile-app deletion unblocked \`@context/mobile\`, five more
pre-existing failures surfaced across shared packages and two services.
All were silent-masked by the postinstall \`|| true\` for months.

- **shared-ai**: \`planner/loop.ts\` imported \`ToolSchema\` from
  \`../tools/function-schema\`, which only imports (not re-exports) the
  type. Fixed to import from the source (\`../tools/schemas\`).
- **shared-logger**: \`typeof window !== 'undefined'\` blows up under
  tsconfigs that don't include the DOM lib (e.g. uload-server's
  \`bun-types\`-only config), because shared-logger is consumed via
  source import. Replaced with a \`globalThis\`-indirected check that
  compiles under any lib configuration.
- **shared-hono**: \`credits.ts\` returned \`res.json()\` directly as
  \`Promise<T | null>\`. Modern \`@types/node\` / undici types return
  \`unknown\` strictly — cast to \`T\` at the boundary so the generic
  contract is explicit.
- **uload-server**: \`routes/analytics.ts\` + \`routes/email.ts\` still
  imported \`AuthUser\` from a \`middleware/jwt-auth\` module that was
  deleted during the migration to \`@mana/shared-hono\`. Replaced with
  \`AuthVariables\` from shared-hono, which matches the actual context
  shape set by \`authMiddleware()\`.
- **manavoxel/web**: \`guestSeed\` collection entries were wrapped in
  arrow functions, but \`local-store\` expects \`T[]\` directly and
  iterates \`seed.length\` — which on a function is 0. The "guest
  seed" was silently dead; eager-evaluating \`generateGuestWorld()\`
  once and sharing the result fixes both the type and the runtime.

Verified: \`pnpm run type-check\` from the repo root now exits 0 —
76/76 tasks successful, no failures. First fully green state since
well before the postinstall \`|| true\` was introduced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:53:07 +02:00
Till JS
4daca8970b feat(shared-ai): runPlannerLoop + compact system prompt for function calling
Introduces the new planner pipeline both the webapp runner and the
mana-ai tick will swap onto in the next commits. Additive for now —
the legacy buildPlannerPrompt + parsePlannerResponse stay exported so
callers can migrate one at a time; they get removed once the last
consumer is gone.

- planner/loop.ts — runPlannerLoop orchestrates a multi-turn chat
  against a caller-supplied LlmClient. Tool-calls from the LLM are
  handed to an onToolCall callback and their results fed back as
  tool-messages. Parallel tool-calls in one turn execute sequentially
  to keep the message log linear for debugging. Stops on assistant
  stop, empty tool_calls, or a hard max-rounds ceiling (default 5).
- planner/system-prompt.ts — new buildSystemPrompt. ~40-line German
  system frame, no tool listing (the SDK-level tools field carries
  the schemas now), no JSON format example, no "please return JSON"
  plea. User frame renders mission + linked inputs + last 3
  iteration summaries, same as before.
- Five test cases covering the loop: immediate stop, single tool
  call with result feedback, parallel calls execute in order, tool
  failures propagate as tool-messages the LLM can react to, and
  maxRounds ceiling fires with the right stopReason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:31:01 +02:00
Till JS
4523ab24e3 feat(shared-ai): toolToFunctionSchema — catalog → OpenAI function-spec
Single bridge between the AI_TOOL_CATALOG shape and the wire format every
provider (Gemini, OpenAI-compat, Ollama ≥ 0.3) speaks for native tool
calling. Keeps the catalog as the source of truth — the runner never
reads catalog entries directly; it asks this converter for function-spec
shapes to hand the LLM.

- No _rationale or wrapper-tool injection: the runner doesn't need it
  and the added schema noise would hurt planner quality.
- Throws on unknown parameter types so catalog typos (e.g. "array"
  instead of "string") fail loudly instead of coercing silently.
- Preserves enum constraints; drops the enum key entirely when absent
  so Gemini doesn't reject empty-enum function-declarations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 15:24:36 +02:00
Till JS
7fb31e41b5 feat(ai): expand Quiz tools — edit/delete questions, edit meta, stats
Completes the Quiz CRUD surface for the AI agent. Five new tools:

- update_quiz (propose) — rename/archive/pin + description/category
- update_quiz_question (propose) — text, type+options, explanation;
  rejects a type swap without a matching optionsJson
- delete_quiz_question (propose) — symmetric to add_quiz_question
- get_quiz_questions (auto) — lets the planner see existing questions
  before appending more (avoids duplicates)
- get_quiz_stats (auto) — attemptCount / avgScore / bestScore /
  lastAttemptAt; enables adaptive missions like "analyze my weak spots
  and generate harder questions"

delete_quiz deliberately left out — too destructive to leave in the
AI's hands when the user can delete manually in two clicks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 19:50:24 +02:00
Till JS
2bcc3954ea feat(ai): add Quiz tools (create_quiz, add_quiz_question, list_quizzes)
Quiz is now an AI-accessible module. The agent can mint empty quizzes
and append questions across all four types (single / multi / truefalse
/ text) via a single add_quiz_question tool whose optionsJson payload
shape is documented in the catalog description. list_quizzes (auto)
returns decrypted metadata so the planner can reference existing
quizzes when extending them. Enables missions like "baue ein Quiz aus
meinen Notizen zu Thema X" — planner reads via list_notes, proposes
create_quiz, then N × add_quiz_question.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 19:29:35 +02:00
Till JS
cd594509b2 chore: clear svelte-check errors + document scene-scope pattern
- `app-registry/types.ts` now includes `tips` in the inline help shape,
  matching `ModuleHelp` and what `AppPage.svelte` actually renders.
  Drops 3 recurring type errors.
- `event-scout` template's `{ kind: 'daily' }` cadence now carries the
  required `atHour` / `atMinute` fields (daily 08:00). Drops the 4th
  type error — svelte-check is clean.
- `apps/mana/CLAUDE.md` gains a "Scene Scope" section documenting the
  pattern: wire `filterBySceneScopeBatch` in the query AND render
  `<ScopeEmptyState>` from the empty branch, so users always see why
  the list is empty.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:24:52 +02:00
Till JS
2c0d866287 feat(events): Phase 3 — AI tools, Event-Scout template, feedback loop
- Add discover_events (auto) and suggest_event (propose) to shared-ai
  tool catalog. discover_events reads the discovery feed, suggest_event
  creates a proposal to save a discovered event to the user's calendar.
- Add Event-Scout agent template with daily "Events der Woche" mission.
  Policy: discover_events=auto, suggest_event=propose, all else denied.
- Add frontend tool implementations in events/tools.ts — discover_events
  calls the feed API, suggest_event delegates to discoveryStore.saveEvent.
- Add feedback.ts — computes implicit user profile from save/dismiss
  history (category affinity + source quality as 0–2x weight multipliers).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 15:37:28 +02:00
Till JS
62aac6dfdb feat(wetter): add weather module with Open-Meteo, DWD alerts, and rain nowcast
New module providing weather data for the DACH region via three sources:
- Open-Meteo (DWD ICON-D2 model) for current conditions and 7-day forecast
- DWD warnings endpoint for severe weather alerts
- Rainbow.ai / Open-Meteo fallback for minute-level rain nowcast

Includes API proxy with in-memory caching, Svelte 5 UI with location
picker, hourly/daily forecast, alert cards, and precipitation bar chart.
Two AI tools (get_weather, get_rain_forecast) enable the companion to
answer weather questions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 03:46:15 +02:00
Till JS
efc7641a60 chore(ai): P2 batch — prompt sync, perf, dedup, scope unification
Six P2 items from the AI Workbench audit:

#7 Prompt ↔ loop budget sync:
  System prompt now says "1 bis 5 Schritte pro Planungsrunde, bis zu 5
  Planungsrunden" — matches MAX_REASONING_LOOP_ITERATIONS. Cross-ref
  comment added to runner.ts.

#9 SceneHeader: useAgents() → useAgent(id):
  Only loads the single bound agent instead of the full agent list.
  Eliminates unnecessary Dexie churn on every scene header render.

#10 Unified scope filter:
  New scope-filter.ts with filterByScopeTagMap() (batch, sync) and
  filterByScopeAsync() (per-record). Both scope-context.ts (AI) and
  scene-scope.svelte.ts (UI) now import from the shared module —
  zero duplicated filter logic.

#11 Research dedup:
  Research input ID changed from `news-research-${Date.now()}` to
  `news-research-${mission.id}` — re-runs overwrite instead of
  appending duplicates.

#12 Kontext injection policy clarified:
  loadAgentKontextAsResolvedInput no longer falls back to the global
  singleton. Comment + code aligned: kontext injection is explicit
  (via input picker), not auto. Dead loadKontextAsResolvedInput
  kept for potential future opt-in auto-inject feature.

Audit doc updated with all items marked DONE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:33:52 +02:00
Till JS
fad7f4bea3 feat(ai): guardrail layer — pre/post-plan + pre-execute checks
Add a guardrail system that runs alongside the Mission Runner pipeline
to catch obvious issues before they waste tokens or corrupt data.

Architecture (packages/shared-ai/src/guardrails/):
- types.ts: Guardrail, GuardrailResult, 4 phase interfaces
- builtin.ts: 4 built-in guardrails (always active):
  - input-size-limit: blocks >100K chars of resolved input
  - plan-step-limit: blocks plans with >25 steps (runaway planner)
  - duplicate-destructive-tool: warns if undo_drink called 2x
  - empty-required-params: blocks create_task without title
- runner.ts: runPrePlanGuardrails/runPostPlanGuardrails/runPreExecuteGuardrails

Wired into runner.ts at 3 checkpoints:
- Before deps.plan() — pre-plan check
- After plan received — post-plan check
- Before each stage() call — pre-execute check

Guardrails are synchronous, never hit the network, and produce
clear error messages when they block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:11:34 +02:00
Till JS
ed01d24f2d feat(ai): add AI tools for myday, goals, mood, finance, and times
Expand agent tool coverage from 28 to 47 tools across 16 modules:

- myday: get_myday_summary (full daily context in one call)
- goals: list_goals, get_goal_progress, create_goal, pause/resume/complete_goal
- mood: log_mood, get_mood_today, get_mood_insights (trends + correlations)
- finance: extend add_transaction, add get_month_summary + list_transactions
- times: extend start/stop_timer, add get_timer_status, get_time_stats, list_projects

All tools registered in both AI_TOOL_CATALOG (shared-ai) and webapp init.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:01:12 +02:00
Till JS
acd7e0d6b0 docs: update architecture comparison — 5/10 roadmap items done
Update report to reflect all completed work:
- Matrix: streaming , tool registration updated to 29 tools + MCP
- §5.2 Streaming: marked done
- §5.3 Tool System: marked done
- §6 Table: items 1-3 + 5 struck through with commit refs
- §8 Fazit: updated gaps and recommendations

5 of 10 roadmap items complete in one session:
1. SSE Streaming, 2. Dynamic Tool Registry, 3. Budget Enforcement,
5. MCP Server Export (27/29 tools with DB ops), plus Tool Drift Fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:00:09 +02:00
Till JS
10acabfed6 feat(ai): tag-based agent scoping — agents see only their tagged records
Connects the existing global tag system (@mana/shared-tags, 15+ module
junctions, TagSelector UI) to the AI agent model so different agents
can operate on different slices of the user's data.

Core additions:

1. Agent.scopeTagIds — optional array of global tag IDs. When set,
   the agent sees only records tagged with at least one of those tags
   (plus untagged records, which stay globally visible). Empty/undefined
   = General-Agent, sees everything. Agent-editor grows a <TagSelector>
   under "Bereiche (Tag-Scope)".

2. Per-agent kontext documents — new Dexie table `agentKontextDocs`
   (v22, encrypted, synced). Each agent can have its own markdown
   context doc, replacing the global singleton auto-inject. Runner
   tries agent kontext first, falls back to global singleton when
   the agent has no dedicated doc.

3. Ambient scope context — `withAgentScope(tagIds, fn)` sets a
   module-level scope during the reasoning loop. Auto-tools read it
   via `getAgentScopeTagIds()` and filter their result sets.
   `filterByScope(records, getTagIds)` is the reusable filter
   primitive (keeps untagged records, drops mismatched tagged ones).

4. Notes tag junction — `noteTags` table (v22) + `noteTagOps` via
   `createTagLinkOps`. Notes was the only major module without
   structured tag support. `list_notes` now calls `filterByScope`
   so a scoped agent only sees notes tagged with its scope.

Flow: mission starts → runner resolves owning agent → reads
agent.scopeTagIds → wraps entire reasoning loop in withAgentScope →
list_notes (and future list_tasks etc.) auto-filter → planner sees
only scope-relevant records → proposes scoped edits.

Runner tests: 8/8. shared-ai type-check: clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:43:33 +02:00
Till JS
56171ff13b fix(ai): resolve tool name + parameter drift between catalog and webapp
Some checks are pending
CI / Build mana-api-gateway (push) Blocked by required conditions
CI / Build mana-crawler (push) Blocked by required conditions
CI / Build mana-media (push) Blocked by required conditions
CI / Build mana-credits (push) Blocked by required conditions
CI / Build mana-web (push) Blocked by required conditions
CI / Build chat-backend (push) Blocked by required conditions
CI / Build chat-web (push) Blocked by required conditions
CI / Build todo-backend (push) Blocked by required conditions
CI / Build todo-web (push) Blocked by required conditions
CI / Build calendar-backend (push) Blocked by required conditions
CI / Build calendar-web (push) Blocked by required conditions
CI / Build clock-web (push) Blocked by required conditions
CI / Build contacts-backend (push) Blocked by required conditions
CI / Build contacts-web (push) Blocked by required conditions
CI / Build presi-web (push) Blocked by required conditions
CI / Build storage-backend (push) Blocked by required conditions
CI / Build storage-web (push) Blocked by required conditions
CI / Build telegram-stats-bot (push) Blocked by required conditions
CI / Build food-backend (push) Blocked by required conditions
CI / Build food-web (push) Blocked by required conditions
CI / Build skilltree-web (push) Blocked by required conditions
Docker Validate / Validate Dockerfiles (push) Waiting to run
Docker Validate / Build calendar-web (push) Blocked by required conditions
Docker Validate / Build quotes-web (push) Blocked by required conditions
Docker Validate / Build todo-backend (push) Blocked by required conditions
Docker Validate / Build todo-web (push) Blocked by required conditions
Docker Validate / Build mana-auth (push) Blocked by required conditions
Docker Validate / Build mana-sync (push) Blocked by required conditions
Docker Validate / Build mana-media (push) Blocked by required conditions
Mirror to Forgejo / Push to Forgejo (push) Waiting to run
8 mismatches fixed between AI_TOOL_CATALOG and webapp module tools:

Tool name renames (webapp → catalog name):
- record_visit → visit_place (places)
- undo_last_drink → undo_drink (drink)
- location_log → get_current_location (places, catalog side)

Catalog parameter fixes (aligned to webapp execute functions):
- create_event: startIso/endIso → startTime/endTime + isAllDay/location/description
- create_note: title required→optional, content optional→required
- complete_tasks_by_title: titleSubstring → titleMatch
- create_place: add latitude/longitude (required) + category enum + address
- create_journal_entry: English mood enum → German mood enum

Webapp parameter additions:
- create_contact: add company + notes params (store already accepts them)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:18:51 +02:00
Till JS
d40a61119e refactor(ai): dynamic tool registry — single-source catalog in shared-ai
Introduce AI_TOOL_CATALOG in @mana/shared-ai as the single source of truth
for all 29 tool schemas (17 propose + 12 auto). Both the webapp policy and
the server-side mana-ai planner now derive their tool lists from the catalog
instead of maintaining independent hardcoded copies.

- New: packages/shared-ai/src/tools/schemas.ts — catalog with ToolSchema type
- Rewrite: proposable-tools.ts — derived from catalog instead of hardcoded array
- Rewrite: services/mana-ai/src/planner/tools.ts — 277→30 lines (imports from catalog)
- Simplify: webapp policy.ts — derives AUTO/PROPOSE from catalog defaultPolicy

Adding a new tool now requires 2 files instead of 3-5:
1. Add schema to AI_TOOL_CATALOG (shared-ai)
2. Add execute function in the module's tools.ts (webapp)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:06:07 +02:00
Till JS
be81d11dc3 feat(ai): SSE streaming for foreground Mission Runner
Enable real-time token streaming during the planner "calling-llm" phase
so the user sees live progress ("empfange Plan… 128 tokens") instead of
a static spinner. The parser still receives the full text once complete —
no partial-JSON risk.

Changes:
- Extract shared SSE parser from playground into @mana/shared-llm/sse-parser
- remote.ts: use stream:true when onToken callback is provided
- AiPlanInput: add optional onToken field (shared-ai)
- ai-plan task: pass onToken through to backend.generate()
- runner.ts: throttled (500ms) phaseDetail updates during streaming
- Playground: refactored to use shared SSE parser

Also includes: AI agent architecture comparison report (docs/reports/)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:32:43 +02:00
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