diff --git a/agent-teams-controller/src/internal/maintenance.js b/agent-teams-controller/src/internal/maintenance.js index e650fc6e..c933ace9 100644 --- a/agent-teams-controller/src/internal/maintenance.js +++ b/agent-teams-controller/src/internal/maintenance.js @@ -82,7 +82,7 @@ function isAutomatedCommentNotification(message) { if (!text) return false; if (text.includes('Reply to this comment using:')) return true; - if (text.startsWith('Comment on task #')) return true; + if (text.startsWith('**Comment on task')) return true; if (text.startsWith('New comment from user on your task #')) return true; return false; } diff --git a/agent-teams-controller/src/internal/tasks.js b/agent-teams-controller/src/internal/tasks.js index 47417436..acb1e522 100644 --- a/agent-teams-controller/src/internal/tasks.js +++ b/agent-teams-controller/src/internal/tasks.js @@ -65,7 +65,8 @@ function buildAssignmentMessage(context, task, options = {}) { function buildCommentNotificationMessage(context, task, comment) { const taskLabel = `#${task.displayId || task.id}`; return [ - `Comment on task ${taskLabel} "${task.subject}":`, + `**Comment on task ${taskLabel}**`, + `> ${task.subject}`, ``, comment.text, ``, diff --git a/agent-teams-controller/test/controller.test.js b/agent-teams-controller/test/controller.test.js index 30219aea..322611ef 100644 --- a/agent-teams-controller/test/controller.test.js +++ b/agent-teams-controller/test/controller.test.js @@ -416,7 +416,7 @@ describe('agent-teams-controller API', () => { timestamp: '2026-02-23T11:00:00.000Z', read: false, text: - `Comment on task #${task.displayId} "Ship migration":\n\nHeads up\n\n` + + `**Comment on task #${task.displayId}**\n> Ship migration\n\nHeads up\n\n` + '\nReply to this comment using:\nnode "tool.js" --team my-team task comment 1 --text "..." --from "bob"\n', }, ], diff --git a/src/main/services/team/TeamMemberLogsFinder.ts b/src/main/services/team/TeamMemberLogsFinder.ts index d8147674..998ee010 100644 --- a/src/main/services/team/TeamMemberLogsFinder.ts +++ b/src/main/services/team/TeamMemberLogsFinder.ts @@ -259,6 +259,7 @@ export class TeamMemberLogsFinder { if (s) results.push(s); } const totalFiles = candidates.length; + const step2Count = results.length; // count before step 3 (owner fallback) const tScan = performance.now(); const normalizedOwner = @@ -350,13 +351,11 @@ export class TeamMemberLogsFinder { const tTotal = performance.now(); console.log( - `[perf] findLogsForTask(${taskId}@${teamName}) ` + - `total=${(tTotal - t0).toFixed(0)}ms | ` + - `discovery=${(tDiscovery - t0).toFixed(0)}ms | ` + - `lead=${(tLead - tDiscovery).toFixed(0)}ms | ` + - `scan=${(tScan - tLead).toFixed(0)}ms (${totalFiles} files, ${mentionHits} hits) | ` + - `owner=${(tOwner - tScan).toFixed(0)}ms | ` + - `sessions=${sessionIds.length} | results=${sorted.length}` + `[findLogsForTask] task=${taskId}@${teamName} | ` + + `step2=${step2Count} (scan ${mentionHits}/${totalFiles} files) | ` + + `step3=${sorted.length - step2Count} (owner=${normalizedOwner ?? 'none'}, includeOwner=${includeOwnerSessions}) | ` + + `total=${sorted.length} | ` + + `${(tTotal - t0).toFixed(0)}ms` ); return sorted; @@ -975,6 +974,12 @@ export class TeamMemberLogsFinder { const b = block as Record; if (b.type !== 'tool_use') continue; + // Skip read-only task tools — they reference taskId but don't indicate + // that this session actually WORKED on the task. Agents commonly call + // task_get to check dependencies from other tasks, producing false matches. + const toolName = typeof b.name === 'string' ? b.name : ''; + if (toolName === 'task_get' || toolName === 'mcp__agent-teams__task_get') continue; + const input = b.input as Record | undefined; if (!input) continue; diff --git a/src/renderer/components/chat/viewers/MarkdownViewer.tsx b/src/renderer/components/chat/viewers/MarkdownViewer.tsx index 092f2301..0618f3fa 100644 --- a/src/renderer/components/chat/viewers/MarkdownViewer.tsx +++ b/src/renderer/components/chat/viewers/MarkdownViewer.tsx @@ -812,6 +812,7 @@ export const MarkdownViewer: React.FC = ({ components={components} urlTransform={allowCustomProtocols} allowElement={isAllowedElement} + unwrapDisallowed > {content} diff --git a/src/renderer/components/team/kanban/KanbanBoard.tsx b/src/renderer/components/team/kanban/KanbanBoard.tsx index 270def53..1728a6e4 100644 --- a/src/renderer/components/team/kanban/KanbanBoard.tsx +++ b/src/renderer/components/team/kanban/KanbanBoard.tsx @@ -104,8 +104,8 @@ type KanbanViewMode = 'grid' | 'columns'; const COLUMNS: { id: KanbanColumnId; title: string }[] = [ { id: 'todo', title: 'TODO' }, { id: 'in_progress', title: 'IN PROGRESS' }, - { id: 'done', title: 'DONE' }, { id: 'review', title: 'REVIEW' }, + { id: 'done', title: 'DONE' }, { id: 'approved', title: 'APPROVED' }, ]; diff --git a/src/renderer/components/team/kanban/KanbanGridLayout.tsx b/src/renderer/components/team/kanban/KanbanGridLayout.tsx index 7159f93f..75c3b944 100644 --- a/src/renderer/components/team/kanban/KanbanGridLayout.tsx +++ b/src/renderer/components/team/kanban/KanbanGridLayout.tsx @@ -23,7 +23,7 @@ const DEFAULT_ITEM_HEIGHT = Math.max( ); const DEFAULT_MIN_HEIGHT = 10; const DEFAULT_MIN_WIDTH = 3; -const GRID_SCOPE_KEY = 'kanban-grid-layout:global'; +const GRID_SCOPE_KEY = 'kanban-grid-layout:global:v2'; const SKELETON_HIDE_DELAY_MS = 500; const RESIZE_HANDLES: ResizeHandleAxis[] = ['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne']; const WidthAwareGridLayout = WidthProvider(ReactGridLayout); @@ -54,16 +54,19 @@ interface LoadingKanbanGridLayoutProps { readonly visibleItems: PersistedGridLayoutItem[]; } +const ITEMS_PER_FIRST_ROW = 3; +const SECOND_ROW_ITEM_WIDTH = 6; + function buildDefaultItems(itemIds: string[]): PersistedGridLayoutItem[] { - return itemIds.map((id, index) => ({ - id, - x: (index % 3) * DEFAULT_ITEM_WIDTH, - y: Math.floor(index / 3) * DEFAULT_ITEM_HEIGHT, - w: DEFAULT_ITEM_WIDTH, - h: DEFAULT_ITEM_HEIGHT, - minW: DEFAULT_MIN_WIDTH, - minH: DEFAULT_MIN_HEIGHT, - })); + return itemIds.map((id, index) => { + const isSecondRow = index >= ITEMS_PER_FIRST_ROW; + const w = isSecondRow ? SECOND_ROW_ITEM_WIDTH : DEFAULT_ITEM_WIDTH; + const x = isSecondRow + ? (index - ITEMS_PER_FIRST_ROW) * SECOND_ROW_ITEM_WIDTH + : index * DEFAULT_ITEM_WIDTH; + const y = isSecondRow ? DEFAULT_ITEM_HEIGHT : 0; + return { id, x, y, w, h: DEFAULT_ITEM_HEIGHT, minW: DEFAULT_MIN_WIDTH, minH: DEFAULT_MIN_HEIGHT }; + }); } function toReactGridLayoutItem(item: PersistedGridLayoutItem): LayoutItem { diff --git a/src/renderer/components/ui/MentionSuggestionList.tsx b/src/renderer/components/ui/MentionSuggestionList.tsx index eed589bb..e845a40e 100644 --- a/src/renderer/components/ui/MentionSuggestionList.tsx +++ b/src/renderer/components/ui/MentionSuggestionList.tsx @@ -188,6 +188,9 @@ export const MentionSuggestionList = ({ > + {!isTask && !isFileOrFolder && s.subtitle ? ( + {s.subtitle} + ) : null} {isTask && s.ownerName ? ( ) : null} @@ -213,12 +216,12 @@ export const MentionSuggestionList = ({ title={s.isOnline ? 'Online' : 'Offline'} /> ) : null} - {s.subtitle && !isTask ? ( + {s.subtitle && isFileOrFolder ? ( - {isFileOrFolder ? '\u200E' + s.subtitle : s.subtitle} + {'\u200E' + s.subtitle} ) : null}