Skip to content
Merged
4 changes: 3 additions & 1 deletion src/crates/core/src/agentic/agents/prompts/code_review.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ When you have gathered sufficient context and completed your review, call the `s
"remediation_groups": {
"must_fix": ["Required correctness/security/regression fixes"],
"should_improve": ["Non-blocking cleanup or quality improvements"],
"needs_decision": ["Items needing user/product judgment"],
"needs_decision": [
{"question": "Decision point description", "plan": "Remediation if approved", "options": ["Option A", "Option B"], "tradeoffs": "Trade-off explanation", "recommendation": 0}
],
"verification": ["Focused verification steps"]
},
"strength_groups": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,12 @@ After the quality gate finishes:
- `executive_summary`: 1-3 concise bullets with the final decision and most important risk.
- `remediation_groups.must_fix`: required correctness/security/regression fixes.
- `remediation_groups.should_improve`: non-blocking cleanup or quality improvements.
- `remediation_groups.needs_decision`: items that need user/product judgment.
- `remediation_groups.needs_decision`: items that need user/product judgment. Each item MUST be an object with:
- `question` (required): the specific decision point (e.g. "Should we use eager loading or lazy loading for this relation?")
- `plan` (required): the remediation plan text to execute if the user approves this item
- `options` (optional): 2-4 possible approaches or choices
- `tradeoffs` (optional): brief trade-off explanation
- `recommendation` (optional): 0-based index of the recommended option
- `remediation_groups.verification`: focused verification or follow-up review steps.
- `strength_groups`: positive observations grouped under `architecture`, `maintainability`, `tests`, `security`, `performance`, `user_experience`, or `other`.
- `coverage_notes`: confidence, timeout/cancel/failure, scope, or manual follow-up notes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,41 @@ impl CodeReviewTool {
},
"needs_decision": {
"type": "array",
"items": { "type": "string" }
"description": "Items needing user/product judgment. Each item should be an object with a 'question' and 'plan'.",
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The specific decision the user needs to make"
},
"plan": {
"type": "string",
"description": "The remediation plan text to execute if the user approves"
},
"options": {
"type": "array",
"description": "2-4 possible choices or approaches",
"items": { "type": "string" }
},
"tradeoffs": {
"type": "string",
"description": "Brief explanation of trade-offs between options"
},
"recommendation": {
"type": "integer",
"description": "Index of the recommended option (0-based), if any"
}
},
"required": ["question", "plan"]
},
{
"type": "string"
}
]
}
},
"verification": {
"type": "array",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ impl Default for FileReadTool {
impl FileReadTool {
pub fn new() -> Self {
Self {
default_max_lines_to_read: 250,
max_line_chars: 300,
max_total_chars: 32_000,
default_max_lines_to_read: 2000,
max_line_chars: 2000,
max_total_chars: 50_000,
}
}

Expand Down Expand Up @@ -165,13 +165,14 @@ Assume this tool is able to read all files on the machine. If the User provides

Usage:
- The file_path parameter must be either an absolute path or an exact `bitfun://runtime/...` URI returned by another tool.
- By default, it reads up to {} lines starting from the beginning of the file.
- By default, it reads up to {} lines starting from the beginning of the file.
- You can optionally specify a start_line and limit. For large files, prefer reading targeted ranges instead of starting over from the beginning every time.
- Any lines longer than {} characters will be truncated.
- Total output is capped at {} characters. If that limit is hit, narrow the range with start_line and limit.
- Results are returned using cat -n format, with line numbers starting at 1
- Results are returned using cat -n format, with line numbers starting at 1.
- This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
- You can call multiple tools in a single response. It is always better to speculatively read multiple potentially useful files in parallel.
- Avoid tiny repeated slices (e.g. 30-100 line chunks). If you need more context, read a larger window.
"#,
self.default_max_lines_to_read, self.max_line_chars, self.max_total_chars
))
Expand Down Expand Up @@ -400,8 +401,17 @@ Usage:
result_for_assistant.push_str(rules_content);
}

if read_file_result.hit_total_char_limit {
result_for_assistant.push_str("\n\n[Output truncated after reaching the Read tool size limit. Request a narrower range with start_line and limit.]");
let has_more = read_file_result.end_line < read_file_result.total_lines;
if has_more {
let next_start = read_file_result.end_line + 1;
if read_file_result.hit_total_char_limit {
result_for_assistant.push_str(
&format!("\n\n[Output truncated after reaching the Read tool size limit. Use start_line={} and limit to continue reading.]", next_start));
} else {
result_for_assistant.push_str(
&format!("\n\n[Showing lines {}-{} of {} total. Use start_line={} and limit to continue reading.]",
read_file_result.start_line, read_file_result.end_line, read_file_result.total_lines, next_start));
}
}

let lines_read = if read_file_result.total_lines == 0
Expand Down
26 changes: 14 additions & 12 deletions src/web-ui/src/app/scenes/skills/SkillsScene.scss
Original file line number Diff line number Diff line change
Expand Up @@ -159,34 +159,36 @@ $skills-content-top: clamp(36px, 4.5vh, 48px);
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
height: 34px;
padding: 0 $size-gap-3;
border: none;
border-radius: $size-radius-base;
@include btn-primary.btn-primary-surface-default;
gap: $size-gap-1;
height: 24px;
padding: 0 $size-gap-2;
border: 1px solid color-mix(in srgb, var(--color-accent-500) 20%, var(--border-subtle));
border-radius: $size-radius-full;
background: color-mix(in srgb, var(--color-accent-500) 8%, transparent);
color: var(--color-accent-600, var(--color-text-secondary));
font-size: var(--font-size-xs);
font-weight: $font-weight-medium;
cursor: pointer;
white-space: nowrap;
transition:
background $motion-fast $easing-standard,
color $motion-fast $easing-standard,
box-shadow $motion-fast $easing-standard;
white-space: nowrap;
border-color $motion-fast $easing-standard;

&:hover {
@include btn-primary.btn-primary-surface-hover;
background: color-mix(in srgb, var(--color-accent-500) 14%, transparent);
border-color: color-mix(in srgb, var(--color-accent-500) 30%, var(--border-medium));
color: var(--color-accent-600, var(--color-text-primary));
}

&:active {
@include btn-primary.btn-primary-surface-active;
background: color-mix(in srgb, var(--color-accent-500) 18%, transparent);
}

&:focus-visible {
outline: 2px solid color-mix(
in srgb,
var(--btn-primary-color, var(--color-accent-500)) 55%,
var(--color-accent-500) 55%,
transparent
);
outline-offset: 2px;
Expand Down
20 changes: 18 additions & 2 deletions src/web-ui/src/app/scenes/skills/SkillsScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CheckCircle2,
ChevronLeft,
ChevronRight,
Filter,
FolderOpen,
Package,
Plus,
Expand Down Expand Up @@ -43,10 +44,12 @@ const SkillsScene: React.FC = () => {
searchDraft,
marketQuery,
installedFilter,
hideDuplicates,
isAddFormOpen,
setSearchDraft,
submitMarketQuery,
setInstalledFilter,
setHideDuplicates,
setAddFormOpen,
toggleAddForm,
} = useSkillsSceneStore();
Expand Down Expand Up @@ -115,7 +118,9 @@ const SkillsScene: React.FC = () => {
const selectedInstalledSkill = selectedDetail?.type === 'installed' ? selectedDetail.skill : null;
const selectedMarketSkill = selectedDetail?.type === 'market' ? selectedDetail.skill : null;

const installedFiltered = installed.filteredSkills;
const installedFiltered = hideDuplicates
? installed.filteredSkills.filter((s) => !s.isShadowed)
: installed.filteredSkills;
const installedTotalPages = Math.max(
1,
Math.ceil(installedFiltered.length / INSTALLED_PAGE_SIZE),
Expand Down Expand Up @@ -300,13 +305,24 @@ const SkillsScene: React.FC = () => {
<span className="skills-split__filter-count">{count}</span>
</button>
))}
<button
type="button"
className={[
'skills-split__filter-chip',
hideDuplicates && 'is-active',
].filter(Boolean).join(' ')}
onClick={() => setHideDuplicates(!hideDuplicates)}
>
<Filter size={13} />
<span>{t('toolbar.hideDuplicates')}</span>
</button>
</div>
<button
type="button"
className="skills-split__add-btn"
onClick={toggleAddForm}
>
<Plus size={14} />
<Plus size={13} />
<span>{t('toolbar.addTooltip')}</span>
</button>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/web-ui/src/app/scenes/skills/skillsSceneStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ interface SkillsSceneState {
searchDraft: string;
marketQuery: string;
installedFilter: InstalledFilter;
hideDuplicates: boolean;
isAddFormOpen: boolean;
setSearchDraft: (value: string) => void;
submitMarketQuery: () => void;
setInstalledFilter: (filter: InstalledFilter) => void;
setHideDuplicates: (hide: boolean) => void;
setAddFormOpen: (open: boolean) => void;
toggleAddForm: () => void;
}
Expand All @@ -18,10 +20,12 @@ export const useSkillsSceneStore = create<SkillsSceneState>((set) => ({
searchDraft: '',
marketQuery: '',
installedFilter: 'all',
hideDuplicates: false,
isAddFormOpen: false,
setSearchDraft: (value) => set({ searchDraft: value }),
submitMarketQuery: () => set((state) => ({ marketQuery: state.searchDraft.trim() })),
setInstalledFilter: (filter) => set({ installedFilter: filter }),
setHideDuplicates: (hide) => set({ hideDuplicates: hide }),
setAddFormOpen: (open) => set({ isAddFormOpen: open }),
toggleAddForm: () => set((state) => ({ isAddFormOpen: !state.isAddFormOpen })),
}));
Original file line number Diff line number Diff line change
Expand Up @@ -750,13 +750,19 @@ export const Markdown = React.memo<MarkdownProps>(({
icon: 'FolderOpen',
onClick: () => handleRevealInExplorer(displayPath || filePath),
},
{
id: 'markdown-copy-file-path',
label: t('markdown.copyFilePath'),
icon: 'Copy',
onClick: () => void handleCopyLink(displayPath || filePath),
},
];

showLinkContextMenu(event, items, 'markdown-local-file-link', {
filePath,
displayPath,
});
}, [handleRevealInExplorer, showLinkContextMenu, t]);
}, [handleRevealInExplorer, handleCopyLink, showLinkContextMenu, t]);

const handleWebLinkContextMenu = useCallback((event: React.MouseEvent<HTMLElement>, url: string) => {
const targetElement = event.currentTarget;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,31 @@
color: var(--color-error);
}

&__error-banner {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 10px 16px;
background: color-mix(in srgb, var(--color-error, #ef4444) 10%, var(--color-bg-secondary));
border-bottom: 1px solid color-mix(in srgb, var(--color-error, #ef4444) 22%, transparent);
flex-shrink: 0;

&-icon {
flex-shrink: 0;
margin-top: 1px;
color: var(--color-error);
}

&-text {
flex: 1;
font-size: 12px;
line-height: 1.5;
color: var(--color-error);
word-break: break-word;
user-select: text;
}
}

&__content {
flex: 1;
display: flex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { FlowTextBlock } from '../FlowTextBlock';
import { FlowToolCard } from '../FlowToolCard';
import { ModelThinkingDisplay } from '../../tool-cards/ModelThinkingDisplay';
import { ToolTimeoutIndicator } from '../../tool-cards/ToolTimeoutIndicator';
import { Button, Tooltip, DotMatrixLoader } from '@/component-library';
import { Button, DotMatrixLoader } from '@/component-library';
import { createLogger } from '@/shared/utils/logger';
import { agentAPI } from '@/infrastructure/api/service-api/AgentAPI';
import type { ReviewerContext } from '@/shared/services/reviewTeamService';
Expand Down Expand Up @@ -542,14 +542,16 @@ export const TaskDetailPanel: React.FC<TaskDetailPanelProps> = ({ data }) => {
<DotMatrixLoader size="small" />
</span>
)}
{isFailed && (
<Tooltip content={getErrorMessage()} placement="bottom">
<AlertCircle size={14} className="task-detail-panel__header-failed" />
</Tooltip>
)}
</div>

<div
{isFailed && (
<div className="task-detail-panel__error-banner">
<AlertCircle size={14} className="task-detail-panel__error-banner-icon" />
<span className="task-detail-panel__error-banner-text">{getErrorMessage()}</span>
</div>
)}

<div
ref={contentRef}
className="task-detail-panel__content"
>
Expand Down
6 changes: 3 additions & 3 deletions src/web-ui/src/flow_chat/components/btw/BtwSessionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,9 @@ export const BtwSessionPanel: React.FC<BtwSessionPanelProps> = ({
completedRemediationIds: store.completedRemediationIds,
});
} else {
// Fix completed with no further remediation needed — dismiss the action bar
// so the user can focus on the fix results in the chat stream.
store.dismiss();
// Fix completed with no further remediation needed — update phase to
// show completion state in the action bar instead of dismissing it.
store.updatePhase('fix_completed');
}
}
return;
Expand Down
Loading
Loading