diff --git a/frontend/src/components/AgentBar.vue b/frontend/src/components/AgentBar.vue index d5bc4cd..b0f6030 100644 --- a/frontend/src/components/AgentBar.vue +++ b/frontend/src/components/AgentBar.vue @@ -20,6 +20,21 @@ const activeAgentId = ref(null) const activeAnchorRect = ref(null) const openInRecording = ref(false) const promptBarRef = ref | null>(null) +const bubbleRefs = new Map() + +function setBubbleRef(agentId: string, el: any) { + if (el?.$el) bubbleRefs.set(agentId, el.$el) + else if (el) bubbleRefs.set(agentId, el) +} + +function autoOpenForAgent(agentId: string) { + if (activeAgentId.value === agentId) return // Already open + const bubbleEl = bubbleRefs.get(agentId) + if (!bubbleEl) return + activeAnchorRect.value = bubbleEl.getBoundingClientRect() + openInRecording.value = false + activeAgentId.value = agentId +} const isRecordingActive = computed(() => promptBarRef.value?.isRecording ?? false @@ -177,6 +192,8 @@ function connectStatusWs() { case 'permissionRequest': s.awaitingPermission = true + // Auto-open PromptBar if not already open for this agent + autoOpenForAgent(agent.id) break case 'reading': @@ -334,6 +351,7 @@ onBeforeUnmount(() => { m.intervention?.requestId === rid)) return const input = msg.tool_input || {} + const toolName = msg.tool_name || 'Unknown' + + // Build a summary line for the content field (fallback) let detail = '' - if (msg.tool_name === 'Bash') { + if (toolName === 'Bash') { detail = input.command || '' - } else if (msg.tool_name === 'Edit' || msg.tool_name === 'Write') { + } else if (toolName === 'Edit') { detail = input.file_path || '' + } else if (toolName === 'Write') { + detail = input.file_path || '' + } else if (toolName === 'Grep') { + detail = `${input.pattern || ''} in ${input.path || '.'}` + } else if (toolName === 'Glob') { + detail = input.pattern || '' + } else if (toolName === 'WebFetch') { + detail = input.url || '' + } else if (toolName === 'WebSearch') { + detail = input.query || '' + } else if (toolName.startsWith('mcp__')) { + detail = JSON.stringify(input).slice(0, 300) } else { - detail = JSON.stringify(input).slice(0, 200) + detail = JSON.stringify(input).slice(0, 300) } messages.push({ @@ -412,7 +427,7 @@ function addPermissionCard(msg: any) { intervention: { type: 'permission', requestId: rid, - toolName: msg.tool_name, + toolName, toolInput: input, resolved: false } @@ -420,6 +435,36 @@ function addPermissionCard(msg: any) { scrollToBottom() } +// ── Permission card helpers ── + +function permToolMeta(toolName: string): ToolMeta { + return TOOL_CATEGORIES[toolName] || getToolMeta(toolName) +} + +function permToolDescription(toolName: string): string { + const descs: Record = { + Bash: 'Execute shell command', + Edit: 'Modify file content', + Write: 'Create or overwrite file', + Read: 'Read file contents', + Glob: 'Search for files', + Grep: 'Search file contents', + WebFetch: 'Fetch URL content', + WebSearch: 'Web search', + Task: 'Launch sub-agent', + NotebookEdit: 'Edit Jupyter notebook', + } + if (descs[toolName]) return descs[toolName] + if (toolName.startsWith('mcp__')) return 'MCP tool call' + return 'Tool execution' +} + +function truncateMiddle(str: string, maxLen: number): string { + if (!str || str.length <= maxLen) return str + const half = Math.floor((maxLen - 3) / 2) + return str.slice(0, half) + '...' + str.slice(-half) +} + function addQuestionCard(msg: any) { const input = msg.tool_input || {} const questions = input.questions || [] @@ -788,18 +833,78 @@ onBeforeUnmount(() => {