diff --git a/AIClasses/ToolDefinitions/AIToolDefinitions.ts b/AIClasses/ToolDefinitions/AIToolDefinitions.ts index b405055..3440655 100644 --- a/AIClasses/ToolDefinitions/AIToolDefinitions.ts +++ b/AIClasses/ToolDefinitions/AIToolDefinitions.ts @@ -23,6 +23,7 @@ import { GetWebViewerContent } from "./Tools/GetWebViewerContent"; import { DeleteVaultFolder } from "./Tools/DeleteVaultFolder"; import { MoveVaultFolder } from "./Tools/MoveVaultFolder"; import { ChatMode, chatModeAllowsEdits } from "Enums/ChatMode"; +import type { AITool } from "Enums/AITool"; export abstract class AIToolDefinitions { @@ -30,6 +31,9 @@ export abstract class AIToolDefinitions { private static readonly definitionsList = [SearchVaultFiles, ReadVaultFiles, ListVaultFiles, GetWebViewerContent, WriteVaultFile, PatchVaultFile, DeleteVaultFiles, MoveVaultFiles, CreateVaultFolder, DeleteVaultFolder, MoveVaultFolder]; + public static readonly editModeDefinitionsList = [WriteVaultFile, PatchVaultFile, DeleteVaultFiles, + MoveVaultFiles, CreateVaultFolder, DeleteVaultFolder, MoveVaultFolder]; + public static agentDefinitions(chatMode: ChatMode, memories: boolean, updateMemories: boolean, webViewer: boolean): IAIToolDefinition[] { if (chatMode === ChatMode.Planning) { @@ -55,15 +59,7 @@ export abstract class AIToolDefinitions { } if (chatModeAllowsEdits(chatMode)) { - actions = actions.concat([ - WriteVaultFile, - PatchVaultFile, - DeleteVaultFiles, - MoveVaultFiles, - CreateVaultFolder, - DeleteVaultFolder, - MoveVaultFolder - ]); + actions = actions.concat(this.editModeDefinitionsList); } return actions; @@ -111,6 +107,10 @@ export abstract class AIToolDefinitions { return [...this.agentDefinitions(ChatMode.Edit, false, false, false), CompleteTask]; } + public static requiresEditModeEnabled(definition: AITool): boolean { + return this.editModeDefinitionsList.map(definition => definition.name).includes(definition); + } + public static compactSummaryForPlanningAgent(): string { return this.definitionsList.map(definition => { // Extract first line of description as brief purpose diff --git a/AIPrompts/IPrompt.ts b/AIPrompts/IPrompt.ts index d36c049..fc64dfa 100644 --- a/AIPrompts/IPrompt.ts +++ b/AIPrompts/IPrompt.ts @@ -8,6 +8,7 @@ import { ExecutionPrompt } from "./ExecutionPrompt"; import { OrchestrationPrompt } from "./OrchestrationPrompt"; import type { MemoriesService } from "Services/MemoriesService"; import { Copy, replaceCopy } from "Enums/Copy"; +import { ChatMode } from "Enums/ChatMode"; export interface IPrompt { systemInstruction(): Promise; @@ -63,6 +64,12 @@ export class AIPrompt implements IPrompt { private buildActiveDirectives(): string { const s = this.settingsService.settings; + const chatModeDirective = s.chatMode === ChatMode.ReadOnly + ? Copy.DirectiveChatModeReadOnly + : s.chatMode === ChatMode.Edit + ? Copy.DirectiveChatModeEdit + : Copy.DirectiveChatModePlanning; + const memoriesDirective = !s.enableMemories ? Copy.DirectiveMemoriesDisabled : s.allowUpdatingMemories @@ -77,7 +84,7 @@ export class AIPrompt implements IPrompt { ? Copy.DirectiveWebViewerEnabled : Copy.DirectiveWebViewerDisabled; - const directives = [memoriesDirective, webSearchDirective, webViewerDirective].join("\n"); + const directives = [chatModeDirective, memoriesDirective, webSearchDirective, webViewerDirective].join("\n"); return replaceCopy(Copy.ActiveCapabilitiesHeader, [directives]); } diff --git a/AIPrompts/SystemPrompt.ts b/AIPrompts/SystemPrompt.ts index 5debf0d..fd32f2f 100644 --- a/AIPrompts/SystemPrompt.ts +++ b/AIPrompts/SystemPrompt.ts @@ -30,7 +30,23 @@ User: "Create a note about today's meeting with Sarah" --- -### 2. PLAN EXECUTION PROTOCOL +### 2. CHAT MODE + +**Your available actions are determined by the current chat mode, shown in the Active Capabilities section at the end of this prompt.** + +| Mode | What you can do | +|------|----------------| +| **Read-Only** | Search, read, and list vault files only — no writes, moves, or deletes | +| **Edit** | Full access — create, edit, move, and delete files | +| **Planning** | Full access via planned workflow execution | + +**In Read-Only mode:** +- Do not attempt to create, edit, move, or delete files — the tools are not available +- If the user asks you to make changes, acknowledge that chat mode is currently set to Read-Only and let them know they can change it using the chat mode button in the input bar + +--- + +### 3. PLAN EXECUTION PROTOCOL **When the user has enabled planning mode and you receive a plan, follow this protocol.** @@ -137,7 +153,7 @@ When asking questions during execution: --- -### 3. HISTORICAL CONTEXT INTERPRETATION +### 4. HISTORICAL CONTEXT INTERPRETATION **Tool call history from previous sessions may appear with HTML comment markers.** @@ -151,7 +167,7 @@ If you see JSON preceded by "Historical tool call/result", it documents a past a --- -### 4. WIKI-LINK EVERYTHING FROM THE VAULT +### 5. WIKI-LINK EVERYTHING FROM THE VAULT **ALWAYS use [[double-bracket]] wiki-link syntax when referencing anything from the user's vault.** @@ -194,7 +210,7 @@ This is a hard requirement, not a suggestion. The format is: \`[[Note Name]]\` --- -### 5. VAULT-FIRST DECISION FRAMEWORK +### 6. VAULT-FIRST DECISION FRAMEWORK **The cost of an unnecessary search is negligible. Missing relevant information is costly.** @@ -218,7 +234,7 @@ Acknowledge the search, then provide general assistance: --- -### 6. MEMORY SYSTEM +### 7. MEMORY SYSTEM **Memory gives you continuity across sessions. It is your record of how this user works in this vault.** diff --git a/Components/ChatInput.svelte b/Components/ChatInput.svelte index ff07811..6a825a6 100644 --- a/Components/ChatInput.svelte +++ b/Components/ChatInput.svelte @@ -30,10 +30,11 @@ export let hasNoApiKey: boolean; export let isSubmitting: boolean; - export let chatMode: ChatMode; export let onSubmit: (userRequest: string, formattedRequest: string) => void; export let onStop: () => void; + const componentToken = {}; + const inputService: InputService = Resolve(Services.InputService); const settingsService: SettingsService = Resolve(Services.SettingsService); const userInputService: UserInputService = Resolve(Services.UserInputService); @@ -59,6 +60,7 @@ let userRequest: string = ""; + let chatMode: ChatMode = settingsService.settings.chatMode; let inputMode: InputMode = InputMode.Normal; let questionResolver: ((answer: string) => void) | null = null; @@ -70,6 +72,8 @@ const diffClosedRef: EventRef = eventService.on(Event.DiffClosed, () => { inputMode = InputMode.Normal; focusInput(); }); const rateLimitCountdownRef: EventRef = eventService.on(Event.RateLimitCountdown, (delayMs: number) => { startCountdown(delayMs); }); + settingsService.subscribeToSettingsChanged(componentToken, () => chatMode = settingsService.settings.chatMode); + onMount(async () => { userInstructionActive = (await aiPrompt.userInstruction()).trim() !== ""; inputInitialHeight = textareaElement.innerHeight; @@ -290,8 +294,9 @@ } function toggleWebSearch() { - settingsService.settings.enableWebSearch = !settingsService.settings.enableWebSearch; - settingsService.saveSettings(); + settingsService.updateSettings(settings => { + settings.enableWebSearch = !settingsService.settings.enableWebSearch; + }); } function toggleChatModeSelectionArea() { @@ -541,7 +546,7 @@
- +