mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:41:09 +02:00
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>
This commit is contained in:
parent
e7398b2dee
commit
6545498dc2
4 changed files with 55 additions and 2 deletions
|
|
@ -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<Agent> {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<number | null>(null);
|
||||
let editScopeTagIds = $state<string[]>([]);
|
||||
let editDefaultWritingStyleId = $state<string | null>(null);
|
||||
let lastSyncedId = $state<string | null>(null);
|
||||
let saveError = $state<string | null>(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 @@
|
|||
</label>
|
||||
</section>
|
||||
|
||||
<section class="block">
|
||||
<h3>Writing</h3>
|
||||
<p class="hint">
|
||||
Default-Schreibstil, den der Agent beim Anlegen eines Drafts nutzt, wenn keiner explizit
|
||||
übergeben wird.
|
||||
</p>
|
||||
<StylePicker
|
||||
value={editDefaultWritingStyleId}
|
||||
onchange={(next) => (editDefaultWritingStyleId = next)}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<div class="save-row">
|
||||
{#if saveError}
|
||||
<span class="form-error">{saveError}</span>
|
||||
|
|
|
|||
|
|
@ -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<string | null> {
|
||||
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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue