fix: AI-created notes now trigger vault refresh and auto-open in tab
Root causes: - toolInputMapRef in useAiAgent was overwritten by tool_progress events (which arrive with input=undefined AFTER the assistant message set the full input), causing detectFileOperation to receive undefined and skip file creation detection entirely. - MCP open_note only broadcast open_tab without vault_changed, so the note list didn't refresh when Claude Code called open_note. - detectFileOperation only handled Write/Edit but not Bash commands that create .md files via redirects. Fixes: - Preserve accumulated input in toolInputMapRef (input ?? prev?.input) - MCP open_note now broadcasts vault_changed before open_tab - detectFileOperation now detects Bash redirect patterns (>, >>, tee) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1 @@
|
||||
66070
|
||||
88289
|
||||
|
||||
@@ -148,6 +148,9 @@ async function handleGetNote(args) {
|
||||
}
|
||||
|
||||
function handleOpenNote(args) {
|
||||
// Refresh vault first so the new/modified note appears in the note list,
|
||||
// then signal the UI to open it in a tab.
|
||||
broadcastUiAction('vault_changed', { path: args.path })
|
||||
broadcastUiAction('open_tab', { path: args.path })
|
||||
return { content: [{ type: 'text', text: `Opening ${args.path} in Laputa` }] }
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ const TOOL_HANDLERS = {
|
||||
read_note: (args) => getNote(VAULT_PATH, args.path).then(note => ({ content: note.content, frontmatter: note.frontmatter })),
|
||||
search_notes: (args) => searchNotes(VAULT_PATH, args.query, args.limit),
|
||||
vault_context: () => vaultContext(VAULT_PATH),
|
||||
ui_open_note: (args) => { broadcastUiAction('open_note', { path: args.path }); return { ok: true } },
|
||||
ui_open_tab: (args) => { broadcastUiAction('open_tab', { path: args.path }); return { ok: true } },
|
||||
ui_open_note: (args) => { broadcastUiAction('vault_changed', { path: args.path }); broadcastUiAction('open_note', { path: args.path }); return { ok: true } },
|
||||
ui_open_tab: (args) => { broadcastUiAction('vault_changed', { path: args.path }); broadcastUiAction('open_tab', { path: args.path }); return { ok: true } },
|
||||
ui_highlight: (args) => { broadcastUiAction('highlight', { element: args.element, path: args.path }); return { ok: true } },
|
||||
ui_set_filter: (args) => { broadcastUiAction('set_filter', { filterType: args.type }); return { ok: true } },
|
||||
highlight_editor: (args) => { broadcastUiAction('highlight', { element: args.element, path: args.path }); return { ok: true } },
|
||||
|
||||
@@ -105,7 +105,10 @@ export function useAiAgent(
|
||||
if (abortRef.current.aborted) return
|
||||
markReasoningDone()
|
||||
setStatus('tool-executing')
|
||||
toolInputMapRef.current.set(toolId, { tool: toolName, input })
|
||||
// Preserve accumulated input — tool_progress events arrive with
|
||||
// input=undefined AFTER the assistant message set the full input.
|
||||
const prev = toolInputMapRef.current.get(toolId)
|
||||
toolInputMapRef.current.set(toolId, { tool: toolName, input: input ?? prev?.input })
|
||||
update(m => {
|
||||
const existing = m.actions.find(a => a.toolId === toolId)
|
||||
if (existing) {
|
||||
@@ -212,6 +215,14 @@ function detectFileOperation(
|
||||
callbacks: AgentFileCallbacks | undefined,
|
||||
) {
|
||||
if (!callbacks) return
|
||||
|
||||
// Handle Bash commands that create/write .md files
|
||||
if (toolName === 'Bash') {
|
||||
const mdPath = parseBashFileCreation(input, vaultPath)
|
||||
if (mdPath) callbacks.onFileCreated?.(mdPath)
|
||||
return
|
||||
}
|
||||
|
||||
if (toolName !== 'Write' && toolName !== 'Edit') return
|
||||
const filePath = parseFilePath(input)
|
||||
if (!filePath || !filePath.endsWith('.md')) return
|
||||
@@ -224,6 +235,23 @@ function detectFileOperation(
|
||||
}
|
||||
}
|
||||
|
||||
/** Detect .md file creation from a Bash command string. */
|
||||
function parseBashFileCreation(input: string | undefined, vaultPath: string): string | null {
|
||||
if (!input) return null
|
||||
try {
|
||||
const parsed = JSON.parse(input)
|
||||
const cmd = parsed.command ?? parsed.cmd
|
||||
if (typeof cmd !== 'string') return null
|
||||
// Match redirect patterns: > file.md, >> file.md, tee file.md, cat > file.md
|
||||
const match = cmd.match(/(?:>|>>|tee\s+(?:-a\s+)?)\s*["']?([^\s"'|;]+\.md)["']?/)
|
||||
if (!match) return null
|
||||
const filePath = match[1]
|
||||
return toVaultRelative(filePath, vaultPath)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/** Generate a human-readable label for a tool call. */
|
||||
function formatToolLabel(toolName: string, input?: string): string {
|
||||
// Native Claude Code tools
|
||||
|
||||
Reference in New Issue
Block a user