Add automatic folder creation for file operations and improve chat session handling
Implement `ensureFolderExists` in `ToolExecutor` to create parent directories when creating, moving, or renaming notes. Update `ChatView` to properly persist agent mode and derive session titles from the first user message. Fix error handling in tool execution to return structured failures instead of null, and include failure details in follow-up messages.
This commit is contained in:
@@ -8968,6 +8968,30 @@ var ToolExecutor = class {
|
||||
const file = this.getFile(path);
|
||||
await this.vault.modify(file, content);
|
||||
}
|
||||
getParentFolderPath(path) {
|
||||
const parts = path.split("/").filter((part) => part.length > 0);
|
||||
parts.pop();
|
||||
return parts.join("/");
|
||||
}
|
||||
async ensureFolderExists(folderPath) {
|
||||
const normalizedFolder = folderPath.replace(/\/$/, "").trim();
|
||||
if (!normalizedFolder) {
|
||||
return;
|
||||
}
|
||||
const parts = normalizedFolder.split("/").filter((part) => part.length > 0);
|
||||
let currentPath = "";
|
||||
for (const part of parts) {
|
||||
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
||||
const existing = this.vault.getAbstractFileByPath(currentPath);
|
||||
if (existing) {
|
||||
if (!(existing instanceof import_obsidian.TFolder)) {
|
||||
throw new Error(`Cannot create folder ${currentPath}: a file already exists at that path`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
await this.vault.createFolder(currentPath);
|
||||
}
|
||||
}
|
||||
async handleToolCall(toolCall) {
|
||||
const startTime = Date.now();
|
||||
const toolName = toolCall.function?.name ?? "unknown";
|
||||
@@ -9062,6 +9086,7 @@ var ToolExecutor = class {
|
||||
throw new Error("Invalid file path detected");
|
||||
}
|
||||
try {
|
||||
await this.ensureFolderExists(this.getParentFolderPath(path));
|
||||
await this.vault.create(path, content);
|
||||
return { success: true, message: "Note created successfully" };
|
||||
} catch (error) {
|
||||
@@ -9285,6 +9310,7 @@ ${lines.join("\n")}
|
||||
throw new Error("Invalid file path detected");
|
||||
}
|
||||
const file = this.getFile(oldPath);
|
||||
await this.ensureFolderExists(this.getParentFolderPath(newPath));
|
||||
await this.vault.rename(file, newPath);
|
||||
return { success: true, message: `Note renamed from ${oldPath} to ${newPath}` };
|
||||
}
|
||||
@@ -9307,6 +9333,7 @@ ${lines.join("\n")}
|
||||
const file = this.getFile(path);
|
||||
const fileName = file.name;
|
||||
const newPath = normalizedFolder ? `${normalizedFolder}/${fileName}` : fileName;
|
||||
await this.ensureFolderExists(normalizedFolder);
|
||||
await this.vault.rename(file, newPath);
|
||||
return { success: true, message: `Note moved to ${newPath}` };
|
||||
}
|
||||
@@ -11022,6 +11049,7 @@ var ChatView = class extends import_obsidian5.ItemView {
|
||||
newSettings.agentModel ?? newSettings.model,
|
||||
{ cacheConfig: newSettings.cacheConfig }
|
||||
);
|
||||
this.conversationStateManager.setSystemPrompt(getSystemPromptForMode(this.currentAgentMode));
|
||||
void this.initializeClientCaches().catch(() => {
|
||||
new import_obsidian5.Notice(
|
||||
"Semantic cache unavailable: could not connect to ChromaDB. Check the ChromaDB URL in settings."
|
||||
@@ -11171,7 +11199,7 @@ var ChatView = class extends import_obsidian5.ItemView {
|
||||
}
|
||||
}
|
||||
this.modeSelectorEl.addEventListener("change", () => {
|
||||
this.currentAgentMode = this.modeSelectorEl.value;
|
||||
this.setAgentMode(this.modeSelectorEl.value);
|
||||
});
|
||||
} else {
|
||||
newChatContainer.appendChild(this.modeSelectorEl);
|
||||
@@ -11368,6 +11396,10 @@ var ChatView = class extends import_obsidian5.ItemView {
|
||||
this.modeSelectorEl.value = mode;
|
||||
}
|
||||
this.conversationStateManager.setSystemPrompt(getSystemPromptForMode(mode));
|
||||
const activeId = this.chatHistoryManager?.getActiveSessionId();
|
||||
if (activeId) {
|
||||
this.chatHistoryManager?.updateSession(activeId, { agentMode: mode });
|
||||
}
|
||||
}
|
||||
clearConversation() {
|
||||
this.saveCurrentSession();
|
||||
@@ -11409,14 +11441,28 @@ var ChatView = class extends import_obsidian5.ItemView {
|
||||
const activeId = this.chatHistoryManager.getActiveSessionId();
|
||||
if (!activeId) return;
|
||||
const nonStreamingMessages = this.messages.filter((msg) => !msg.isStreaming);
|
||||
this.chatHistoryManager.updateSessionMessages(activeId, nonStreamingMessages);
|
||||
this.chatHistoryManager.updateSession(activeId, {
|
||||
messages: nonStreamingMessages,
|
||||
agentMode: this.currentAgentMode,
|
||||
title: this.deriveSessionTitle(nonStreamingMessages)
|
||||
});
|
||||
}
|
||||
syncMessagesToSession() {
|
||||
if (!this.chatHistoryManager) return;
|
||||
const activeId = this.chatHistoryManager.getActiveSessionId();
|
||||
if (!activeId) return;
|
||||
const nonStreamingMessages = this.messages.filter((msg) => !msg.isStreaming);
|
||||
this.chatHistoryManager.updateSessionMessages(activeId, nonStreamingMessages);
|
||||
this.chatHistoryManager.updateSession(activeId, {
|
||||
messages: nonStreamingMessages,
|
||||
agentMode: this.currentAgentMode,
|
||||
title: this.deriveSessionTitle(nonStreamingMessages)
|
||||
});
|
||||
}
|
||||
deriveSessionTitle(messages) {
|
||||
const firstUser = messages.find((m) => m.role === "user");
|
||||
const text = firstUser?.content.trim() ?? "";
|
||||
if (!text) return "New Chat";
|
||||
return text.length > 40 ? text.slice(0, 40) + "\u2026" : text;
|
||||
}
|
||||
populateHistoryDropdown() {
|
||||
if (!this.historySelectEl) return;
|
||||
@@ -11785,7 +11831,7 @@ var ChatView = class extends import_obsidian5.ItemView {
|
||||
if (writePreviews.length > 0 && modeRequiresPreview(this.currentAgentMode)) {
|
||||
this.pendingActions = writePreviews;
|
||||
this.pendingReadResults = readResults;
|
||||
this.pendingFollowUpContext = { messages, tools, assistantMessageId };
|
||||
this.pendingFollowUpContext = { messages, tools, assistantMessageId, allToolCalls: toolCalls, assistantText: fullResponse };
|
||||
this.updateMessageById(assistantMessageId, {
|
||||
content: `${fullResponse}
|
||||
|
||||
@@ -11800,17 +11846,21 @@ ${writePreviews.map((a) => `- ${a.description}`).join("\n")}`,
|
||||
}
|
||||
let writeResults = [];
|
||||
if (writePreviews.length > 0 && !modeRequiresPreview(this.currentAgentMode)) {
|
||||
writeResults = (await Promise.all(
|
||||
writeResults = await Promise.all(
|
||||
writePreviews.map(async (action) => {
|
||||
try {
|
||||
const toolResult = await this.toolExecutor.handleToolCall(action.toolCall);
|
||||
return { ...toolResult, id: action.toolCall.id };
|
||||
} catch (error) {
|
||||
ErrorHandler.handleError(error, "ChatView.processToolCalls");
|
||||
return null;
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
id: action.toolCall.id
|
||||
};
|
||||
}
|
||||
})
|
||||
)).filter((result) => result !== null);
|
||||
);
|
||||
}
|
||||
const allResults = [...readResults, ...writeResults];
|
||||
const followUpMessages = allResults.map((result) => ({
|
||||
@@ -11868,19 +11918,24 @@ ${writePreviews.map((a) => `- ${a.description}`).join("\n")}`,
|
||||
if (this.pendingActions.length === 0 || !this.pendingFollowUpContext) {
|
||||
return;
|
||||
}
|
||||
const { messages, tools, assistantMessageId } = this.pendingFollowUpContext;
|
||||
const writeResults = (await Promise.all(
|
||||
const { messages, tools, assistantMessageId, allToolCalls, assistantText } = this.pendingFollowUpContext;
|
||||
const writeResults = await Promise.all(
|
||||
this.pendingActions.map(async (action) => {
|
||||
try {
|
||||
const toolResult = await this.toolExecutor.handleToolCall(action.toolCall);
|
||||
return { ...toolResult, id: action.toolCall.id };
|
||||
} catch (error) {
|
||||
ErrorHandler.handleError(error, "ChatView.applyPendingActions");
|
||||
return null;
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
id: action.toolCall.id
|
||||
};
|
||||
}
|
||||
})
|
||||
)).filter((result) => result !== null);
|
||||
);
|
||||
const allResults = [...this.pendingReadResults, ...writeResults];
|
||||
const failedWrites = writeResults.filter((result) => !result.success);
|
||||
const followUpMessages = allResults.map((result) => ({
|
||||
role: "tool",
|
||||
content: JSON.stringify(result),
|
||||
@@ -11888,8 +11943,8 @@ ${writePreviews.map((a) => `- ${a.description}`).join("\n")}`,
|
||||
}));
|
||||
const followUp = {
|
||||
role: "assistant",
|
||||
content: "I have processed your request using the following tools. Here are the results:",
|
||||
tool_calls: this.pendingActions.map((a) => a.toolCall)
|
||||
content: assistantText,
|
||||
tool_calls: allToolCalls
|
||||
};
|
||||
if (followUpMessages.length > 0) {
|
||||
const finalMessages = [...messages, followUp, ...followUpMessages];
|
||||
@@ -11920,14 +11975,16 @@ ${writePreviews.map((a) => `- ${a.description}`).join("\n")}`,
|
||||
);
|
||||
} else {
|
||||
this.updateMessageById(assistantMessageId, {
|
||||
content: followUpContent || "Actions applied successfully.",
|
||||
content: followUpContent || (failedWrites.length > 0 ? `Some actions failed:
|
||||
${failedWrites.map((r) => `- ${r.message}`).join("\n")}` : "Actions applied successfully."),
|
||||
isStreaming: false,
|
||||
isThinking: false
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.updateMessageById(assistantMessageId, {
|
||||
content: "Actions applied successfully.",
|
||||
content: failedWrites.length > 0 ? `Actions failed:
|
||||
${failedWrites.map((r) => `- ${r.message}`).join("\n")}` : "Actions applied successfully.",
|
||||
isStreaming: false,
|
||||
isThinking: false
|
||||
});
|
||||
@@ -12477,7 +12534,7 @@ ${actualMessage}` : actualMessage;
|
||||
return this.isAgenticMode(this.currentAgentMode) ? this.agentOllamaClient : this.ollamaClient;
|
||||
}
|
||||
isAgenticMode(mode) {
|
||||
return mode === "edit" || mode === "organize" || mode === "workflow";
|
||||
return mode === "edit" || mode === "organize" || mode === "research" || mode === "workflow";
|
||||
}
|
||||
renderLogEntry(entry, container) {
|
||||
const row = container.createEl("div", { cls: "ollama-log-row" });
|
||||
|
||||
Reference in New Issue
Block a user