diff --git a/apps/mana/apps/web/src/lib/data/ai/agents/store.ts b/apps/mana/apps/web/src/lib/data/ai/agents/store.ts index 15393dd99..ef8c8a840 100644 --- a/apps/mana/apps/web/src/lib/data/ai/agents/store.ts +++ b/apps/mana/apps/web/src/lib/data/ai/agents/store.ts @@ -40,6 +40,8 @@ export interface CreateAgentInput { policy?: AiPolicy; maxTokensPerDay?: number; maxConcurrentMissions?: number; + scopeTagIds?: string[]; + defaultWritingStyleId?: string; state?: AgentState; } @@ -66,6 +68,8 @@ export async function createAgent(input: CreateAgentInput): Promise { avatar: input.avatar, systemPrompt: input.systemPrompt, memory: input.memory, + scopeTagIds: input.scopeTagIds, + defaultWritingStyleId: input.defaultWritingStyleId, policy: deepClone(input.policy ?? DEFAULT_AI_POLICY), maxTokensPerDay: input.maxTokensPerDay, maxConcurrentMissions: input.maxConcurrentMissions ?? 1, @@ -130,6 +134,7 @@ export interface AgentPatch { maxTokensPerDay?: number; maxConcurrentMissions?: number; scopeTagIds?: string[]; + defaultWritingStyleId?: string; state?: AgentState; } diff --git a/apps/mana/apps/web/src/lib/modules/ai-agents/ListView.svelte b/apps/mana/apps/web/src/lib/modules/ai-agents/ListView.svelte index 3ecacef41..e8433d8ab 100644 --- a/apps/mana/apps/web/src/lib/modules/ai-agents/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/ai-agents/ListView.svelte @@ -37,6 +37,7 @@ import type { AiPolicy, PolicyDecision } from '@mana/shared-ai'; import { TagSelector } from '@mana/shared-ui'; import { useAllTags } from '@mana/shared-stores'; + import StylePicker from '$lib/modules/writing/components/StylePicker.svelte'; const agents = $derived(useAgents()); const allTags = $derived(useAllTags()); @@ -89,6 +90,7 @@ let editMaxConcurrent = $state(1); let editMaxTokensPerDay = $state(null); let editScopeTagIds = $state([]); + let editDefaultWritingStyleId = $state(null); let lastSyncedId = $state(null); let saveError = $state(null); let saving = $state(false); @@ -103,6 +105,7 @@ editMaxConcurrent = selected.maxConcurrentMissions; editMaxTokensPerDay = selected.maxTokensPerDay ?? null; editScopeTagIds = [...(selected.scopeTagIds ?? [])]; + editDefaultWritingStyleId = selected.defaultWritingStyleId ?? null; lastSyncedId = selected.id; saveError = null; } @@ -121,6 +124,7 @@ maxConcurrentMissions: editMaxConcurrent, maxTokensPerDay: editMaxTokensPerDay ?? undefined, scopeTagIds: editScopeTagIds.length > 0 ? editScopeTagIds : undefined, + defaultWritingStyleId: editDefaultWritingStyleId ?? undefined, }); } catch (err) { if (err instanceof DuplicateAgentNameError) { @@ -455,6 +459,18 @@ +
+

Writing

+

+ Default-Schreibstil, den der Agent beim Anlegen eines Drafts nutzt, wenn keiner explizit + übergeben wird. +

+ (editDefaultWritingStyleId = next)} + /> +
+
{#if saveError} {saveError} diff --git a/apps/mana/apps/web/src/lib/modules/writing/tools.ts b/apps/mana/apps/web/src/lib/modules/writing/tools.ts index 83ae0ccda..952fe1609 100644 --- a/apps/mana/apps/web/src/lib/modules/writing/tools.ts +++ b/apps/mana/apps/web/src/lib/modules/writing/tools.ts @@ -27,6 +27,8 @@ import { decryptRecords, VaultLockedError } from '$lib/data/crypto'; import { toDraft, toDraftVersion } from './queries'; import { STYLE_PRESETS } from './presets/styles'; import { writingStyleTable } from './collections'; +import { getCurrentActor, isAiActor } from '$lib/data/events'; +import { getAgent } from '$lib/data/ai/agents/store'; import type { LocalDraft, LocalDraftVersion, @@ -35,6 +37,23 @@ import type { DraftStatus, } from './types'; +/** + * When an AI actor is running a writing tool without an explicit + * styleId, fall back to the agent's `defaultWritingStyleId`. User- + * invoked calls skip this — a human passing no styleId means "ad-hoc, + * no style", not "use my default". + */ +async function resolveAgentDefaultStyle(): Promise { + const actor = getCurrentActor(); + if (!isAiActor(actor)) return null; + try { + const agent = await getAgent(actor.principalId); + return agent?.defaultWritingStyleId ?? null; + } catch { + return null; + } +} + const KINDS: DraftKind[] = [ 'blog', 'essay', @@ -266,11 +285,16 @@ export const writingTools: ModuleTool[] = [ const targetWordsRaw = typeof params.targetWords === 'number' ? Math.round(params.targetWords) : null; + const explicitStyleId = + typeof params.styleId === 'string' && params.styleId.length > 0 ? params.styleId : null; + // Persona-Linkage: AI actors inherit the agent's default style + // when they don't pass one; user-invoked calls do not — a human + // omitting styleId means "ad-hoc, no style". + const styleId = explicitStyleId ?? (await resolveAgentDefaultStyle()); const { draft } = await draftsStore.createDraft({ kind, title, - styleId: - typeof params.styleId === 'string' && params.styleId.length > 0 ? params.styleId : null, + styleId, briefing: { topic, audience: typeof params.audience === 'string' ? params.audience : null, diff --git a/packages/shared-ai/src/agents/types.ts b/packages/shared-ai/src/agents/types.ts index 5d5c4afe3..50ac3b1f6 100644 --- a/packages/shared-ai/src/agents/types.ts +++ b/packages/shared-ai/src/agents/types.ts @@ -54,6 +54,14 @@ export interface Agent { * (tag IDs are not sensitive). */ scopeTagIds?: string[]; + /** Fallback writing-style id for the Writing module (M8 follow-up). + * When this agent runs create_draft / generate_draft_content without + * an explicit styleId, the tool resolves to this value so e.g. a + * "Marketing-Agent" always drafts in the Corporate-Tone style. + * Format: `preset:` for a built-in preset or the uuid of a + * custom LocalWritingStyle row. Plaintext FK. */ + defaultWritingStyleId?: string; + /** Per-tool allowlist/propose/deny. Replaces the user-level AiPolicy * in Phase 4; pre-populated with the default policy at create time * so the runner can start reading it even while still consulting