@@ -210,6 +228,28 @@ export function AgentDebugPanel() {
);
}
+function formatDebugMessage(message?: string): string {
+ if (!message) return "Debug Event";
+ return message
+ .split("-")
+ .filter(Boolean)
+ .map((part) => part[0]!.toUpperCase() + part.slice(1))
+ .join(" ");
+}
+
+function formatDebugArgs(args: unknown[]): string {
+ return args
+ .map((arg) => {
+ if (typeof arg === "string") return arg;
+ try {
+ return JSON.stringify(arg, null, 2);
+ } catch {
+ return String(arg);
+ }
+ })
+ .join("\n\n");
+}
+
function formatPhase(phase: string): string {
switch (phase) {
case "pre_generation":
diff --git a/src/features/characters/components/CharacterAvatarImage.tsx b/src/features/characters/components/CharacterAvatarImage.tsx
new file mode 100644
index 0000000..5cc593e
--- /dev/null
+++ b/src/features/characters/components/CharacterAvatarImage.tsx
@@ -0,0 +1,48 @@
+import type { AvatarCrop } from "../../../shared/lib/utils";
+import { cn, getAvatarCropStyle, parseAvatarCropJson } from "../../../shared/lib/utils";
+
+function isAvatarCrop(value: unknown): value is AvatarCrop {
+ return (
+ !!value &&
+ typeof value === "object" &&
+ Number.isFinite((value as AvatarCrop).srcX) &&
+ Number.isFinite((value as AvatarCrop).srcY) &&
+ Number.isFinite((value as AvatarCrop).srcWidth) &&
+ Number.isFinite((value as AvatarCrop).srcHeight) &&
+ (value as AvatarCrop).srcWidth > 0 &&
+ (value as AvatarCrop).srcHeight > 0 &&
+ (value as AvatarCrop).srcX >= 0 &&
+ (value as AvatarCrop).srcY >= 0 &&
+ (value as AvatarCrop).srcX + (value as AvatarCrop).srcWidth <= 1.001 &&
+ (value as AvatarCrop).srcY + (value as AvatarCrop).srcHeight <= 1.001
+ );
+}
+
+function resolveAvatarCrop(crop: unknown): AvatarCrop | null {
+ if (!crop) return null;
+ if (typeof crop === "string") return parseAvatarCropJson(crop);
+ return isAvatarCrop(crop) ? crop : null;
+}
+
+export function CharacterAvatarImage({
+ src,
+ alt,
+ crop,
+ className,
+}: {
+ src: string;
+ alt: string;
+ crop?: unknown;
+ className?: string;
+}) {
+ return (
+

+ );
+}
diff --git a/src/features/characters/components/CharacterLibraryView.tsx b/src/features/characters/components/CharacterLibraryView.tsx
index bf9d721..eb4b2fe 100644
--- a/src/features/characters/components/CharacterLibraryView.tsx
+++ b/src/features/characters/components/CharacterLibraryView.tsx
@@ -3,8 +3,9 @@ import { ArrowLeft, ArrowUpDown, Download, MessageCircle, Pencil, Plus, Search,
import { useCharacters } from "../hooks/use-characters";
import { useStartChatFromCharacter } from "../hooks/use-start-chat-from-character";
import { getCharacterTitle } from "../../../shared/lib/character-display";
-import { cn, getAvatarCropStyle, type AvatarCrop } from "../../../shared/lib/utils";
+import { cn } from "../../../shared/lib/utils";
import { useUIStore } from "../../../shared/stores/ui.store";
+import { CharacterAvatarImage } from "./CharacterAvatarImage";
type CharacterData = Record
& {
name?: string;
@@ -106,14 +107,10 @@ function CharacterLibraryDetailCard({
{character.avatarPath ? (
-

) : (
@@ -428,15 +425,11 @@ export function CharacterLibraryView() {
>
{char.avatarPath ? (
-

) : (
diff --git a/src/features/characters/components/CharactersPanel.tsx b/src/features/characters/components/CharactersPanel.tsx
index cf5d5d4..692944f 100644
--- a/src/features/characters/components/CharactersPanel.tsx
+++ b/src/features/characters/components/CharactersPanel.tsx
@@ -48,8 +48,9 @@ import {
} from "lucide-react";
import { getCharacterTitle } from "../../../shared/lib/character-display";
import { useUIStore } from "../../../shared/stores/ui.store";
-import { cn, getAvatarCropStyle, type AvatarCrop } from "../../../shared/lib/utils";
+import { cn } from "../../../shared/lib/utils";
import { ExportFormatDialog, type ExportFormatChoice } from "../../../shared/components/ui/ExportFormatDialog";
+import { CharacterAvatarImage } from "./CharacterAvatarImage";
type CharacterRow = {
id: string;
@@ -946,12 +947,7 @@ export function CharactersPanel() {
>
{member.avatarPath ? (
-

+
) : (
)}
@@ -1120,14 +1116,10 @@ export function CharactersPanel() {
{avatarUrl ? (
-
) : (
diff --git a/src/features/generation/hooks/use-generate.ts b/src/features/generation/hooks/use-generate.ts
index 0e374d5..c19febe 100644
--- a/src/features/generation/hooks/use-generate.ts
+++ b/src/features/generation/hooks/use-generate.ts
@@ -36,6 +36,7 @@ import {
type GenerationReplay,
} from "../../../engine/generation/generation-replay";
import { readNonNegativeInteger } from "../../../engine/generation/runtime-records";
+import type { AgentDebugEntry } from "../../../engine/contracts/types/agent";
export type GenerateArgs = GenerationReplayInput & {
chatId: string;
@@ -846,7 +847,12 @@ export function useGenerate() {
(streamArgs, signal) =>
startGeneration(
{ storage: storageApi, llm: llmApi, integrations: integrationGateway },
- streamArgs,
+ {
+ ...streamArgs,
+ debugMode: useUIStore.getState().debugMode,
+ debugSink: (entry: Omit
& { timestamp?: number }) =>
+ useAgentStore.getState().addDebugEntry(entry),
+ },
signal,
) as AsyncGenerator,
{
diff --git a/src/features/settings/components/SettingsPanel.tsx b/src/features/settings/components/SettingsPanel.tsx
index 7b1ad0e..e062dfa 100644
--- a/src/features/settings/components/SettingsPanel.tsx
+++ b/src/features/settings/components/SettingsPanel.tsx
@@ -3410,7 +3410,7 @@ function AdvancedSettings() {
label="Debug mode"
checked={debugMode}
onChange={setDebugMode}
- help="Logs the prompt and response payloads sent to the local Tauri console for debugging."
+ help="Shows the in-app agent debug panel with prompt, response, tool, and result details for troubleshooting."
/>
{/* ── Profile Export ── */}
diff --git a/src/shared/stores/agent.store.ts b/src/shared/stores/agent.store.ts
index 70bee10..20fbe16 100644
--- a/src/shared/stores/agent.store.ts
+++ b/src/shared/stores/agent.store.ts
@@ -2,7 +2,7 @@
// Zustand Store: Agent Slice
// ──────────────────────────────────────────────
import { create } from "zustand";
-import type { AgentResult, CharacterCardFieldUpdate } from "../../engine/contracts/types/agent";
+import type { AgentDebugEntry, AgentResult, CharacterCardFieldUpdate } from "../../engine/contracts/types/agent";
import type { AgentFailure } from "../lib/agent-failures";
/**
@@ -23,29 +23,6 @@ export interface PendingCardUpdate {
timestamp: number;
}
-export interface AgentDebugEntry {
- phase: string;
- agents?: Array<{
- type: string;
- name: string;
- model: string;
- maxTokens: number;
- }>;
- results?: AgentResult[];
- toolCall?: {
- name: string;
- arguments: string;
- allowed: boolean;
- };
- toolResult?: {
- name: string;
- result: string;
- success: boolean;
- };
- batchMaxTokens?: number;
- timestamp: number;
-}
-
interface AgentState {
activeAgents: string[];
lastResults: Map;