From 93b52f06212e8faaeff9c4a332d9da88a4087a71 Mon Sep 17 00:00:00 2001 From: h Date: Thu, 21 May 2026 20:11:52 +0200 Subject: [PATCH] fix: better filename resolution, choosing another agent works --- manifest.json | 2 +- src/agentPicker.ts | 66 +++++++++++++++++++++------------------------- src/main.ts | 42 ++++++++++++++++++++++------- src/settings.ts | 22 ++++++++++++++++ 4 files changed, 86 insertions(+), 46 deletions(-) diff --git a/manifest.json b/manifest.json index 3872e5e..1aff75e 100644 --- a/manifest.json +++ b/manifest.json @@ -4,6 +4,6 @@ "version": "0.1.0", "minAppVersion": "1.5.0", "description": "Send the current note to a Beaver agent via the markdown frontend.", - "author": "Beaver", + "author": "h", "isDesktopOnly": false } diff --git a/src/agentPicker.ts b/src/agentPicker.ts index 074cae6..10c87fd 100644 --- a/src/agentPicker.ts +++ b/src/agentPicker.ts @@ -1,42 +1,36 @@ import { App, FuzzySuggestModal } from "obsidian"; -export class AgentPickerModal extends FuzzySuggestModal { - private agents: string[]; - private resolve: (agent: string | null) => void; - private resolved = false; - - constructor( - app: App, - agents: string[], - resolve: (agent: string | null) => void, - ) { - super(app); - this.agents = agents; - this.resolve = resolve; - this.setPlaceholder("Pick a Beaver agent…"); - } - - getItems(): string[] { - return this.agents; - } - - getItemText(item: string): string { - return item; - } - - onChooseItem(item: string): void { - this.resolved = true; - this.resolve(item); - } - - onClose(): void { - super.onClose(); - if (!this.resolved) this.resolve(null); - } -} - export function pickAgent(app: App, agents: string[]): Promise { return new Promise((resolve) => { - new AgentPickerModal(app, agents, resolve).open(); + let resolved = false; + const settle = (value: string | null) => { + if (resolved) return; + resolved = true; + resolve(value); + }; + + const Picker = class extends FuzzySuggestModal { + getItems(): string[] { + return agents; + } + getItemText(item: string): string { + return item; + } + onChooseItem(item: string): void { + settle(item); + } + onClose(): void { + super.onClose(); + // onClose fires BEFORE onChooseItem in Obsidian's modal pipeline, + // so we can't resolve null synchronously here — it'd race the + // legitimate pick and steal the slot. Defer to the next tick so + // onChooseItem (if any) settles first; if no pick happened + // (Escape / click-away), this fires and we resolve null. + setTimeout(() => settle(null), 0); + } + }; + const modal = new Picker(app); + modal.setPlaceholder("Pick a Beaver agent…"); + modal.open(); }); } diff --git a/src/main.ts b/src/main.ts index baebf9e..eed0d8f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -84,15 +84,22 @@ export default class BeaverPlugin extends Plugin { } private async runDifferent(file: TFile): Promise { - const agents = await this.getAgents(); - if (!agents) return; - if (agents.length === 0) { - new Notice("Beaver: no agents available"); - return; + try { + const agents = await this.getAgents(); + if (!agents) { + new Notice("Beaver: couldn't fetch agents (see console)", 6000); + return; + } + if (agents.length === 0) { + new Notice("Beaver: no agents available"); + return; + } + const picked = await pickAgent(this.app, agents); + if (!picked) return; + await this.dispatch(file, picked); + } catch (err) { + this.notifyError("send-different", err); } - const picked = await pickAgent(this.app, agents); - if (!picked) return; - await this.dispatch(file, picked); } private async getAgents(): Promise { @@ -113,6 +120,9 @@ export default class BeaverPlugin extends Plugin { } private async dispatch(file: TFile, agent: string): Promise { + const filename = this.gatewayFilename(file); + if (!filename) return; + const { editor, view } = this.findEditorFor(file); const content = editor ? editor.getValue() @@ -122,7 +132,7 @@ export default class BeaverPlugin extends Plugin { let response: ChatResponse; try { response = await sendChat(this.settings, { - filename: file.path, + filename, content, agent, }); @@ -149,6 +159,20 @@ export default class BeaverPlugin extends Plugin { new Notice(`Beaver: ${agent} replied`); } + private gatewayFilename(file: TFile): string | null { + const subpath = this.settings.vaultSubpath; + if (!subpath) return file.path; + const prefix = subpath + "/"; + if (file.path === subpath || file.path.startsWith(prefix)) { + return file.path.slice(prefix.length); + } + new Notice( + `Beaver: file ${file.path} is outside the configured vault subpath (${subpath})`, + 8000, + ); + return null; + } + private findEditorFor(file: TFile): { editor: Editor | null; view: MarkdownView | null; diff --git a/src/settings.ts b/src/settings.ts index a2dc82a..c903b64 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -5,13 +5,19 @@ import type BeaverPlugin from "./main"; export interface BeaverSettings { baseUrl: string; token: string; + vaultSubpath: string; } export const DEFAULT_SETTINGS: BeaverSettings = { baseUrl: "http://localhost:62993", token: "", + vaultSubpath: "", }; +export function normalizeSubpath(raw: string): string { + return raw.trim().replace(/^\/+|\/+$/g, ""); +} + export class BeaverSettingsTab extends PluginSettingTab { plugin: BeaverPlugin; @@ -51,6 +57,22 @@ export class BeaverSettingsTab extends PluginSettingTab { }); }); + new Setting(containerEl) + .setName("Vault subpath") + .setDesc( + "Folder in this Obsidian vault that maps to the gateway's vault root. " + + "Leave empty if the two vault roots match. Example: `💬 чаты`.", + ) + .addText((text) => + text + .setPlaceholder("") + .setValue(this.plugin.settings.vaultSubpath) + .onChange(async (value) => { + this.plugin.settings.vaultSubpath = normalizeSubpath(value); + await this.plugin.saveSettings(); + }), + ); + new Setting(containerEl) .setName("Test connection") .setDesc("Calls GET /agents and reports the count.")