mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:21:10 +02:00
feat(ai-agents): Template gallery — 3 ready-to-use agent bundles
First pass of the Multi-Agent discoverability UX. A new /agents/ templates route showcases pre-configured agents; clicking one creates agent + scene + starter mission(s) as a single bundle. Addresses the "blank form anxiety" + "user doesn't know what agents are for" observations from the UX brainstorm. Three templates for v1 (shared-ai/src/agents/templates/): - 🔍 Recherche-Agent — reads sources one by one, writes a note per source, summarizes into a report. Manual-cadence mission; all writes propose so user curates. - 🧭 Kontext-Agent — learns about the user via a weekly check-in. Reads kontext/notes/goals, asks 2-3 questions, proposes a diff- style context update. Weekly Sunday cadence. - 🌅 Today-Agent — researches "on this day" history each morning, writes a 4-8 line German poem, proposes a journal note. Daily 7am cadence. A "delight" agent, not a productive one. Each template packs (agent config, scene layout, starter mission): - AgentTemplate type lives in @mana/shared-ai — pure data, no runtime imports. Adding a new template = drop a file in templates/ and extend ALL_TEMPLATES. - Template-specific policies derive from the proposable-tool list so drift-guard catches divergence from the canonical set. - Starter missions default to startPaused=true — user sees the mission ready-to-go and hits Play when ready. Prevents surprise autonomous work on first apply. Applicator (data/ai/agents/apply-template.ts): - Creates agent → scene (if template defines one) → missions in order. Agent failure = abort; scene/mission failures surface as warnings in the result without blocking. - Duplicate-name handling: falls through to findByName, returns existing agent with wasExisting=true; scene is skipped in that case to avoid clone-proliferation. Gallery page /(app)/agents/templates/+page.svelte: - Three large cards side-by-side (stacks on mobile) with avatar / label / tagline / meta chips (Scene, N Missionen). - Click opens detail panel with full description, scene preview (app-ids + widths), mission preview (title / objective / cadence), and override checkboxes (create scene, create missions, start active vs paused). - Success panel shows what landed with warnings inline; CTA back to workbench. Discoverability in /ai-agents module: - Bar now has two buttons: "Aus Template" (primary, goto templates route) + "Eigener Agent" (secondary, opens the existing blank-form create mode). - When only the default "Mana" agent exists, render a dashed promo banner at the top linking to the template gallery. Disappears as soon as the user has a second agent. Tests: webapp svelte-check 0 errors, 0 warnings. shared-ai 26/26. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4d9b16a683
commit
7822340ea0
10 changed files with 1150 additions and 6 deletions
154
apps/mana/apps/web/src/lib/data/ai/agents/apply-template.ts
Normal file
154
apps/mana/apps/web/src/lib/data/ai/agents/apply-template.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* Template applicator — turns an AgentTemplate from `@mana/shared-ai`
|
||||
* into concrete Dexie records: an Agent, optionally a workbench Scene,
|
||||
* optionally starter Missions.
|
||||
*
|
||||
* Ordering matters: agent first (so mission.agentId can reference it),
|
||||
* then scene (so `setActive` lands on a scene that contains the
|
||||
* relevant apps), then missions (so they show up under the agent).
|
||||
*
|
||||
* Error semantics: failures bubble up but the ones that happened
|
||||
* before are NOT rolled back — user is told what did and didn't land.
|
||||
* Pure-transaction semantics aren't worth the wrapper complexity for
|
||||
* a 3-step sequence that is already idempotent:
|
||||
* - duplicate agent name → returns existing agent (getOrCreate-ish)
|
||||
* - scene creation is a fresh insert, no dedup needed
|
||||
* - missions use fresh UUIDs, no dedup needed
|
||||
*/
|
||||
|
||||
import { createAgent, findByName, DuplicateAgentNameError } from './store';
|
||||
import { createMission, pauseMission } from '../missions/store';
|
||||
import { workbenchScenesStore } from '$lib/stores/workbench-scenes.svelte';
|
||||
import type { AgentTemplate } from '@mana/shared-ai';
|
||||
import type { Agent } from './types';
|
||||
|
||||
export interface ApplyTemplateOptions {
|
||||
/** Create the template's scene + set it active. Default true when the
|
||||
* template defines a scene; false when it doesn't. */
|
||||
createScene?: boolean;
|
||||
/** Create the template's starter missions. Default true. */
|
||||
createMissions?: boolean;
|
||||
/** When true, starter missions are left in whatever `startPaused`
|
||||
* the template declares (usually paused). When false, override to
|
||||
* active — Power-User opt-in that skips the "click Play" step. */
|
||||
respectPauseHint?: boolean;
|
||||
}
|
||||
|
||||
export interface ApplyTemplateResult {
|
||||
/** The agent that was created — OR the pre-existing agent with the
|
||||
* same name that we re-used. `wasExisting` tells you which. */
|
||||
readonly agent: Agent;
|
||||
readonly wasExisting: boolean;
|
||||
readonly sceneId?: string;
|
||||
readonly missionIds: readonly string[];
|
||||
/** Any non-fatal errors from the sequence. Agent is guaranteed when
|
||||
* this array is empty on agent slot; scene/mission failures still
|
||||
* return here so the UI can surface them without blocking. */
|
||||
readonly warnings: readonly string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a template end-to-end. Returns a result object describing what
|
||||
* actually landed in Dexie. Call sites render a success panel or a
|
||||
* partial-failure panel based on `warnings` + presence of each field.
|
||||
*/
|
||||
export async function applyTemplate(
|
||||
template: AgentTemplate,
|
||||
opts: ApplyTemplateOptions = {}
|
||||
): Promise<ApplyTemplateResult> {
|
||||
const {
|
||||
createScene = template.scene !== undefined,
|
||||
createMissions = true,
|
||||
respectPauseHint = true,
|
||||
} = opts;
|
||||
|
||||
const warnings: string[] = [];
|
||||
|
||||
// 1. Agent — the only required piece. If duplicate name, re-use the
|
||||
// existing agent (idempotent "apply twice" behavior).
|
||||
let agent: Agent;
|
||||
let wasExisting = false;
|
||||
try {
|
||||
agent = await createAgent({
|
||||
name: template.agent.name,
|
||||
avatar: template.agent.avatar,
|
||||
role: template.agent.role,
|
||||
systemPrompt: template.agent.systemPrompt,
|
||||
memory: template.agent.memory,
|
||||
policy: template.agent.policy,
|
||||
maxTokensPerDay: template.agent.maxTokensPerDay,
|
||||
maxConcurrentMissions: template.agent.maxConcurrentMissions,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof DuplicateAgentNameError) {
|
||||
const existing = await findByName(template.agent.name);
|
||||
if (!existing) {
|
||||
throw err;
|
||||
}
|
||||
agent = existing;
|
||||
wasExisting = true;
|
||||
warnings.push(
|
||||
`Ein Agent mit Namen "${template.agent.name}" existiert bereits — Template nutzt diesen.`
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Scene — skipped on re-apply so we don't generate Scene-Clones
|
||||
// on every click.
|
||||
let sceneId: string | undefined;
|
||||
if (createScene && template.scene && !wasExisting) {
|
||||
try {
|
||||
sceneId = await workbenchScenesStore.createScene({
|
||||
name: template.scene.name,
|
||||
description: template.scene.description ?? null,
|
||||
seedApps: [...template.scene.openApps],
|
||||
setActive: true,
|
||||
});
|
||||
} catch (err) {
|
||||
warnings.push(
|
||||
`Scene konnte nicht angelegt werden: ${err instanceof Error ? err.message : String(err)}`
|
||||
);
|
||||
}
|
||||
} else if (createScene && wasExisting) {
|
||||
warnings.push(
|
||||
'Scene übersprungen weil der Agent schon existierte — öffne die Scene manuell falls gewünscht.'
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Missions — paused by default per template hint. Reapply on an
|
||||
// existing agent is idempotent-ish: we create NEW missions (they
|
||||
// have fresh UUIDs) but the UI should make that obvious.
|
||||
const missionIds: string[] = [];
|
||||
if (createMissions && template.missions) {
|
||||
for (const m of template.missions) {
|
||||
try {
|
||||
const mission = await createMission({
|
||||
title: m.title,
|
||||
objective: m.objective,
|
||||
conceptMarkdown: m.conceptMarkdown,
|
||||
cadence: m.cadence,
|
||||
inputs: m.inputs ? [...m.inputs] : undefined,
|
||||
agentId: agent.id,
|
||||
});
|
||||
if (respectPauseHint && m.startPaused !== false) {
|
||||
await pauseMission(mission.id);
|
||||
}
|
||||
missionIds.push(mission.id);
|
||||
} catch (err) {
|
||||
warnings.push(
|
||||
`Starter-Mission "${m.title}" fehlgeschlagen: ${err instanceof Error ? err.message : String(err)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
agent,
|
||||
wasExisting,
|
||||
sceneId,
|
||||
missionIds,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
|
@ -17,8 +17,10 @@
|
|||
handle the common "let the agent touch todo but not calendar" case.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { ArrowLeft, Plus, Pause, Play, Archive, Trash } from '@mana/shared-icons';
|
||||
import { ArrowLeft, Plus, Pause, Play, Archive, Trash, Sparkle } from '@mana/shared-icons';
|
||||
import { goto } from '$app/navigation';
|
||||
import { useAgents } from '$lib/data/ai/agents/queries';
|
||||
import { DEFAULT_AGENT_ID } from '@mana/shared-ai';
|
||||
import {
|
||||
createAgent,
|
||||
updateAgent,
|
||||
|
|
@ -215,16 +217,34 @@
|
|||
</script>
|
||||
|
||||
{#if mode === 'list'}
|
||||
{@const onlyDefaultAgent = agents.value.length === 1 && agents.value[0].id === DEFAULT_AGENT_ID}
|
||||
<div class="pane">
|
||||
<header class="bar">
|
||||
<button type="button" class="primary" onclick={() => (mode = 'create')}>
|
||||
<Plus size={14} /><span>Neuer Agent</span>
|
||||
<button type="button" class="primary" onclick={() => goto('/agents/templates')}>
|
||||
<Sparkle size={14} /><span>Aus Template</span>
|
||||
</button>
|
||||
<button type="button" class="secondary" onclick={() => (mode = 'create')}>
|
||||
<Plus size={14} /><span>Eigener Agent</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{#if onlyDefaultAgent}
|
||||
<button type="button" class="promo" onclick={() => goto('/agents/templates')}>
|
||||
<span class="promo-icon"><Sparkle size={16} weight="fill" /></span>
|
||||
<span class="promo-body">
|
||||
<strong>Starte mit einem Template</strong>
|
||||
<span class="promo-sub">
|
||||
Recherche · Kontext · Today — vorgefertigte Agenten mit passender Scene und
|
||||
Starter-Mission.
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if agents.value.length === 0}
|
||||
<p class="empty">
|
||||
Noch keine Agenten. Ein Default-Agent „Mana" wird beim ersten Login automatisch angelegt;
|
||||
für weitere persona-basierte Agenten klicke auf „Neuer Agent".
|
||||
für weitere persona-basierte Agenten klicke auf „Aus Template" oder „Eigener Agent".
|
||||
</p>
|
||||
{:else}
|
||||
<ul class="m-list">
|
||||
|
|
@ -446,6 +466,7 @@
|
|||
.bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.primary {
|
||||
display: inline-flex;
|
||||
|
|
@ -463,6 +484,52 @@
|
|||
.primary:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.secondary {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: 0.375rem;
|
||||
background: hsl(var(--color-surface));
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.promo {
|
||||
display: flex;
|
||||
gap: 0.625rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border: 1px dashed color-mix(in oklab, hsl(var(--color-primary)) 50%, transparent);
|
||||
border-radius: 0.5rem;
|
||||
background: color-mix(in oklab, hsl(var(--color-primary)) 6%, hsl(var(--color-surface)));
|
||||
color: hsl(var(--color-foreground));
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
.promo:hover {
|
||||
background: color-mix(in oklab, hsl(var(--color-primary)) 10%, hsl(var(--color-surface)));
|
||||
}
|
||||
.promo-icon {
|
||||
color: hsl(var(--color-primary));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.promo-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
}
|
||||
.promo-body strong {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.promo-sub {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
.empty {
|
||||
padding: 1.5rem 1rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,512 @@
|
|||
<!--
|
||||
Agent-Templates Gallery — /agents/templates
|
||||
|
||||
Landing page for picking a pre-configured AI agent. Three cards
|
||||
side-by-side on desktop, stacked on mobile. Clicking a card opens a
|
||||
detail panel with description + "apply" options.
|
||||
|
||||
Designed as a standalone route (not an AppPage) so it can be deep-
|
||||
linked from /welcome, from in-module banners, or shared directly.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { ArrowLeft, Check, Play } from '@mana/shared-icons';
|
||||
import { goto } from '$app/navigation';
|
||||
import { ALL_TEMPLATES, type AgentTemplate } from '@mana/shared-ai';
|
||||
import { applyTemplate } from '$lib/data/ai/agents/apply-template';
|
||||
|
||||
let selected = $state<AgentTemplate | null>(null);
|
||||
let applying = $state(false);
|
||||
let result = $state<{
|
||||
agentName: string;
|
||||
sceneCreated: boolean;
|
||||
missionCount: number;
|
||||
wasExisting: boolean;
|
||||
warnings: readonly string[];
|
||||
} | null>(null);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Override toggles — default to the "smart" values we recommend.
|
||||
let optCreateScene = $state(true);
|
||||
let optCreateMissions = $state(true);
|
||||
let optStartActive = $state(false); // false = respect paused hint
|
||||
|
||||
function openDetail(t: AgentTemplate) {
|
||||
selected = t;
|
||||
result = null;
|
||||
error = null;
|
||||
optCreateScene = t.scene !== undefined;
|
||||
optCreateMissions = t.missions !== undefined && t.missions.length > 0;
|
||||
optStartActive = false;
|
||||
}
|
||||
|
||||
async function handleApply() {
|
||||
if (!selected) return;
|
||||
applying = true;
|
||||
error = null;
|
||||
try {
|
||||
const r = await applyTemplate(selected, {
|
||||
createScene: optCreateScene,
|
||||
createMissions: optCreateMissions,
|
||||
respectPauseHint: !optStartActive,
|
||||
});
|
||||
result = {
|
||||
agentName: r.agent.name,
|
||||
sceneCreated: r.sceneId !== undefined,
|
||||
missionCount: r.missionIds.length,
|
||||
wasExisting: r.wasExisting,
|
||||
warnings: r.warnings,
|
||||
};
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
applying = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Agent-Templates — Mana</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="page">
|
||||
<header class="header">
|
||||
<button type="button" class="back" onclick={() => goto('/')}>
|
||||
<ArrowLeft size={14} /><span>Zurück zum Workbench</span>
|
||||
</button>
|
||||
<h1>Agent-Templates</h1>
|
||||
<p class="sub">
|
||||
Vorgefertigte AI-Agenten, die sofort loslaufen. Jedes Template legt einen Agent, eine passende
|
||||
Scene und eine Starter-Mission an — die Mission ist standardmäßig pausiert, damit du bewusst
|
||||
Play drückst.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="grid">
|
||||
{#each ALL_TEMPLATES as t (t.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="card"
|
||||
class:selected={selected?.id === t.id}
|
||||
style="--accent: {t.color}"
|
||||
onclick={() => openDetail(t)}
|
||||
>
|
||||
<span class="avatar">{t.agent.avatar}</span>
|
||||
<span class="label">{t.label}</span>
|
||||
<span class="tagline">{t.tagline}</span>
|
||||
<span class="meta">
|
||||
{#if t.scene}<span class="chip">Scene</span>{/if}
|
||||
{#if t.missions && t.missions.length > 0}
|
||||
<span class="chip"
|
||||
>{t.missions.length} Mission{t.missions.length !== 1 ? 'en' : ''}</span
|
||||
>
|
||||
{/if}
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if selected}
|
||||
<section class="detail" style="--accent: {selected.color}">
|
||||
<header class="detail-head">
|
||||
<span class="detail-avatar">{selected.agent.avatar}</span>
|
||||
<div>
|
||||
<h2>{selected.label}</h2>
|
||||
<p class="detail-role">{selected.agent.role}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="detail-desc">{selected.description}</div>
|
||||
|
||||
{#if selected.scene}
|
||||
<section class="preview">
|
||||
<h3>Scene-Layout</h3>
|
||||
<p class="preview-name">
|
||||
<strong>{selected.scene.name}</strong>
|
||||
{#if selected.scene.description}
|
||||
— {selected.scene.description}{/if}
|
||||
</p>
|
||||
<ul class="apps-preview">
|
||||
{#each selected.scene.openApps as app (app.appId)}
|
||||
<li>
|
||||
<code>{app.appId}</code>
|
||||
{#if app.widthPx}<span class="app-w">{app.widthPx}px</span>{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if selected.missions && selected.missions.length > 0}
|
||||
<section class="preview">
|
||||
<h3>Starter-Missionen</h3>
|
||||
<ul class="missions-preview">
|
||||
{#each selected.missions as m}
|
||||
<li>
|
||||
<strong>{m.title}</strong>
|
||||
<p>{m.objective}</p>
|
||||
<span class="cadence">
|
||||
{#if m.cadence.kind === 'manual'}manuell auslösen
|
||||
{:else if m.cadence.kind === 'daily'}täglich {m.cadence.atHour}:{String(
|
||||
m.cadence.atMinute
|
||||
).padStart(2, '0')}
|
||||
{:else if m.cadence.kind === 'weekly'}wöchentlich, Tag {m.cadence.dayOfWeek} um {m
|
||||
.cadence.atHour}:00
|
||||
{:else if m.cadence.kind === 'interval'}alle {m.cadence.everyMinutes} Minuten
|
||||
{:else}cron: {m.cadence.expression}
|
||||
{/if}
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<section class="options">
|
||||
<h3>Optionen</h3>
|
||||
{#if selected.scene}
|
||||
<label class="opt">
|
||||
<input type="checkbox" bind:checked={optCreateScene} />
|
||||
<span>Scene „{selected.scene.name}" anlegen und direkt öffnen</span>
|
||||
</label>
|
||||
{/if}
|
||||
{#if selected.missions && selected.missions.length > 0}
|
||||
<label class="opt">
|
||||
<input type="checkbox" bind:checked={optCreateMissions} />
|
||||
<span>Starter-Mission(en) mit anlegen</span>
|
||||
</label>
|
||||
<label class="opt" class:disabled={!optCreateMissions}>
|
||||
<input type="checkbox" bind:checked={optStartActive} disabled={!optCreateMissions} />
|
||||
<span>Mission(en) sofort aktivieren (Standard: pausiert)</span>
|
||||
</label>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
{#if result}
|
||||
<div class="result success">
|
||||
<Check size={16} />
|
||||
<div>
|
||||
<strong>
|
||||
{result.wasExisting
|
||||
? `„${result.agentName}" existierte schon — wiederverwendet.`
|
||||
: `Agent „${result.agentName}" angelegt.`}
|
||||
</strong>
|
||||
<p>
|
||||
{#if result.sceneCreated}Scene angelegt + aktiviert.{/if}
|
||||
{#if result.missionCount > 0}
|
||||
{result.missionCount} Mission{result.missionCount !== 1 ? 'en' : ''}
|
||||
{optStartActive ? 'aktiviert' : 'pausiert angelegt'}.
|
||||
{/if}
|
||||
</p>
|
||||
{#if result.warnings.length > 0}
|
||||
<ul class="warnings">
|
||||
{#each result.warnings as w}
|
||||
<li>⚠ {w}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-actions">
|
||||
<button type="button" class="btn-ghost" onclick={() => (selected = null)}>
|
||||
Weiteres Template auswählen
|
||||
</button>
|
||||
<button type="button" class="btn-primary" onclick={() => goto('/')}>
|
||||
Zum Workbench
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
{#if error}
|
||||
<div class="result error">
|
||||
<strong>Konnte Template nicht anwenden</strong>
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="apply-row">
|
||||
<button type="button" class="btn-primary" onclick={handleApply} disabled={applying}>
|
||||
<Play size={14} />
|
||||
<span>{applying ? 'Lege an…' : `Template „${selected.label}" anwenden`}</span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.page {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1.5rem 4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.back {
|
||||
align-self: flex-start;
|
||||
display: inline-flex;
|
||||
gap: 0.375rem;
|
||||
align-items: center;
|
||||
padding: 0.375rem 0.625rem;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: 0.375rem;
|
||||
background: hsl(var(--color-surface));
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
.sub {
|
||||
margin: 0;
|
||||
font-size: 0.9375rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
max-width: 60ch;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
padding: 1.25rem;
|
||||
border: 2px solid hsl(var(--color-border));
|
||||
border-radius: 0.75rem;
|
||||
background: hsl(var(--color-surface));
|
||||
color: hsl(var(--color-foreground));
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.card:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.card.selected {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 25%, transparent);
|
||||
}
|
||||
.avatar {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.label {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tagline {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
line-height: 1.4;
|
||||
}
|
||||
.meta {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.chip {
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in oklab, var(--accent) 15%, transparent);
|
||||
color: var(--accent);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.detail {
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
background: hsl(var(--color-surface));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
border-top: 3px solid var(--accent);
|
||||
}
|
||||
.detail-head {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
.detail-avatar {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.detail-head h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.detail-role {
|
||||
margin: 0.125rem 0 0;
|
||||
font-size: 0.8125rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
.detail-desc {
|
||||
white-space: pre-wrap;
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.preview {
|
||||
padding: 0.875rem 1rem;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: 0.5rem;
|
||||
background: hsl(var(--color-background));
|
||||
}
|
||||
.preview h3 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
.preview-name {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.apps-preview {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.apps-preview li {
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
background: hsl(var(--color-surface));
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
font-size: 0.75rem;
|
||||
display: inline-flex;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
}
|
||||
.apps-preview code {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.app-w {
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.missions-preview {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.missions-preview li strong {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.missions-preview li p {
|
||||
margin: 0.125rem 0;
|
||||
font-size: 0.8125rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
.cadence {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
font-style: italic;
|
||||
}
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
.options h3 {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
.opt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.opt.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.apply-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.btn-primary {
|
||||
display: inline-flex;
|
||||
gap: 0.375rem;
|
||||
align-items: center;
|
||||
padding: 0.625rem 1rem;
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 0.5rem;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.btn-ghost {
|
||||
padding: 0.625rem 1rem;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: 0.5rem;
|
||||
background: hsl(var(--color-surface));
|
||||
color: hsl(var(--color-foreground));
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
.result {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
padding: 0.875rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid;
|
||||
}
|
||||
.result.success {
|
||||
border-color: #1b7a3a;
|
||||
background: #d7f7e3;
|
||||
color: #0f3f1d;
|
||||
}
|
||||
.result.error {
|
||||
border-color: #8a1b1b;
|
||||
background: #f7d7d7;
|
||||
color: #3f0f0f;
|
||||
}
|
||||
.result p {
|
||||
margin: 0.25rem 0 0;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.result strong {
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
.warnings {
|
||||
margin: 0.375rem 0 0;
|
||||
padding-left: 1rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
.result-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,2 +1,11 @@
|
|||
export type { Agent, AgentState } from './types';
|
||||
export { DEFAULT_AGENT_ID, DEFAULT_AGENT_NAME } from './types';
|
||||
|
||||
export type {
|
||||
AgentTemplate,
|
||||
AgentTemplateAgentPart,
|
||||
AgentTemplateScenePart,
|
||||
AgentTemplateSceneApp,
|
||||
AgentTemplateMissionPart,
|
||||
} from './templates';
|
||||
export { ALL_TEMPLATES, getTemplateById } from './templates';
|
||||
|
|
|
|||
95
packages/shared-ai/src/agents/templates/context.ts
Normal file
95
packages/shared-ai/src/agents/templates/context.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { AI_PROPOSABLE_TOOL_NAMES } from '../../policy/proposable-tools';
|
||||
import type { AgentTemplate } from './types';
|
||||
import type { AiPolicy } from '../../policy/types';
|
||||
|
||||
/**
|
||||
* Context agent — tries to learn as much as possible about the user by
|
||||
* asking questions + reading available context, then consolidates into
|
||||
* the Kontext-document. Everything is propose so the user curates their
|
||||
* own profile.
|
||||
*/
|
||||
|
||||
const CONTEXT_POLICY: AiPolicy = {
|
||||
tools: Object.fromEntries(AI_PROPOSABLE_TOOL_NAMES.map((n) => [n, 'propose'])),
|
||||
defaultsByModule: {
|
||||
kontext: 'propose',
|
||||
notes: 'propose',
|
||||
goals: 'auto',
|
||||
},
|
||||
defaultForAi: 'propose',
|
||||
};
|
||||
|
||||
export const contextTemplate: AgentTemplate = {
|
||||
id: 'context',
|
||||
label: 'Kontext-Agent',
|
||||
tagline: 'Lernt dich kennen, damit andere Agents besser arbeiten',
|
||||
description: `Der Agent fragt dich gezielt Fragen und destilliert die Antworten
|
||||
in dein Kontext-Dokument. Andere Agents (Recherche, Today, …) lesen dieses
|
||||
Dokument als Prompt-Zusatz — je besser es gepflegt ist, desto relevanter werden
|
||||
ihre Vorschläge.
|
||||
|
||||
Was er tut:
|
||||
|
||||
1. Liest was schon in deinem Kontext + Notizen + Goals steht
|
||||
2. Stellt gezielt Fragen zu Lücken ("Was treibt dich aktuell um?", "Welche Projekte liegen an?")
|
||||
3. Verdichtet deine Antworten zu einem strukturierten Kontext-Update (als Vorschlag)
|
||||
|
||||
Alles läuft als Vorschlag — du bestätigst welche Version deines Profils gespeichert wird.`,
|
||||
category: 'context',
|
||||
color: '#D946EF',
|
||||
agent: {
|
||||
name: 'Kontext-Agent',
|
||||
avatar: '🧭',
|
||||
role: 'Lernt dich kennen und pflegt dein Kontext-Dokument',
|
||||
systemPrompt: `Du bist ein neugieriger aber respektvoller Kontext-Agent. Ziel: verdichte was der User von sich selbst preisgibt zu einem gut strukturierten Kontext-Dokument, das andere AI-Agents als Prompt-Input nutzen können.
|
||||
|
||||
Vorgehen:
|
||||
1. Lies immer zuerst das existierende kontextDoc + die letzten 5 Notizen + Goals, bevor du Fragen stellst.
|
||||
2. Frage pro Iteration höchstens 2-3 konkrete Fragen. Keine Massenbefragung.
|
||||
3. Schlage beim Update des Kontext-Dokuments immer eine Diff-Ansicht vor — nie Full-Replace.
|
||||
4. Respektiere Lücken: wenn der User etwas nicht teilen will, nimm das auf ("Thema nicht relevant für den Agent").
|
||||
5. Schreibe das Kontext-Dokument auf Deutsch, in Ich-Form ("Ich bin…", "Mir ist wichtig…").
|
||||
|
||||
Struktur im Kontext-Dokument:
|
||||
- # Wer ich bin (Rolle, Hintergrund)
|
||||
- # Was mich umtreibt (aktuelle Projekte, Themen)
|
||||
- # Wie ich arbeite (Arbeitsstil, Präferenzen)
|
||||
- # Was ich lieber nicht teile (Opt-outs)`,
|
||||
memory: `# Kontext-Ziele
|
||||
|
||||
(Hier kannst du festhalten welche Aspekte von dir der Agent priorisieren soll —
|
||||
z.B. "fokus auf berufliche Projekte, privat ist mir egal" oder "frag mich zu
|
||||
meinen Hobbys" etc.)
|
||||
`,
|
||||
policy: CONTEXT_POLICY,
|
||||
maxConcurrentMissions: 1,
|
||||
},
|
||||
scene: {
|
||||
name: 'Kontext',
|
||||
description: 'Dein Profil für alle anderen Agents',
|
||||
openApps: [
|
||||
{ appId: 'kontext', widthPx: 720 },
|
||||
{ appId: 'ai-missions', widthPx: 440 },
|
||||
{ appId: 'ai-workbench', widthPx: 440 },
|
||||
],
|
||||
},
|
||||
missions: [
|
||||
{
|
||||
title: 'Kontext verdichten',
|
||||
objective:
|
||||
'Lies was schon da ist, identifiziere Lücken, stelle 2-3 Fragen und schlage ein Kontext-Update vor.',
|
||||
conceptMarkdown: `# Kontext-Erkundung
|
||||
|
||||
Der Agent tickt wöchentlich und macht einen "Kontext-Check":
|
||||
|
||||
1. Was hat sich seit dem letzten Update geändert?
|
||||
2. Welche Lücken sind noch im Profil?
|
||||
3. 2-3 neue Fragen die der User beantworten kann (via Proposal-Inbox)
|
||||
|
||||
**Tipp:** Beantworte die Fragen einfach als Note-Antwort — der Agent liest sie
|
||||
beim nächsten Tick.`,
|
||||
cadence: { kind: 'weekly', dayOfWeek: 0, atHour: 10 },
|
||||
startPaused: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
30
packages/shared-ai/src/agents/templates/index.ts
Normal file
30
packages/shared-ai/src/agents/templates/index.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Agent-Templates — canonical set of pre-configured agents the user can
|
||||
* apply from the gallery in `/agents/templates`.
|
||||
*
|
||||
* Adding a new template: drop a new file next to `research.ts`,
|
||||
* `context.ts`, `today.ts`, export the `AgentTemplate` constant, and
|
||||
* add it to the `ALL_TEMPLATES` array below.
|
||||
*/
|
||||
|
||||
import { researchTemplate } from './research';
|
||||
import { contextTemplate } from './context';
|
||||
import { todayTemplate } from './today';
|
||||
|
||||
export type {
|
||||
AgentTemplate,
|
||||
AgentTemplateAgentPart,
|
||||
AgentTemplateScenePart,
|
||||
AgentTemplateSceneApp,
|
||||
AgentTemplateMissionPart,
|
||||
} from './types';
|
||||
|
||||
export const ALL_TEMPLATES = [researchTemplate, contextTemplate, todayTemplate] as const;
|
||||
|
||||
export { researchTemplate, contextTemplate, todayTemplate };
|
||||
|
||||
/** Lookup helper — returns the template matching the given id, or
|
||||
* undefined. Useful for deep-links `/agents/templates?pick=research`. */
|
||||
export function getTemplateById(id: string): (typeof ALL_TEMPLATES)[number] | undefined {
|
||||
return ALL_TEMPLATES.find((t) => t.id === id);
|
||||
}
|
||||
87
packages/shared-ai/src/agents/templates/research.ts
Normal file
87
packages/shared-ai/src/agents/templates/research.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import { AI_PROPOSABLE_TOOL_NAMES } from '../../policy/proposable-tools';
|
||||
import type { AgentTemplate } from './types';
|
||||
import type { AiPolicy } from '../../policy/types';
|
||||
|
||||
/**
|
||||
* Research agent — gets a topic + sources, writes a note per source,
|
||||
* then summarizes into a report note. Biased toward propose for every
|
||||
* note write so the user reviews what gets stored.
|
||||
*/
|
||||
|
||||
const RESEARCH_POLICY: AiPolicy = {
|
||||
tools: Object.fromEntries(AI_PROPOSABLE_TOOL_NAMES.map((n) => [n, 'propose'])),
|
||||
defaultsByModule: {
|
||||
notes: 'propose',
|
||||
// Read-only modules default to auto — the agent is allowed to
|
||||
// peek into kontext / goals without nagging the user.
|
||||
goals: 'auto',
|
||||
kontext: 'auto',
|
||||
},
|
||||
defaultForAi: 'propose',
|
||||
};
|
||||
|
||||
export const researchTemplate: AgentTemplate = {
|
||||
id: 'research',
|
||||
label: 'Recherche-Agent',
|
||||
tagline: 'Liest Quellen, schreibt Notizen, destilliert einen Bericht',
|
||||
description: `Gib dem Agent ein Thema und eine Liste von Quellen-URLs. Er:
|
||||
|
||||
1. Liest jede Quelle einzeln
|
||||
2. Schreibt pro Quelle eine strukturierte Notiz mit Kernaussagen + Zitaten
|
||||
3. Fasst am Ende alle Notizen zu einem Gesamt-Bericht zusammen
|
||||
4. Verlinkt im Bericht zurück auf die Quellen-Notizen
|
||||
|
||||
Jede Notiz wird als Vorschlag angelegt — du bestätigst was wirklich gespeichert wird.`,
|
||||
category: 'research',
|
||||
color: '#0EA5E9',
|
||||
agent: {
|
||||
name: 'Recherche-Agent',
|
||||
avatar: '🔍',
|
||||
role: 'Liest Quellen, schreibt Notizen, erstellt Gesamtberichte',
|
||||
systemPrompt: `Du bist ein systematischer Recherche-Agent. Deine Aufgabe ist Quellen in strukturierte Notizen zu verwandeln und diese dann zu einem Gesamtbericht zu destillieren.
|
||||
|
||||
Vorgehen:
|
||||
1. Pro Quelle: schreibe eine Notiz mit Titel "Q: [Quelle]", Kerninhalt als 3-7 Bullet-Points, direkte Zitate in Blockquotes.
|
||||
2. Verweise auf keine Quelle die du nicht wirklich gelesen hast. Erfinde nichts.
|
||||
3. Für den Gesamtbericht: fasse die Notizen unter 3-5 Thesen zusammen, mit Cross-Links zurück auf die Quellen-Notizen.
|
||||
|
||||
Schreib deutsch, klar, ohne Marketing-Sprache.`,
|
||||
memory: `# Recherche-Richtlinien
|
||||
|
||||
(Hier kannst du festhalten wie du recherchiert haben willst — z.B. bevorzugte Sprache,
|
||||
Zitier-Stil, Themengebiete die dich besonders interessieren, Quellen denen du vertraust.)
|
||||
`,
|
||||
policy: RESEARCH_POLICY,
|
||||
maxConcurrentMissions: 1,
|
||||
},
|
||||
scene: {
|
||||
name: 'Recherche',
|
||||
description: 'Quellen lesen, Notizen schreiben, Berichte erstellen',
|
||||
openApps: [
|
||||
{ appId: 'notes', widthPx: 540 },
|
||||
{ appId: 'ai-missions', widthPx: 440 },
|
||||
{ appId: 'ai-workbench', widthPx: 440 },
|
||||
{ appId: 'news-research', widthPx: 540 },
|
||||
],
|
||||
},
|
||||
missions: [
|
||||
{
|
||||
title: 'Quellen-Recherche zu einem Thema',
|
||||
objective:
|
||||
'Lies die verlinkten Quellen, schreibe pro Quelle eine Notiz, erstelle am Ende einen Gesamt-Bericht.',
|
||||
conceptMarkdown: `# Recherche-Auftrag
|
||||
|
||||
Ersetze diesen Block mit:
|
||||
|
||||
- **Thema:** _worum geht es?_
|
||||
- **Quellen:** _Liste von URLs oder Input-Notizen_
|
||||
- **Fragestellung:** _was willst du am Ende wissen?_
|
||||
|
||||
Der Agent liest die Quellen sequentiell und schreibt pro Quelle eine Notiz (als
|
||||
Vorschlag). Am Ende erstellt er einen Gesamtbericht der die Notizen zusammenfasst
|
||||
und die Fragestellung beantwortet.`,
|
||||
cadence: { kind: 'manual' },
|
||||
startPaused: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
98
packages/shared-ai/src/agents/templates/today.ts
Normal file
98
packages/shared-ai/src/agents/templates/today.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { AI_PROPOSABLE_TOOL_NAMES } from '../../policy/proposable-tools';
|
||||
import type { AgentTemplate } from './types';
|
||||
import type { AiPolicy } from '../../policy/types';
|
||||
|
||||
/**
|
||||
* Today agent — daily poem about what happened on this calendar date in
|
||||
* history. Researches via the news-research module and saves the result
|
||||
* as a note. Designed as a lightweight "delight" agent; shows off the
|
||||
* autonomous-creative side of the system.
|
||||
*/
|
||||
|
||||
const TODAY_POLICY: AiPolicy = {
|
||||
tools: Object.fromEntries(AI_PROPOSABLE_TOOL_NAMES.map((n) => [n, 'propose'])),
|
||||
defaultsByModule: {
|
||||
notes: 'propose',
|
||||
// The agent does plenty of reads; those are auto anyway under the
|
||||
// policy helper, but explicit here to make the intent clear.
|
||||
news: 'auto',
|
||||
kontext: 'auto',
|
||||
},
|
||||
defaultForAi: 'propose',
|
||||
};
|
||||
|
||||
export const todayTemplate: AgentTemplate = {
|
||||
id: 'today',
|
||||
label: 'Today-Agent',
|
||||
tagline: 'Jeden Tag ein Gedicht über das was heute besonderes passierte',
|
||||
description: `Der Agent recherchiert täglich (morgens um 7 Uhr) was an diesem
|
||||
Kalendertag in der Geschichte Besonderes passiert ist, und destilliert das zu
|
||||
einem kleinen Gedicht — als Notiz in deinem Journal.
|
||||
|
||||
Ein "Delight-Agent" — hat keinen produktiven Zweck. Gedacht als tägliches Moment
|
||||
der Reflexion und als Beispiel dafür dass AI nicht nur effizient sein muss.
|
||||
|
||||
Was er tut:
|
||||
|
||||
1. Ermittelt das heutige Datum
|
||||
2. Recherchiert 3-5 historische Ereignisse dieses Tages (via Web-Research)
|
||||
3. Wählt ein Thema das ihn (oder dich) inspiriert
|
||||
4. Schreibt ein kurzes Gedicht (4-8 Zeilen, deutsch)
|
||||
5. Schlägt eine Journal-Notiz vor mit Titel "Heute — [Datum]"`,
|
||||
category: 'today',
|
||||
color: '#F97316',
|
||||
agent: {
|
||||
name: 'Today-Agent',
|
||||
avatar: '🌅',
|
||||
role: 'Tägliches Gedicht über historische Ereignisse dieses Tages',
|
||||
systemPrompt: `Du bist der Today-Agent. Einziger Job: jeden Morgen ein kurzes Gedicht über etwas das an diesem Tag in der Geschichte passiert ist.
|
||||
|
||||
Regeln:
|
||||
1. **Immer** zuerst per Web-Research "on this day [Datum]" oder "historische Ereignisse [Datum]" recherchieren.
|
||||
2. Wähle EIN Ereignis — nicht fünf. Lieber ein kleines poetisches Detail als eine Liste.
|
||||
3. Gedicht: 4-8 Zeilen, deutsch, **kein Reim-Zwang** (Reim nur wenn er natürlich kommt), freier Rhythmus ok.
|
||||
4. Speichere als Proposal für eine Note — Titel "Heute — [YYYY-MM-DD] — [kurzes Thema]".
|
||||
5. Kein Content-Warning nötig für historisch bekannte Themen (Kriege, Tode). Aber: behandle sie würdevoll, nicht ironisch.
|
||||
6. **Kein Meta-Kommentar im Gedicht selbst** — kein "An diesem Tag vor 50 Jahren…". Direkt ins Bild.
|
||||
|
||||
Beispiel-Qualität: lieber ein kurzes, klares Bild als eine überladene Reim-Konstruktion.`,
|
||||
memory: `# Stilvorlieben
|
||||
|
||||
(Hier kannst du dem Agent sagen welchen Ton du magst — z.B. "eher melancholisch"
|
||||
oder "mit Humor" oder "klassisch und streng".)
|
||||
`,
|
||||
policy: TODAY_POLICY,
|
||||
maxConcurrentMissions: 1,
|
||||
},
|
||||
scene: {
|
||||
name: 'Today',
|
||||
description: 'Dein tägliches Gedicht',
|
||||
openApps: [
|
||||
{ appId: 'journal', widthPx: 540 },
|
||||
{ appId: 'ai-missions', widthPx: 440 },
|
||||
{ appId: 'ai-workbench', widthPx: 440 },
|
||||
],
|
||||
},
|
||||
missions: [
|
||||
{
|
||||
title: 'Tägliches Gedicht über heute',
|
||||
objective:
|
||||
'Recherchiere was an diesem Datum in der Geschichte passiert ist, wähle ein Thema, schreibe ein kurzes deutsches Gedicht, schlage eine Journal-Notiz vor.',
|
||||
conceptMarkdown: `# Today-Poem
|
||||
|
||||
**Cadence:** jeden Morgen um 7 Uhr.
|
||||
|
||||
**Ablauf jedes Runs:**
|
||||
1. Web-Research: "on this day [Datum]" + deutschsprachige Quellen bevorzugt
|
||||
2. Pick: ein Ereignis, ein Detail, ein Bild
|
||||
3. Write: 4-8 Zeilen freies Gedicht
|
||||
4. Propose: neue Journal-Notiz mit Titel "Heute — [Datum] — [Thema]"
|
||||
|
||||
Der Agent soll **nicht** jeden Tag eine Zusammenfassung produzieren — sondern einen
|
||||
kleinen poetischen Moment. Wenn er nichts findet das ihn inspiriert, darf er das
|
||||
auch sagen ("heute fiel mir nichts ein").`,
|
||||
cadence: { kind: 'daily', atHour: 7, atMinute: 0 },
|
||||
startPaused: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
84
packages/shared-ai/src/agents/templates/types.ts
Normal file
84
packages/shared-ai/src/agents/templates/types.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* Agent-Template shape — a bundle of (agent config, optional scene
|
||||
* layout, optional starter missions) that the webapp applies as a
|
||||
* single unit when the user picks it from the template gallery.
|
||||
*
|
||||
* Templates are pure data: no runtime imports, no side effects, no
|
||||
* references to Dexie / Svelte. The webapp's `apply-template.ts`
|
||||
* orchestrator is the only code that turns a template into concrete
|
||||
* records. This keeps the templates trivial to author (drop a file
|
||||
* next to this one) and keeps shared-ai dependency-free.
|
||||
*/
|
||||
|
||||
import type { AiPolicy } from '../../policy/types';
|
||||
import type { MissionCadence, MissionInputRef } from '../../missions/types';
|
||||
|
||||
export interface AgentTemplateAgentPart {
|
||||
/** Display name — the user can rename after creation. Must be unique
|
||||
* at apply-time; the orchestrator deduplicates via `createAgent`. */
|
||||
name: string;
|
||||
/** Emoji or short string. Shown on the card + everywhere the agent
|
||||
* appears in UI. */
|
||||
avatar: string;
|
||||
/** Short user-facing description ("what this agent does for you"). */
|
||||
role: string;
|
||||
/** Optional pre-filled systemPrompt. Encrypted at rest. */
|
||||
systemPrompt?: string;
|
||||
/** Optional pre-filled memory. Encrypted at rest. */
|
||||
memory?: string;
|
||||
/** Per-tool + per-module decisions. Templates reuse the DEFAULT_AI_POLICY
|
||||
* and layer tweaks on top. Undefined → DEFAULT_AI_POLICY. */
|
||||
policy?: AiPolicy;
|
||||
/** Optional budget; undefined = no daily cap. */
|
||||
maxTokensPerDay?: number;
|
||||
/** Default 1 (serial). */
|
||||
maxConcurrentMissions?: number;
|
||||
}
|
||||
|
||||
export interface AgentTemplateSceneApp {
|
||||
readonly appId: string;
|
||||
readonly widthPx?: number;
|
||||
readonly maximized?: boolean;
|
||||
}
|
||||
|
||||
export interface AgentTemplateScenePart {
|
||||
/** Display name for the scene tab. */
|
||||
name: string;
|
||||
description?: string;
|
||||
openApps: readonly AgentTemplateSceneApp[];
|
||||
}
|
||||
|
||||
export interface AgentTemplateMissionPart {
|
||||
title: string;
|
||||
objective: string;
|
||||
conceptMarkdown: string;
|
||||
cadence: MissionCadence;
|
||||
inputs?: readonly MissionInputRef[];
|
||||
/** Whether the mission should be immediately active. Templates
|
||||
* default to `paused` so the user has to hit Play — avoids
|
||||
* surprise autonomous work on first use. */
|
||||
startPaused?: boolean;
|
||||
}
|
||||
|
||||
export interface AgentTemplate {
|
||||
/** Stable id, used for analytics + "this template was applied" detection. */
|
||||
id: string;
|
||||
/** Short display label for the gallery card. */
|
||||
label: string;
|
||||
/** One-line tagline under the label. */
|
||||
tagline: string;
|
||||
/** Longer body for the card's detail pane. Can be markdown. */
|
||||
description: string;
|
||||
/** Category / tag for grouping in the gallery. */
|
||||
category: 'research' | 'context' | 'today' | 'custom';
|
||||
/** Accent color for the card. */
|
||||
color: string;
|
||||
|
||||
agent: AgentTemplateAgentPart;
|
||||
/** Optional — when absent, no scene is created. When present, the
|
||||
* orchestrator creates a scene pre-populated with these apps. */
|
||||
scene?: AgentTemplateScenePart;
|
||||
/** Optional starter missions. Each defaults to `startPaused: true`
|
||||
* so autonomous work doesn't start without explicit user consent. */
|
||||
missions?: readonly AgentTemplateMissionPart[];
|
||||
}
|
||||
|
|
@ -75,5 +75,13 @@ export {
|
|||
type PolicyDecision,
|
||||
} from './policy';
|
||||
|
||||
export type { Agent, AgentState } from './agents';
|
||||
export { DEFAULT_AGENT_ID, DEFAULT_AGENT_NAME } from './agents';
|
||||
export type {
|
||||
Agent,
|
||||
AgentState,
|
||||
AgentTemplate,
|
||||
AgentTemplateAgentPart,
|
||||
AgentTemplateScenePart,
|
||||
AgentTemplateSceneApp,
|
||||
AgentTemplateMissionPart,
|
||||
} from './agents';
|
||||
export { DEFAULT_AGENT_ID, DEFAULT_AGENT_NAME, ALL_TEMPLATES, getTemplateById } from './agents';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue