chore: CLI v2.1.75–v2.1.80 audit and support (#223–#232) (#233)
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m8s
CI / Build Linux (push) Has been cancelled
CI / Build Windows (cross-compile) (push) Has been cancelled
CI / Lint & Test (push) Has been cancelled

## Summary

This PR implements all tickets filed from the CLI v2.1.74 → v2.1.80 changelog audit (issues #223–#232).

### Changes by Issue

- **#223** — `feat: handle Elicitation and ElicitationResult hook events`
  New `ElicitationModal.svelte` component, Rust parsing for `[Elicitation Hook]` and `[ElicitationResult Hook]`, new store methods, and TypeScript event types.

- **#224** — `feat: handle StopFailure hook event for API error turns`
  Rust parsing for `[StopFailure Hook]`; frontend shows error toast + error character state.

- **#225** — `feat: handle PostCompact hook event`
  Rust parsing for `[PostCompact Hook]`; frontend shows info toast + success character state.

- **#226** — `feat: expose --name CLI flag as session name at startup`
  Added `session_name` field to `ClaudeStartOptions`; `StatusBar.doConnect()` passes the conversation name.

- **#227** — `fix: tighten startup watchdog and correct misleading comment`
  Startup watchdog tightened from 60 s → 30 s; corrected a comment that said "5 minutes" whilst the code used 60 seconds.

- **#228** — `fix: document cost estimation review and update default model fallback`
  Default model fallback updated from `claude-sonnet-4-5-20250929` → `claude-sonnet-4-6`; added doc comment explaining why char-based estimation is unaffected by v2.1.75 token overcounting fix.

- **#229** — `chore: update supported CLI version constant to 2.1.80`
  `SUPPORTED_CLI_VERSION` bumped in `CliVersion.svelte`.

- **#230** — `feat: surface memory file last-modified timestamps in MemoryBrowserPanel`
  Backend populates `last_modified` Unix timestamp; frontend formats and displays it per file.

- **#231** — `feat: update max_output_tokens upper bound and helper text for 128k`
  Input max raised to 128 000; placeholder and helper text updated to reflect model-dependent defaults and 128 k ceiling for Opus/Sonnet 4.6.

- **#232** — `fix: document non-streaming fallback compatibility with mid-session watchdog`
  Added doc comment above `STUCK_TIMEOUT` explaining the 5-minute watchdog is intentionally larger than the CLI's 2-minute non-streaming API fallback.

---

 This PR was created with help from Hikari~ 🌸

Reviewed-on: #233
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #233.
This commit is contained in:
2026-03-23 14:28:08 -07:00
committed by Naomi Carrigan
parent 8220ab6b85
commit 4134e11c88
21 changed files with 1075 additions and 22 deletions
+2
View File
@@ -71,6 +71,7 @@ async function changeDirectory(path: string): Promise<void> {
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
},
});
@@ -152,6 +153,7 @@ async function startNewConversation(): Promise<void> {
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
},
});
+1 -1
View File
@@ -2,7 +2,7 @@
import { invoke } from "@tauri-apps/api/core";
import { onMount } from "svelte";
const SUPPORTED_CLI_VERSION = "2.1.74";
const SUPPORTED_CLI_VERSION = "2.1.80";
let installedVersion = $state("Loading...");
let latestNpmVersion = $state<string | null>(null);
+5 -3
View File
@@ -610,13 +610,15 @@
id="max-output-tokens"
type="number"
min="1"
placeholder="Default (32000)"
max="128000"
placeholder="Default (model-dependent)"
bind:value={config.max_output_tokens}
class="w-full px-3 py-2 text-sm bg-[var(--bg-primary)] border border-[var(--border-color)] rounded text-[var(--text-primary)] placeholder-[var(--text-tertiary)] focus:outline-none focus:border-[var(--accent-primary)]"
/>
<p class="text-xs text-[var(--text-tertiary)] mt-1">
Sets <code class="font-mono">CLAUDE_CODE_MAX_OUTPUT_TOKENS</code> — increase if responses are
being cut off mid-reply
Sets <code class="font-mono">CLAUDE_CODE_MAX_OUTPUT_TOKENS</code>. Maximum: 128k tokens
for Opus 4.6 and Sonnet 4.6 (64k default for Opus 4.6, 32k for other models). Increase if
responses are being cut off.
</p>
</div>
+188
View File
@@ -0,0 +1,188 @@
<script lang="ts">
import { invoke } from "@tauri-apps/api/core";
import { get } from "svelte/store";
import { claudeStore, hasElicitationPending } from "$lib/stores/claude";
import { characterState } from "$lib/stores/character";
import type { ElicitationEvent } from "$lib/types/messages";
import { updateDiscordRpc, setSkipNextGreeting } from "$lib/tauri";
import { conversationsStore } from "$lib/stores/conversations";
import { configStore } from "$lib/stores/config";
let isVisible = $state(false);
let elicitation: ElicitationEvent | null = $state(null);
let response = $state("");
let grantedToolsList: string[] = $state([]);
let workingDirectory = $state("");
hasElicitationPending.subscribe((pending) => {
isVisible = pending;
if (!pending) {
response = "";
}
});
claudeStore.pendingElicitation.subscribe((e) => {
elicitation = e;
if (e) {
characterState.setState("permission");
}
});
claudeStore.grantedTools.subscribe((tools) => {
grantedToolsList = Array.from(tools);
});
claudeStore.currentWorkingDirectory.subscribe((dir) => {
workingDirectory = dir;
});
async function handleSubmitAndReconnect() {
if (!elicitation || !response.trim()) return;
const conversationId = get(claudeStore.activeConversationId);
if (!conversationId) return;
const responseText = response.trim();
const elicitationMessage = elicitation.message;
const conversationHistory = claudeStore.getConversationHistory();
claudeStore.addLine("system", `MCP response submitted. Reconnecting with context...`);
claudeStore.clearElicitation();
try {
setSkipNextGreeting(true);
await invoke("stop_claude", { conversationId });
await new Promise((resolve) => setTimeout(resolve, 500));
const config = configStore.getConfig();
await invoke("start_claude", {
conversationId,
options: {
working_dir: workingDirectory || "/home/naomi",
model: config.model || null,
api_key: config.api_key || null,
custom_instructions: config.custom_instructions || null,
mcp_servers_json: config.mcp_servers_json || null,
allowed_tools: grantedToolsList,
use_worktree: config.use_worktree ?? false,
disable_1m_context: config.disable_1m_context ?? false,
include_git_instructions: config.include_git_instructions ?? true,
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
},
});
const activeConversation = get(conversationsStore.activeConversation);
if (activeConversation) {
await updateDiscordRpc(
activeConversation.name,
config.model || "claude",
activeConversation.startedAt
);
}
await new Promise((resolve) => setTimeout(resolve, 1000));
if (conversationHistory) {
const contextMessage = `[CONTEXT RESTORATION]
I just responded to an MCP server elicitation request. Here's our conversation so far:
${conversationHistory}
The MCP server asked: "${elicitationMessage}"
My response: "${responseText}"
Please continue where we left off, taking my response into account.`;
await invoke("send_prompt", {
conversationId,
message: contextMessage,
});
}
characterState.setTemporaryState("success", 2000);
} catch (error) {
console.error("Failed to reconnect:", error);
claudeStore.addLine("error", `Reconnect failed: ${error}`);
characterState.setTemporaryState("error", 3000);
}
}
function handleDismiss() {
claudeStore.clearElicitation();
claudeStore.addLine("system", "MCP elicitation dismissed");
characterState.setTemporaryState("idle", 1000);
}
function handleKeydown(event: KeyboardEvent) {
if (!isVisible || !elicitation) return;
if (event.key === "Escape") {
event.preventDefault();
handleDismiss();
}
}
function canSubmit(): boolean {
return response.trim().length > 0;
}
</script>
<svelte:window onkeydown={handleKeydown} />
{#if isVisible && elicitation}
<div
class="elicitation-overlay fixed inset-0 bg-black/70 flex items-center justify-center z-50 backdrop-blur-sm"
>
<div
class="elicitation-modal bg-[var(--bg-primary)] border border-[var(--border-color)] rounded-xl p-6 max-w-md w-full mx-4 shadow-2xl"
>
<div class="flex items-center gap-3 mb-4">
<div class="w-10 h-10 rounded-full bg-blue-500/20 flex items-center justify-center">
<span class="text-xl">💬</span>
</div>
<div>
<h2 class="text-lg font-semibold text-[var(--text-primary)]">MCP Server Request</h2>
{#if elicitation.server_name}
<p class="text-sm text-[var(--text-secondary)]">from: {elicitation.server_name}</p>
{:else}
<p class="text-sm text-[var(--text-secondary)]">Input required from MCP server</p>
{/if}
</div>
</div>
<div class="mb-4">
<p class="text-[var(--text-primary)]">{elicitation.message}</p>
</div>
<div class="mb-4">
<textarea
bind:value={response}
placeholder="Type your response here..."
class="w-full px-4 py-3 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-lg text-[var(--text-primary)] placeholder-[var(--text-secondary)] resize-none focus:outline-none focus:border-[var(--accent-primary)]"
rows="4"
></textarea>
</div>
<div class="flex gap-3">
<button
onclick={handleDismiss}
class="flex-1 px-4 py-2 bg-gray-500/20 hover:bg-gray-500/30 text-[var(--text-secondary)] rounded-lg transition-colors font-medium"
>
Dismiss
</button>
<button
onclick={handleSubmitAndReconnect}
disabled={!canSubmit()}
class="flex-1 px-4 py-2 bg-blue-500/20 hover:bg-blue-500/30 text-blue-400 rounded-lg transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed"
>
Submit & Reconnect
</button>
</div>
</div>
</div>
{/if}
+1
View File
@@ -406,6 +406,7 @@ User: ${formattedMessage}`;
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
},
});
+34 -2
View File
@@ -14,6 +14,7 @@
interface MemoryFileInfo {
path: string;
heading: string | null;
last_modified?: string; // Unix timestamp in seconds as a string, optional for backwards compat
}
interface MemoryFilesResponse {
@@ -65,6 +66,16 @@
return file.heading ?? getFileName(file.path);
}
function formatLastModified(ts: string | undefined): string {
if (!ts) return "";
const date = new Date(Number(ts) * 1000);
return date.toLocaleDateString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
});
}
async function sendMemoryCommand() {
const conversationId = get(claudeStore.activeConversationId);
if (!conversationId) return;
@@ -205,7 +216,12 @@
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
/>
</svg>
<span class="file-name">{getDisplayName(file)}</span>
<div class="file-info">
<span class="file-name">{getDisplayName(file)}</span>
{#if file.last_modified}
<span class="file-date">{formatLastModified(file.last_modified)}</span>
{/if}
</div>
</button>
{/each}
</div>
@@ -416,7 +432,7 @@
.file-item {
display: flex;
align-items: center;
align-items: flex-start;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: var(--bg-secondary);
@@ -445,6 +461,13 @@
flex-shrink: 0;
}
.file-info {
display: flex;
flex-direction: column;
gap: 0.125rem;
overflow: hidden;
}
.file-name {
font-size: 0.875rem;
font-weight: 500;
@@ -453,6 +476,15 @@
white-space: nowrap;
}
.file-date {
font-size: 0.75rem;
color: var(--text-secondary);
}
.file-item.active .file-date {
color: rgba(255, 255, 255, 0.75);
}
.file-viewer {
display: flex;
flex-direction: column;
@@ -93,6 +93,7 @@
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
},
});
+3
View File
@@ -160,6 +160,7 @@
if (!conversationId) {
throw new Error("No active conversation");
}
const activeConversationForName = get(conversationsStore.activeConversation);
await invoke("start_claude", {
conversationId,
options: {
@@ -177,6 +178,7 @@
enable_claudeai_mcp_servers: currentConfig.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: currentConfig.auto_memory_directory || null,
model_overrides: currentConfig.model_overrides || null,
session_name: activeConversationForName?.name || null,
},
});
@@ -338,6 +340,7 @@
enable_claudeai_mcp_servers: currentConfig.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: currentConfig.auto_memory_directory || null,
model_overrides: currentConfig.model_overrides || null,
session_name: null,
},
});
+1
View File
@@ -222,6 +222,7 @@
enable_claudeai_mcp_servers: cfg.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: cfg.auto_memory_directory || null,
model_overrides: cfg.model_overrides || null,
session_name: null,
},
});
} catch (error) {
@@ -112,6 +112,7 @@
enable_claudeai_mcp_servers: config.enable_claudeai_mcp_servers ?? true,
auto_memory_directory: config.auto_memory_directory || null,
model_overrides: config.model_overrides || null,
session_name: null,
},
});
+11
View File
@@ -22,6 +22,7 @@ export const claudeStore = {
terminalLines: conversationsStore.terminalLines,
pendingPermission: conversationsStore.pendingPermission,
pendingQuestion: conversationsStore.pendingQuestion,
pendingElicitation: conversationsStore.pendingElicitation,
isProcessing: conversationsStore.isProcessing,
grantedTools: conversationsStore.grantedTools,
pendingRetryMessage: conversationsStore.pendingRetryMessage,
@@ -57,6 +58,10 @@ export const claudeStore = {
clearQuestion: conversationsStore.clearQuestion,
requestQuestionForConversation: conversationsStore.requestQuestionForConversation,
clearQuestionForConversation: conversationsStore.clearQuestionForConversation,
requestElicitation: conversationsStore.requestElicitation,
clearElicitation: conversationsStore.clearElicitation,
requestElicitationForConversation: conversationsStore.requestElicitationForConversation,
clearElicitationForConversation: conversationsStore.clearElicitationForConversation,
grantTool: conversationsStore.grantTool,
revokeAllTools: conversationsStore.revokeAllTools,
isToolGranted: conversationsStore.isToolGranted,
@@ -126,6 +131,12 @@ export const hasQuestionPending = derived(
($conversation) => $conversation?.pendingQuestion !== null
);
export const hasElicitationPending = derived(
claudeStore.activeConversation,
($conversation) =>
$conversation?.pendingElicitation !== null && $conversation?.pendingElicitation !== undefined
);
// Derived store to check if Claude is currently processing (can be interrupted)
export const isClaudeProcessing = derived(
[claudeStore.connectionStatus, characterState],
+54
View File
@@ -2,6 +2,7 @@ import { writable, derived, get } from "svelte/store";
import type {
TerminalLine,
ConnectionStatus,
ElicitationEvent,
PermissionRequest,
UserQuestionEvent,
Attachment,
@@ -32,6 +33,7 @@ export interface Conversation {
grantedTools: Set<string>;
pendingPermissions: PermissionRequest[];
pendingQuestion: UserQuestionEvent | null;
pendingElicitation: ElicitationEvent | null;
scrollPosition: number;
createdAt: Date;
lastActivityAt: Date;
@@ -157,6 +159,7 @@ function createConversationsStore() {
grantedTools: new Set(),
pendingPermissions: [],
pendingQuestion: null,
pendingElicitation: null,
scrollPosition: -1, // -1 means "scroll to bottom" (auto-scroll)
createdAt: new Date(),
lastActivityAt: new Date(),
@@ -221,6 +224,10 @@ function createConversationsStore() {
($conv) => $conv?.pendingPermissions || []
);
const pendingQuestion = derived(activeConversation, ($conv) => $conv?.pendingQuestion || null);
const pendingElicitation = derived(
activeConversation,
($conv) => $conv?.pendingElicitation ?? null
);
const scrollPosition = derived(activeConversation, ($conv) => $conv?.scrollPosition ?? -1);
const attachments = derived(activeConversation, ($conv) => $conv?.attachments || []);
const worktreeInfo = derived(activeConversation, ($conv) => $conv?.worktreeInfo ?? null);
@@ -234,6 +241,7 @@ function createConversationsStore() {
pendingPermission: { subscribe: pendingPermission.subscribe },
pendingPermissions: { subscribe: pendingPermissions.subscribe },
pendingQuestion: { subscribe: pendingQuestion.subscribe },
pendingElicitation: { subscribe: pendingElicitation.subscribe },
isProcessing: { subscribe: isProcessing.subscribe },
grantedTools: { subscribe: grantedTools.subscribe },
pendingRetryMessage: { subscribe: pendingRetryMessage.subscribe },
@@ -399,6 +407,52 @@ function createConversationsStore() {
return convs;
});
},
requestElicitation: (elicitation: ElicitationEvent) => {
const activeId = get(activeConversationId);
if (!activeId) return;
conversations.update((convs) => {
const conv = convs.get(activeId);
if (conv) {
conv.pendingElicitation = elicitation;
conv.lastActivityAt = new Date();
}
return convs;
});
},
clearElicitation: () => {
const activeId = get(activeConversationId);
if (!activeId) return;
conversations.update((convs) => {
const conv = convs.get(activeId);
if (conv) {
conv.pendingElicitation = null;
conv.lastActivityAt = new Date();
}
return convs;
});
},
requestElicitationForConversation: (conversationId: string, elicitation: ElicitationEvent) => {
conversations.update((convs) => {
const conv = convs.get(conversationId);
if (conv) {
conv.pendingElicitation = elicitation;
conv.lastActivityAt = new Date();
}
return convs;
});
},
clearElicitationForConversation: (conversationId: string) => {
conversations.update((convs) => {
const conv = convs.get(conversationId);
if (conv) {
conv.pendingElicitation = null;
conv.lastActivityAt = new Date();
}
return convs;
});
},
setPendingRetryMessage: (message: string | null) => pendingRetryMessage.set(message),
// Conversation management
+27
View File
@@ -187,6 +187,33 @@ describe("toastStore", () => {
});
});
describe("addError", () => {
it("adds an error toast with the warning icon", () => {
toastStore.addError("Something went wrong");
const toasts = get(toastStore);
expect(toasts).toHaveLength(1);
const toast = toasts[0];
expect(toast.kind).toBe("info");
if (toast.kind === "info") {
expect(toast.message).toBe("Something went wrong");
expect(toast.icon).toBe("⚠️");
expect(typeof toast.id).toBe("string");
expect(toast.id.length).toBeGreaterThan(0);
}
});
it("auto-dismisses after 6000ms", () => {
toastStore.addError("Rate limit reached");
expect(get(toastStore)).toHaveLength(1);
vi.advanceTimersByTime(5999);
expect(get(toastStore)).toHaveLength(1);
vi.advanceTimersByTime(1);
expect(get(toastStore)).toHaveLength(0);
});
});
describe("addUpdate", () => {
it("adds a persistent update toast with the correct fields", () => {
toastStore.addUpdate("2.0.0", "1.9.0", "https://example.com/release");
+8 -1
View File
@@ -68,6 +68,13 @@ function createToastStore() {
setTimeout(() => remove(id), 4000);
}
function addError(message: string) {
const id = crypto.randomUUID();
const toast: InfoToast = { id, kind: "info", message, icon: "⚠️" };
update((toasts) => [...toasts, toast]);
setTimeout(() => remove(id), 6000);
}
function addAchievement(achievement: AchievementUnlockedEvent["achievement"]) {
const id = crypto.randomUUID();
const toast: AchievementToast = { id, kind: "achievement", achievement };
@@ -82,7 +89,7 @@ function createToastStore() {
// Update toasts are persistent — no auto-dismiss
}
return { subscribe, addInfo, addAchievement, addUpdate, remove };
return { subscribe, addInfo, addError, addAchievement, addUpdate, remove };
}
export const toastStore = createToastStore();
+57 -2
View File
@@ -8,7 +8,10 @@ import { initStatsListener, resetSessionStats } from "$lib/stores/stats";
import { initAchievementsListener } from "$lib/stores/achievements";
import type {
ConnectionStatus,
ElicitationEvent,
PermissionPromptEvent,
PostCompactEvent,
StopFailureEvent,
UserQuestionEvent,
} from "$lib/types/messages";
import type { CharacterState } from "$lib/types/states";
@@ -406,7 +409,8 @@ export async function initializeTauriListeners() {
| "rate-limit"
| "compact-prompt"
| "worktree"
| "config-change",
| "config-change"
| "elicitation",
content,
tool_name || undefined,
costData,
@@ -425,7 +429,8 @@ export async function initializeTauriListeners() {
| "rate-limit"
| "compact-prompt"
| "worktree"
| "config-change",
| "config-change"
| "elicitation",
content,
tool_name || undefined,
costData,
@@ -611,6 +616,56 @@ export async function initializeTauriListeners() {
}
});
unlisteners.push(questionUnlisten);
const elicitationUnlisten = await listen<ElicitationEvent>("claude:elicitation", (event) => {
const elicitationEvent = event.payload;
if (elicitationEvent.conversation_id) {
claudeStore.requestElicitationForConversation(
elicitationEvent.conversation_id,
elicitationEvent
);
} else {
claudeStore.requestElicitation(elicitationEvent);
}
});
unlisteners.push(elicitationUnlisten);
const elicitationResultUnlisten = await listen<{ conversation_id?: string }>(
"claude:elicitation-result",
(event) => {
const { conversation_id } = event.payload;
if (conversation_id) {
claudeStore.clearElicitationForConversation(conversation_id);
} else {
claudeStore.clearElicitation();
}
}
);
unlisteners.push(elicitationResultUnlisten);
const stopFailureUnlisten = await listen<StopFailureEvent>("claude:stop-failure", (event) => {
const { stop_reason, error_type } = event.payload;
characterState.setTemporaryState("error", 3000);
let message: string;
if (stop_reason === "rate_limit") {
message = "Rate limit reached";
} else if (stop_reason === "auth_failure" || stop_reason === "authentication") {
message = "Authentication failed";
} else {
message = `API error: ${stop_reason ?? error_type ?? "unknown"}`;
}
toastStore.addError(message);
});
unlisteners.push(stopFailureUnlisten);
const postCompactUnlisten = await listen<PostCompactEvent>("claude:post-compact", () => {
toastStore.addInfo("Context compacted", "🗜️");
characterState.setTemporaryState("success", 2000);
});
unlisteners.push(postCompactUnlisten);
}
export function cleanupTauriListeners() {
+26 -1
View File
@@ -10,7 +10,8 @@ export interface TerminalLine {
| "rate-limit"
| "compact-prompt"
| "worktree"
| "config-change";
| "config-change"
| "elicitation";
content: string;
timestamp: Date;
toolName?: string;
@@ -162,6 +163,30 @@ export interface UserQuestionEvent {
conversation_id?: string;
}
export interface ElicitationEvent {
message: string;
server_name?: string;
request_id?: string;
conversation_id?: string;
}
export interface ElicitationResultEvent {
action: string;
request_id?: string;
conversation_id?: string;
}
export interface StopFailureEvent {
stop_reason?: string;
error_type?: string;
conversation_id?: string;
}
export interface PostCompactEvent {
session_id?: string;
conversation_id?: string;
}
export type ConnectionStatus = "disconnected" | "connecting" | "connected" | "error";
export interface Attachment {
+2
View File
@@ -36,6 +36,7 @@
import type { CharacterState } from "$lib/types/states";
import PermissionModal from "$lib/components/PermissionModal.svelte";
import UserQuestionModal from "$lib/components/UserQuestionModal.svelte";
import ElicitationModal from "$lib/components/ElicitationModal.svelte";
import ConfigSidebar from "$lib/components/ConfigSidebar.svelte";
import AchievementsPanel from "$lib/components/AchievementsPanel.svelte";
import ToastContainer from "$lib/components/ToastContainer.svelte";
@@ -593,6 +594,7 @@
<PermissionModal />
<UserQuestionModal />
<ElicitationModal />
<ConfigSidebar />
<AchievementsPanel
bind:isOpen={achievementPanelOpen}