Skip to content

feat(web-search): ✨ add web search tool with multi-engine support#197

Merged
jorben merged 9 commits into
masterfrom
feta/web-search-support
May 22, 2026
Merged

feat(web-search): ✨ add web search tool with multi-engine support#197
jorben merged 9 commits into
masterfrom
feta/web-search-support

Conversation

@jorben
Copy link
Copy Markdown
Contributor

@jorben jorben commented May 22, 2026

Summary

Add a built-in web search tool with multi-engine support (Tavily, Brave, Exa, Firecrawl), configurable per-engine API keys and base URLs, domain filtering, and a full settings UI in the General settings panel.

Changes

Backend (Rust)

  • New web search executor (web_search.rs): implements tool execution with standardized result format across all four engines, including time-range filtering, domain include/exclude, and country hints
  • New settings module (web_search_settings.rs): persists and loads web search settings from SQLite, with per-engine API key and base URL maps, legacy migration, and sanitization
  • Tool context consolidation (executors/mod.rs): introduces ToolContext struct to bundle workspace path, writable roots, thread ID, terminal manager, and DB pool — simplifies the execute_tool signature and passes the pool needed by web search
  • Conditional tool injection (agent_session_tools.rs): web_search tool is added to default and plan-read-only profiles only when the feature is enabled and an API key is configured; also wired into subagent helper profiles
  • Tool gateway (tool_gateway.rs): updated to pass ToolContext instead of individual parameters
  • Subagent orchestration (orchestrator.rs, runtime_orchestration.rs): propagates web search enabled state to helper agents

Frontend (TypeScript/React)

  • Settings types & store (types.ts, settings-store.ts, defaults.ts): adds WebSearchSettings type with engine, API key status, base URL, max results, and raw content toggle
  • Web search settings model (web-search-settings.ts): handles mapping between persisted and in-memory formats, per-engine API key/base URL normalization, and legacy field migration
  • IPC actions (settings-ipc-actions.ts, settings-ipc-actions.test.ts): adds updateWebSearchSettings with optimistic UI updates and backend sync; includes comprehensive unit tests for per-engine key/URL persistence and migration
  • Settings UI (settings-center-overlay.tsx): adds Web Search section with enable toggle, engine picker, API key input (masked), base URL, max results slider, and raw content switch
  • Profile library cards (settings-center-overlay.tsx): redesigns cards to show model brand icon and compact layout with hover-reveal actions
  • Agent access panel (profile-agent-access.tsx): replaces lock icon with checkbox for always-on agents, improves truncation and layout
  • Agents panel (agents-settings-panel.tsx): adds web_search to tool categories, uses shared error message helper, shows invocation description
  • i18n (en.ts, zh-CN.ts): adds translation keys for all web search settings labels and descriptions
  • Tool names (tool-names.ts): adds web_search to default-collapsed tools list

Test Plan

  • Enable web search in Settings → General, configure API key, verify tool appears in agent sessions
  • Switch engines (Tavily, Brave, Exa, Firecrawl) and confirm per-engine API key/base URL persistence
  • Test domain include/exclude and time range filters per engine
  • Verify web search is hidden when disabled or no API key configured
  • Confirm profile library card redesign renders correctly with model icons
  • Run npm run typecheck and npm run test:unit
  • Run cargo test --locked --manifest-path src-tauri/Cargo.toml

🤖 Generated with TiyCode

jorben added 5 commits May 22, 2026 16:42
…n filtering

Migrate API key and base URL storage from single fields to per-engine maps,
enabling independent configuration for each search engine. Remove legacy
`clearApiKey` action in favor of saving an empty string.

- Store `apiKeys` and `baseUrls` as engine-keyed maps in settings
- Automatically migrate existing single `apiKey`/`baseUrl` on read
- Update agent tool to remove `maxResults`/`includeRawContent` parameters (now exclusively controlled by app settings)
- Improve agent tool descriptions for query and timeRange parameters
- Add Brave domain filtering via `site:`/`-site:` query modifiers
- Add Exa published date range support for timeRange parameter
- Change default `includeRawContent` to `true`
- Remove unused translation keys `alwaysOn` and `off` in profile agent access
- UI: replace lock icon with read-only checkbox for built-in agents, clamp descriptions, clear API key input on engine change
… search

Introduce ToolContext to bundle workspace path, writable roots, thread id,
terminal manager, and database pool, simplifying function signatures across
executors and tool gateway.

Web search improvements:
- Use a static OnceLock HTTP client to avoid recreating per request.
- Remove providerResponse from tool output to reduce payload size.
- Adjust topic_from_time_range to only return "news" for day and week.

Subagent tool selection:
- Conditionally add web_search tool based on user settings.
- Pass a flag to helper_tools to control inclusion.

UI: refine profile library card styling (font sizes, spacing, layout)
and add debounced web search base URL update.
Add logic to transfer subagent access IDs from the source profile to the
new duplicate when cloning a profile. On failure, the created profile is
preserved and a warning is logged, ensuring robustness.

Also replace inline error message construction in agents settings panel
with a shared utility function for consistent invoke error handling.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 22, 2026

AI Code Review Summary

PR: #197 (feat(web-search): ✨ add web search tool with multi-engine support)
Preferred language: English

Overall Assessment

Detected 7 actionable findings, prioritize CRITICAL/HIGH before merge.

Major Findings by Severity

  • MEDIUM (2)
    • src-tauri/src/core/executors/web_search.rs:153 - Missing HTTPS scheme enforcement on custom web search base URLs
    • src/modules/settings-center/ui/settings-center-overlay.tsx:1356 - Silent Rejection of Web Search Settings Promises
  • LOW (5)
    • src-tauri/src/core/executors/web_search.rs:24 - Missing Custom User-Agent Header in HTTP Client
    • src-tauri/src/core/web_search_settings.rs:31 - Exposure of sensitive API keys in WebSearchSettings Debug logs
    • src/modules/settings-center/ui/agents-settings-panel.tsx:357 - Add hover tooltip for truncated editor error messages
    • src/modules/settings-center/ui/profile-agent-access.tsx:149 - Identical Styling for Interactive and Non-Interactive Agents
    • src/modules/settings-center/ui/settings-center-overlay.tsx:1138 - Trim Web Search API Key before saving

Actionable Suggestions

  • Configure a custom User-Agent on the reqwest ClientBuilder inside src-tauri/src/core/executors/web_search.rs to maintain high reliability across API providers, Buddy.
  • Enforce HTTPS-only connections on the shared HTTP client or validate custom base URL schemes to prevent unencrypted transmissions of keys.
  • Redact credentials from being formatted in debug logging by implementing manual Debug for WebSearchSettings or using a wrapper type.
  • Ensure backend/Rust handlers (specifically for web search tool integration) correctly parse the new multi-engine schema fields from web_search.settings.
  • Verify that the frontend UI elements (e.g., in Settings Center) properly bind to this store and trigger updateWebSearchSettings on user configuration changes.
  • Trim the Web Search API key to avoid authentication failures due to accidental trailing copy-paste whitespace: webSearchApiKey.trim().
  • Add native tooltip support (via title attribute) on truncated UI elements like the editorErrorMessage.
  • Buddy, add error-handling to the Web Search update actions in 'settings-center-overlay.tsx' to avoid ignoring rejected promises with 'void'.
  • Reinstate a distinction between system-level permissions and user-custom subagents inside 'ProfileAgentAccess'. Keep the read-only checkbox visibly distinct, or label it as 'Always Allowed' rather than identical to the user-enabled ones.

Potential Risks

  • Possibility of search engine blocking default reqwest connections over time if many parallel requests are fired without a distinctive User-Agent header.
  • Interception of critical API keys due to unencrypted HTTP transmission.
  • Accidental exposure of secrets in crash reports, local log files, or terminal diagnostics.
  • Users pasting API keys with trailing whitespaces or carriage return characters, resulting in API authentication failures that are difficult to debug visually.
  • Web Search custom base URL might contain invalid or unvalidated inputs which are directly passed to the backend, causing run-time tool failures.
  • Automation tests built on top of checkbox elements will trigger errors when attempting to interact with the pointer-events-none input elements on system agents.

Test Suggestions

  • Consider adding standard unit tests that feed malformed JSON responses from external services into parser functions (map_exa_result, etc.) to verify no unexpected panics can occur on invalid engine states.
  • Implement an integration test ensuring that attempts to configure or use an unencrypted http:// base URL result in a validation error or request refusal.
  • Run backend integration tests to ensure that the Rust-side settings store reads and handles the migrated apiKeys and baseUrls objects correctly.
  • Perform manual QA by configuring Tavily and Brave keys sequentially, verifying that switching engines retrieves and uses the correct cached API key without leaking details to incorrect engine endpoints.
  • Add a unit test in Vitest to verify that setting/clearing API key states in GeneralSettingsPanel reacts correctly when changing the selected engine.
  • Verify with keyboard-only navigation that selecting/renaming profile cards triggers/hides the actions overlay correctly on focus/blur.
  • Verify component unmount safety on the 'settings-center-overlay.tsx' when blurred input fields trigger saving actions while switching tabs.
  • Verify range clamping bounds for the 'maxResults' settings input.

File-Level Coverage Notes

  • src-tauri/src/core/agent_session.rs: Safe. Properly handles initial state checking and configures runtime tools conditionally. (Buddy, this integration looks clean and aligns well with standard practices.)
  • src-tauri/src/core/agent_session_tools.rs: Safe. Schema definitions and conditional profile checks are secure and well-tested. (Buddy, the conditional addition tests in this file are excellent and robust.)
  • src-tauri/src/core/executors/mod.rs: Safe. Successfully encapsulates execution properties into a unified ToolContext structure. (Clean signature changes, Buddy.)
  • src-tauri/src/core/executors/web_search.rs: Contains secure limits on responses and queries, but allows unencrypted custom base URLs which could leak secrets. (Buddy, excellent defense-in-depth on maximum response size limits to prevent zip bomb attacks.)
  • src-tauri/src/core/mod.rs: Safe module declaration. (Simple module export, Buddy.)
  • src-tauri/src/core/subagent/orchestrator.rs: Safe. Propagates correct web search state to the helper orchestration pipeline. (Clean propagation logic, Buddy.)
  • src-tauri/src/core/subagent/runtime_orchestration.rs: Safe. Correctly exposes web search capability to custom subagent profiles. (Tests cover conditional inclusion successfully, Buddy.)
  • src-tauri/src/core/tool_gateway.rs: Safe. Minor adjustment to pass context down safely. (No security issues here, Buddy.)
  • src-tauri/src/core/web_search_settings.rs: Safe representation, but derives Debug which prints plain-text secrets. (Buddy, implementing a custom Debug representation is highly recommended here.)
  • src-tauri/src/persistence/repo/custom_subagent_repo.rs: Safe. Just removed an unused variable statement. (Clean modification, Buddy.)
  • src/modules/settings-center/model/defaults.ts: Perfect. Correctly adds default settings for web search without unnecessarily bumping SETTINGS_STORAGE_SCHEMA_VERSION (which only tracks UI localStorage), adhering to repo guidelines. (Aligned with the new separation of local UI and backend/file-backed settings.)
  • src/modules/settings-center/model/settings-hydration.test.ts: Excellent. Thoroughly covers failure/fallback paths to verify resilience of the overall settings hydration process when fetching web search settings from the backend. (Ensures that a failure to retrieve web search settings doesn't block loading of other settings.)
  • src/modules/settings-center/model/settings-hydration.ts: Robust. Integrates backend-backed web search settings fetching via settingsGet and safely recovers from potential read failures. (Optimally hydrated alongside other phase-2 settings.)
  • src/modules/settings-center/model/settings-ipc-actions.test.ts: Comprehensive. The unit tests exhaustively cover multi-engine state changes, API key hiding, legacy migrations, rollback upon errors, and profile subagent access duplication. (Zero gap in verification.)
  • src/modules/settings-center/model/settings-ipc-actions.ts: High quality. Seamlessly implements optimistic updates, rollback behavior, and also duplicates custom subagent access to duplicated agent profiles. (The error rollback mechanism prevents the UI state from diverging from backend persistence upon write failure.)
  • src/modules/settings-center/model/settings-storage.test.ts: Perfect. Confirms that web search settings are excluded from local storage UI persistence, preserving the privacy/security of API keys and matching guidelines. (No sensitive settings are leaked to localStorage.)
  • src/modules/settings-center/model/settings-store.ts: Clean. Correctly typed and initialized with default values. (Integrates webSearch state into the global settingsStore.)
  • src/modules/settings-center/model/types.ts: Clean. Introduces well-defined type signatures for WebSearchEngine, WebSearchSettings, and associated settings structures. (Ensures strong TypeScript compilation guarantees.)
  • src/modules/settings-center/model/web-search-settings.ts: Exemplary. Solid data normalization, defensive handling of unknown/corrupt input types, and robust backward compatibility migration for legacy single-engine schema versions. (The reduction only over defined WEB_SEARCH_ENGINES avoids prototype pollution issues.)
  • src/shared/constants/tool-names.ts: Clean. Properly adds 'web_search' to standard collapsed tools list. (Maintains consistent terminal/tool panel UX.)
  • ... and 7 more file-level entries.

Inline Downgraded Items (processed but not inline)

  • None

Coverage Status

  • Target files: 27
  • Covered files: 27
  • Uncovered files: 0
  • No-patch/binary covered as file-level: 0
  • Findings with unknown confidence (N/A): 0

Uncovered list:

  • None

No-patch covered list:

  • None

Runtime/Budget

  • Rounds used: 1/4
  • Planned batches: 3
  • Executed batches: 3
  • Sub-agent runs: 5
  • Planner calls: 1
  • Reviewer calls: 6
  • Model calls: 7/64
  • Structured-output summary-only degradation: NO

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 13
  • Findings with unknown confidence: 0
  • Inline comments attempted: 13
  • Target files: 26
  • Covered files: 26
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

insert_string(&mut body, "country", country);
}

let value = post_json(client.post(endpoint).bearer_auth(api_key).json(&body)).await?;

This comment was marked as outdated.

response_json(builder.send().await).await
}

async fn response_json(

This comment was marked as outdated.

)
})?;
let status = response.status();
let body = response.text().await.map_err(|error| {

This comment was marked as outdated.

}
}

fn map_tavily_result(value: &Value) -> StandardSearchResult {

This comment was marked as outdated.

}

fn parse_input(input: &Value, settings: &WebSearchSettings) -> Result<WebSearchInput, AppError> {
let query = input

This comment was marked as outdated.


const HTTP_TIMEOUT_SECS: u64 = 30;

static HTTP_CLIENT: OnceLock<reqwest::Client> = OnceLock::new();

This comment was marked as outdated.

}
}

pub async fn load_web_search_settings(pool: &SqlitePool) -> Result<WebSearchSettings, AppError> {

This comment was marked as outdated.

return optimistic;
}

try {

This comment was marked as outdated.

}));
}

export async function updateWebSearchSettings(patch: WebSearchSettingsPatch) {

This comment was marked as outdated.

</span>

<div
className={cn(

This comment was marked as outdated.

- Add MAX_QUERY_CHARS (500) and MAX_RESPONSE_BYTES (5MB) constants
- Validate web search query length before making request
- Check HTTP response size to prevent large payloads
- Move Tavily API key from bearer auth to request body
- Fix http_error function to accept &str instead of String
- Update settings overlay to debounce max results value with onBlur
- Add local state for max results to prevent premature saves
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 14
  • Findings with unknown confidence: 0
  • Inline comments attempted: 14
  • Target files: 26
  • Covered files: 26
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

profileList(),
promptCommandList(),
customSubagentList(),
settingsGet(WEB_SEARCH_SETTINGS_KEY),

This comment was marked as outdated.

)
})?;
let status = response.status();
let raw_body = response.bytes().await.map_err(|error| {

This comment was marked as outdated.

)
})?;
let status = response.status();
let raw_body = response.bytes().await.map_err(|error| {

This comment was marked as outdated.

)
})?;
let status = response.status();
let raw_body = response.bytes().await.map_err(|error| {

This comment was marked as outdated.

[],
);

const handleSaveWebSearchApiKey = async () => {

This comment was marked as outdated.

)
.then(async (profile) => {
const mapped = mapProfileDto(profile);
try {

This comment was marked as outdated.

}));
}

export async function updateWebSearchSettings(patch: WebSearchSettingsPatch) {

This comment was marked as outdated.

const optimistic: WebSearchSettings = {
...current,
...patch,
hasApiKey: Object.prototype.hasOwnProperty.call(patch, "apiKey")

This comment was marked as outdated.

}
};

useEffect(() => {

This comment was marked as outdated.

className="w-full md:w-[360px]"
onChange={(event) => setWebSearchBaseUrl(event.target.value)}
onBlur={() => {
const trimmed = webSearchBaseUrl.trim();

This comment was marked as outdated.

Add localized error handling for slug conflicts when creating or saving custom agents. The application now detects the `custom_subagent.slug_conflict` error code and displays a specific translation with the conflicting slug, improving user feedback.

Split error state into page-level (`pageErrorMessage`) and editor-level (`editorErrorMessage`) to show errors in their correct context. The editor now displays inline error messages near the header, with a dismiss button, while global errors like create/delete failures remain at the bottom. This provides clearer error attribution.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 10
  • Findings with unknown confidence: 0
  • Inline comments attempted: 10
  • Target files: 26
  • Covered files: 26
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

const mapped = mapPersistedWebSearchSettings(persisted);
settingsStore.setState({ webSearch: mapped });
return mapped;
} catch (error) {

This comment was marked as outdated.

)
})?;
let status = response.status();
let raw_body = response.bytes().await.map_err(|error| {

This comment was marked as outdated.

)
})?;
let status = response.status();
let raw_body = response.bytes().await.map_err(|error| {

This comment was marked as outdated.

});
});

describe("updateWebSearchSettings", () => {

This comment was marked as outdated.

@@ -0,0 +1,165 @@
import type { WebSearchEngine, WebSearchSettings } from "@/modules/settings-center/model/types";

This comment was marked as outdated.

placeholder={webSearch.hasApiKey ? "••••••••••••" : t("settings.general.webSearchApiKeyPlaceholder")}
onChange={(event) => setWebSearchApiKey(event.target.value)}
/>
<Button

This comment was marked as outdated.

),
));
}
let body = String::from_utf8_lossy(&raw_body);

This comment was marked as outdated.

}
}

fn brave_query(query: &str, include_domains: &[String], exclude_domains: &[String]) -> String {

This comment was marked as outdated.

const optimistic: WebSearchSettings = {
...current,
...patch,
hasApiKey: Object.prototype.hasOwnProperty.call(patch, "apiKey")

This comment was marked as outdated.

Improve robustness of web search executor by streaming response body
with size limits and sanitizing domain filters to prevent injection
attacks. Enhance settings UI to display API key save errors and add
a clear button when a key exists.

- Refactor response reading to use streaming chunks with early size
  validation, preventing large responses from being fully buffered.
- Add domain filter sanitization to strip schemes, ports, paths, and
  invalid characters, reducing risk of query manipulation.
- Correct UI button label to show "Clear saved key" when a key is
  stored and the input is empty.
- Display backend save failures as user-visible error messages using
  the new i18n `webSearchApiKeySaveError` translation.
- Update `updateWebSearchSettings` to clear optimistic `hasApiKey`
  and `baseUrl` when switching engines, and to reject the promise on
  persistence failure, enabling proper rollback and error display.
- Add test coverage for rollback on read/write failures and engine
  switch logic.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 12
  • Findings with unknown confidence: 0
  • Inline comments attempted: 12
  • Target files: 26
  • Covered files: 26
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

...pendingCommands,
],
customSubagents: customSubagents ?? [],
webSearch: mapPersistedWebSearchSettings(webSearchSetting?.value),

This comment was marked as outdated.


try {
const existing = await settingsGet(WEB_SEARCH_SETTINGS_KEY);
const persisted = buildPersistedWebSearchSettings(

This comment was marked as outdated.

<span className="mt-2 block text-[12px] leading-5 text-app-muted">
<span
className="mt-2 line-clamp-2 text-[12px] leading-5 text-app-muted"
title={agent.invocationDescription.trim() || t("settings.profileAgentAccess.noDescription")}

This comment was marked as outdated.

profileList(),
promptCommandList(),
customSubagentList(),
settingsGet(WEB_SEARCH_SETTINGS_KEY),

This comment was marked as outdated.

}

try {
const existing = await settingsGet(WEB_SEARCH_SETTINGS_KEY);

This comment was marked as outdated.

}));
}

export async function updateWebSearchSettings(patch: WebSearchSettingsPatch) {

This comment was marked as outdated.

.then(async (profile) => {
const mapped = mapProfileDto(profile);
try {
const subagentAccessIds = await profileSubagentAccessGet(source.id);

This comment was marked as outdated.


const isClearingWebSearchApiKey = webSearch.hasApiKey && !webSearchApiKey.trim();

useEffect(() => {

This comment was marked as outdated.

onChange={(event) => {
const nextValue = Number(event.target.value);
if (Number.isFinite(nextValue)) {
setWebSearchMaxResults(clampWebSearchMaxResults(nextValue));

This comment was marked as outdated.

canDelete={agentProfiles.length > 1}
primaryModelLabel={getProfilePrimaryModelLabel(profile)}
thinkingLevelLabel={getProfileThinkingLevelLabel(profile)}
primaryModel={getProfilePrimaryModelSummary(profile)}

This comment was marked as outdated.

…I state bugs

Added error handling in settings hydration to catch Web Search fetch failures, preventing them from blocking other phase-2 data.

Fixed useEffect dependencies for webSearch fields (engine, baseUrl, maxResults) to avoid stale state and incorrect resets.

Allowed empty input for maxResults to improve user experience when editing.

Added test to verify graceful degradation when Web Search settings hydration fails.
@jorben jorben merged commit c16f62b into master May 22, 2026
4 checks passed
@jorben jorben deleted the feta/web-search-support branch May 22, 2026 17:28
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 7
  • Findings with unknown confidence: 0
  • Inline comments attempted: 7
  • Target files: 27
  • Covered files: 27
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

api_key: &str,
input: &WebSearchInput,
) -> Result<ToolOutput, AppError> {
let endpoint = settings
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Missing HTTPS scheme enforcement on custom web search base URLs

Custom base URLs configured for search engines (Tavily, Brave, Exa, Firecrawl) do not enforce the secure HTTPS protocol. If a user or an attacker manipulates the base URL to use 'http://', the app will transmit sensitive API keys in plain text over the network, exposing them to interception.

Suggestion: Validate that any custom configured base URL starts with 'https://' before making requests, or configure the shared reqwest::Client with .https_only(true) in http_client() to prevent unencrypted requests automatically.

Risk: Exposure of third-party API keys (Tavily, Brave, Exa, Firecrawl) over unencrypted connections if configured with custom HTTP endpoints.

Confidence: 0.85

[From SubAgent: security]

const clamped = clampWebSearchMaxResults(draftValue);
setWebSearchMaxResults(clamped);
if (clamped !== webSearch.maxResults) {
void onUpdateWebSearchSettings({ maxResults: clamped });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Silent Rejection of Web Search Settings Promises

Buddy, when web search settings (such as maxResults, baseUrl, enabled, or includeRawContent) are changed, the app invokes 'onUpdateWebSearchSettings' and discards the Promise with the 'void' operator. If the backend command fails, the frontend UI state will reflect the updated state temporarily but become desynchronized with the actual persistent backend settings.

Suggestion: Handle potential errors from the 'onUpdateWebSearchSettings' promise. Display a toast or set an error state when the update fails, and revert the UI state back to the previous prop values on failure.

Risk: Silent save failures where users believe settings were updated, causing unexpected application behavior and hard-to-reproduce test failures in integration or end-to-end environments.

Confidence: 0.95

[From SubAgent: testing]


fn http_client() -> &'static reqwest::Client {
HTTP_CLIENT.get_or_init(|| {
reqwest::Client::builder()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Missing Custom User-Agent Header in HTTP Client

The shared HTTP client is initialized without a custom User-Agent header.

Suggestion: Add a descriptive .user_agent("tiy-agent/1.0") or similar unique identifier to the reqwest builder.

Risk: Some search API gateways or scrapers (such as Firecrawl/Brave) aggressively rate-limit or outright reject requests utilizing the default reqwest user agent to prevent automated abuse.

Confidence: 0.90

[From SubAgent: general]

}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Exposure of sensitive API keys in WebSearchSettings Debug logs

The WebSearchSettings struct derives the Debug trait automatically. This includes the api_keys map and api_key fields in the generated string representation. If this struct is ever formatted in debug logging, error messages, or telemetry, the API keys will be exposed.

Suggestion: Implement the Debug trait manually for WebSearchSettings and redact the sensitive keys, or wrap the API keys in a helper type that implements a secure, redacted Debug representation.

Risk: Accidental exposure of sensitive API keys in log outputs or telemetry data.

Confidence: 0.90

[From SubAgent: security]

{editorErrorMessage ? (
<div className="flex min-w-[240px] flex-1 items-center gap-2 py-0.5 text-[14px] font-semibold text-app-error">
<AlertTriangle className="size-4 shrink-0" />
<span className="min-w-0 truncate">{editorErrorMessage}</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Add hover tooltip for truncated editor error messages

The inline editor error message utilizes Tailwind's truncate utility to fit inside a single line. If the error text is long, users won't be able to read the full context.

Suggestion: Add a title={editorErrorMessage} attribute to the span so that users can hover over the truncated text to view the complete error message.

Risk: Important details of a subagent save/update error could be cut off, making it difficult for the user to troubleshoot formatting or validation errors.

Confidence: 0.90

[From SubAgent: general]

<p className="text-[13px] font-medium text-app-foreground">{name}</p>
<span className="rounded-full border border-app-info/30 bg-app-info/10 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-[0.08em] text-app-info">
{t("settings.profileAgentAccess.alwaysOn")}
<span className="rounded-full border border-app-success/30 bg-app-success/10 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-[0.08em] text-app-success">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Identical Styling for Interactive and Non-Interactive Agents

Buddy, the built-in agents (which are always enabled and immutable) now display the identical 'Allowed' badge and checked checkbox visual style as enabled custom agents. However, unlike custom agents, the checkbox for built-in agents is completely read-only and non-interactive.

Suggestion: Differentiate immutable system permissions from user-configurable permissions. Keep the lock icon or mark built-in agents clearly with an 'Always Allowed' or 'System' label, and add 'aria-disabled="true"' to the non-interactive checkboxes.

Risk: End-users will be confused as to why clicking certain checkboxes has no effect, and automated visual regression or accessibility testing scripts will struggle to distinguish interactive versus non-interactive states.

Confidence: 0.90

[From SubAgent: testing]

setWebSearchApiKeyError(null);
setIsSavingWebSearchKey(true);
try {
await onUpdateWebSearchSettings({ apiKey: webSearchApiKey });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Trim Web Search API Key before saving

The input text is saved as-is without trimming leading or trailing whitespaces. If a user accidentally copies a trailing space or newline with their API key, it will be saved and might cause the search engine query requests to fail authentication.

Suggestion: Trim the API key string before calling the update handler, for example: await onUpdateWebSearchSettings({ apiKey: webSearchApiKey.trim() });.

Risk: A user could paste an API key with trailing whitespace, leading to persistent, hard-to-debug search authentication errors.

Confidence: 0.95

[From SubAgent: general]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant