-
Output
-
-
+ >
+ }
+ aside={
+ <>
+ {failed && failure ? (
+
+
-
- ) : null}
-
-
+ ) : null}
+ {footer ?
{footer}
: null}
+ {/* Search results are the call's whole point: visible without
+ opening the disclosure, as in assistant-ui's own web-search. */}
+ {searchBody ?
{searchBody}
: null}
+ >
+ }>
+ {richBody ?
{richBody}
: undefined}
+
);
}
+function openExternal(url: string): void {
+ void openUrl(url).catch(() => undefined);
+}
+
/**
* Terminal status carried inside a settled tool part's `result`.
*
@@ -299,6 +247,10 @@ function toolStatusEnvelope(
* to destructure four fields and drop the rest, which is why a parked call
* rendered as an ordinary running one with no way to answer it.
*
+ * The part's `artifact` carries what the core said about the call (its label
+ * for a dynamic tool, the duration, a structured result). The adapter used to
+ * drop all of it, so the card guessed a label from the tool name.
+ *
* The decision surface itself is passed in rather than built here. It is
* `ApprovalRequestCard`, which needs the thread id and the store's
* `PendingApproval` — neither of which belongs in this file, and both of which
@@ -311,6 +263,7 @@ export const OpenHumanToolCall: FC<
}
> = props => {
const envelope = toolStatusEnvelope(props.result);
+ const artifact = readOpenHumanToolArtifact(props.artifact);
return (
diff --git a/app/src/features/conversations/components/ChatToolParts.test.tsx b/app/src/features/conversations/components/ChatToolParts.test.tsx
index f2a0180304d..03f917bf679 100644
--- a/app/src/features/conversations/components/ChatToolParts.test.tsx
+++ b/app/src/features/conversations/components/ChatToolParts.test.tsx
@@ -158,7 +158,7 @@ describe('ChatToolParts', () => {
expect(screen.getByTestId('assistant-ui-tool-call')).toHaveTextContent('Searched the web');
await userEvent.click(screen.getByRole('button', { name: /Searched the web/ }));
- expect(screen.getByText(/Lean open conjectures/)).toBeInTheDocument();
+ expect(screen.getAllByText(/Lean open conjectures/).length).toBeGreaterThan(0);
expect(screen.queryByText('Query', { exact: true })).not.toBeInTheDocument();
expect(screen.getByText('Found 12 candidate problems')).toBeInTheDocument();
});
@@ -179,12 +179,16 @@ describe('ChatToolParts', () => {
/>
);
- await userEvent.click(screen.getByRole('button', { name: /Fetched from the web/ }));
+ await userEvent.click(screen.getByRole('button', { name: /Read webpage/ }));
expect(screen.getByRole('strong')).toHaveTextContent('Example Domain');
expect(screen.queryByText('Content', { exact: true })).not.toBeInTheDocument();
});
- it('infers web search labels when a persisted tool name degraded to tool', () => {
+ // The old card called any call with a `query` argument "Searched the web",
+ // which mislabelled memory, tool and email searches. A call whose name
+ // degraded to `tool` is now labelled as what is known about it: an
+ // unnamed tool, with its query as the chip.
+ it('does not guess a web search from a query argument alone', () => {
render(
{
/>
);
- expect(screen.getByTestId('assistant-ui-tool-call')).toHaveTextContent('Searched the web');
- expect(screen.getByTestId('assistant-ui-tool-call')).not.toHaveTextContent(/^Tool done$/);
+ const card = screen.getByTestId('assistant-ui-tool-call');
+ expect(card).not.toHaveTextContent('Searched the web');
+ expect(card).toHaveTextContent('Used tool');
+ expect(card).toHaveTextContent('latest world news');
});
it('names the tool-discovery bridge for what it is, not a web search', () => {
@@ -225,7 +231,7 @@ describe('ChatToolParts', () => {
/>
);
- expect(screen.getByTestId('assistant-ui-tool-call')).toHaveTextContent('Found the right tool');
+ expect(screen.getByTestId('assistant-ui-tool-call')).toHaveTextContent('Found tools');
// The regression this pins: BOTH heuristic legs still match this row, so
// dropping the explicit branch renders the web label again.
expect(screen.getByTestId('assistant-ui-tool-call')).not.toHaveTextContent('Searched the web');
@@ -251,11 +257,12 @@ describe('ChatToolParts', () => {
);
// Composio slugs carry no separator inside the toolkit name, so this also
- // pins the `googlecalendar` spelling in `KNOWN_TOOLKIT_RE`: without it the
- // row degrades to the raw "GOOGLECALENDAR EVENTS LIST".
- expect(screen.getByTestId('assistant-ui-tool-call')).toHaveTextContent(
- 'Google Calendar: Events list'
- );
+ // pins the `googlecalendar` catalog lookup: without it the row degrades to
+ // the raw "GOOGLECALENDAR EVENTS LIST".
+ const card = screen.getByTestId('assistant-ui-tool-call');
+ expect(card).toHaveTextContent('Used Google Calendar');
+ expect(card).toHaveTextContent('Events list');
+ expect(card).not.toHaveTextContent('GOOGLECALENDAR');
expect(screen.getByTestId('assistant-ui-tool-call')).not.toHaveTextContent('Tool Call');
});
});
diff --git a/app/src/features/conversations/components/ChatToolParts.tsx b/app/src/features/conversations/components/ChatToolParts.tsx
index 478b2ad40d1..8c09b5b02c3 100644
--- a/app/src/features/conversations/components/ChatToolParts.tsx
+++ b/app/src/features/conversations/components/ChatToolParts.tsx
@@ -1,15 +1,23 @@
import {
+ type AssistantState,
type ToolCallMessagePart,
type ToolCallMessagePartComponent,
useAui,
+ useAuiState,
} from '@assistant-ui/react';
-import { useCallback } from 'react';
+import { type FC, type PropsWithChildren, useCallback, useMemo } from 'react';
+import { ToolTimeline } from '../../../components/assistant-ui/elements/tool-timeline';
+import type { ThreadGroupPart } from '../../../components/assistant-ui/thread';
import ApprovalRequestCard from '../../../components/chat/ApprovalRequestCard';
import IntegrationConnectCard from '../../../components/chat/IntegrationConnectCard';
+import { useT } from '../../../lib/i18n/I18nContext';
+import { readOpenHumanToolArtifact } from '../../../providers/assistantUiMessages';
import { useAuiThreadId } from '../../../providers/AssistantUiRuntimeProvider';
import type { PendingApproval, SubagentActivity } from '../../../store/chatRuntimeSlice';
import { useAppSelector } from '../../../store/hooks';
+import { summarizeToolCalls } from '../../../utils/toolTimelineFormatting';
+import { describeToolCall, toolLabel } from '../tools/toolPresentation';
import { AssistantUiSubagentCall, isActiveSubagentStatus } from './AssistantUiSubagentCall';
import { isApprovalPending, OpenHumanToolCall } from './AssistantUiToolCall';
import { useSubagentDrawerHost } from './aui/subagentDrawerHost';
@@ -204,3 +212,74 @@ export const ChatToolFallback: ToolCallMessagePartComponent = props => {
if (props.toolName === COMPOSIO_CONNECT_TOOL) return ;
return ;
};
+const NO_PARTS: readonly never[] = [];
+// `optional`: a group rendered outside a message (tests, previews) has no
+// message scope, and reading `state.message` there throws.
+const selectMessageParts = (state: AssistantState) => state.optional.message?.parts ?? NO_PARTS;
+
+/**
+ * The chat's tool timeline: a run of adjacent tool calls under assistant-ui's
+ * tool-timeline element.
+ *
+ * Its label shimmers with what is happening now ("Searching the web") while
+ * the run is in flight, and swaps to a summary once it settles ("5 steps ·
+ * Read file ×3, Searched the web ×2"). Each step is a full tool-call element.
+ * A lone call needs no header over itself, so it renders bare.
+ */
+export const ChatToolGroup: FC> = ({
+ group,
+ children,
+}) => {
+ const { t } = useT();
+ const parts = useAuiState(selectMessageParts);
+ const running = group.status.type === 'running';
+ const presentations = useMemo(
+ () =>
+ group.indices
+ .map(index => parts[index])
+ .filter(part => part?.type === 'tool-call')
+ .map(part => toolPartPresentation(part as unknown as ToolCallMessagePart)),
+ [group.indices, parts]
+ );
+ if (group.indices.length <= 1) {
+ return {children}
;
+ }
+ const active = [...presentations].reverse().find(p => p.tense === 'active');
+ return (
+
+ {children}
+
+ );
+};
+
+/** Resolve a raw assistant-ui tool part (status packed into `result`). */
+function toolPartPresentation(part: ToolCallMessagePart) {
+ const result = part.result as { status?: unknown } | undefined;
+ const envelopeStatus =
+ result && typeof result === 'object' && !Array.isArray(result) ? result.status : undefined;
+ const status =
+ envelopeStatus === 'error' || envelopeStatus === 'cancelled'
+ ? envelopeStatus
+ : part.result === undefined
+ ? 'running'
+ : 'success';
+ if (part.toolName === 'task') {
+ const args = part.args as { subagent_type?: unknown } | undefined;
+ const agent = typeof args?.subagent_type === 'string' ? args.subagent_type : 'subagent';
+ return describeToolCall({ name: `subagent:${agent}`, status });
+ }
+ const artifact = readOpenHumanToolArtifact(part.artifact);
+ return describeToolCall({
+ name: part.toolName,
+ args: part.args,
+ status,
+ serverLabel: artifact?.displayName,
+ serverDetail: artifact?.detail,
+ });
+}
diff --git a/app/src/features/conversations/components/PastTurnInsights.tsx b/app/src/features/conversations/components/PastTurnInsights.tsx
index 6389ed70e56..f8f5bc9bd62 100644
--- a/app/src/features/conversations/components/PastTurnInsights.tsx
+++ b/app/src/features/conversations/components/PastTurnInsights.tsx
@@ -1,3 +1,4 @@
+import { useT } from '../../../lib/i18n/I18nContext';
import type { ProcessingTranscriptItem, ToolTimelineEntry } from '../../../store/chatRuntimeSlice';
import { formatTimelineEntry } from '../../../utils/toolTimelineFormatting';
import { AssistantUiSubagentCall } from './AssistantUiSubagentCall';
@@ -27,6 +28,7 @@ export function PastTurnInsights({
entries: ToolTimelineEntry[];
transcript: ProcessingTranscriptItem[];
}) {
+ const { t } = useT();
// No reasoning/narration trail persisted (legacy snapshot): render the
// tool-only timeline, which already nests each sub-agent's activity inline.
if (transcript.length === 0) {
@@ -49,7 +51,7 @@ export function PastTurnInsights({
{subagentEntries.map(entry => (
- {formatTimelineEntry(entry).title}
+ {formatTimelineEntry(entry, t).title}
diff --git a/app/src/features/conversations/components/ProcessingTranscriptView.tsx b/app/src/features/conversations/components/ProcessingTranscriptView.tsx
index 465a4ed4381..a3c885cfcc5 100644
--- a/app/src/features/conversations/components/ProcessingTranscriptView.tsx
+++ b/app/src/features/conversations/components/ProcessingTranscriptView.tsx
@@ -9,11 +9,11 @@ import type {
} from '../../../store/chatRuntimeSlice';
import {
buildProcessingBlocks,
- categorizeTool,
formatTimelineEntry,
+ presentTimelineEntry,
stripToolCallEnvelopes,
- type ToolCategory,
} from '../../../utils/toolTimelineFormatting';
+import { ToolIcon } from '../tools/ToolIcon';
import { ToolFailureLines } from './ToolFailureLines';
/**
@@ -58,7 +58,8 @@ export function ProcessingTranscriptView({
*/
renderSubagent?: (subagent: NonNullable) => React.ReactNode;
}) {
- const blocks = buildProcessingBlocks(transcript, entries);
+ const { t } = useT();
+ const blocks = buildProcessingBlocks(transcript, entries, t);
if (blocks.length === 0) return null;
return (
@@ -185,12 +186,13 @@ function ToolRow({
entry: ToolTimelineEntry;
renderSubagent?: (subagent: NonNullable) => React.ReactNode;
}) {
- const { title, detail } = formatTimelineEntry(entry);
+ const { t } = useT();
+ const { title, detail } = formatTimelineEntry(entry, t);
return (
-
+
{title}
@@ -225,47 +227,3 @@ function StatusGlyph({ status }: { status: ToolTimelineEntryStatus }) {
}
return ✓ ;
}
-
-/** Minimal monochrome glyph per tool category (inherits `currentColor`). */
-function CategoryIcon({ category }: { category: ToolCategory }) {
- const common = { width: 12, height: 12, viewBox: '0 0 12 12', 'aria-hidden': true } as const;
- switch (category) {
- case 'search':
- return (
-
-
-
-
- );
- case 'run':
- return (
-
-
-
-
- );
- case 'fetch':
- case 'browse':
- return (
-
-
-
-
- );
- case 'write':
- return (
-
-
-
-
- );
- case 'read':
- default:
- return (
-
-
-
-
- );
- }
-}
diff --git a/app/src/features/conversations/components/ToolTimelineBlock.tsx b/app/src/features/conversations/components/ToolTimelineBlock.tsx
index b768ddd0975..e4ebe5523a0 100644
--- a/app/src/features/conversations/components/ToolTimelineBlock.tsx
+++ b/app/src/features/conversations/components/ToolTimelineBlock.tsx
@@ -433,7 +433,7 @@ export function ToolTimelineBlock({
) : (
{rows.map(({ entry, count }, index) => {
- const formatted = formatTimelineEntry(entry);
+ const formatted = formatTimelineEntry(entry, t);
const detailContent =
normalizeToolBody(formatted.detail) ?? normalizeToolBody(entry.argsBuffer);
const workerRef = parseWorkerThreadRef(formatted.detail ?? entry.detail);
diff --git a/app/src/features/conversations/components/__tests__/AgentProcessSourcePanel.test.tsx b/app/src/features/conversations/components/__tests__/AgentProcessSourcePanel.test.tsx
index fd0f5fd301e..677628dde3f 100644
--- a/app/src/features/conversations/components/__tests__/AgentProcessSourcePanel.test.tsx
+++ b/app/src/features/conversations/components/__tests__/AgentProcessSourcePanel.test.tsx
@@ -160,7 +160,7 @@ describe('AgentProcessSourcePanel', () => {
expect(screen.getByText('Let me check both docs first.')).toBeInTheDocument();
expect(screen.getByText('Now I can see what is missing.')).toBeInTheDocument();
// The two consecutive reads collapse into one human-summarized group.
- expect(screen.getByText('Read 2 files')).toBeInTheDocument();
+ expect(screen.getByText('2 steps · Read file ×2')).toBeInTheDocument();
expect(screen.getByTestId('processing-transcript')).toBeInTheDocument();
});
@@ -226,7 +226,7 @@ describe('AgentProcessSourcePanel', () => {
/>
);
// Header shows the step's label, not the generic title.
- expect(screen.getByText('Researching')).toBeInTheDocument();
+ expect(screen.getByText('Researched')).toBeInTheDocument();
// Only the scoped step's activity renders…
openFirstSubagent();
expect(screen.getByTestId('subagent-activity').textContent).toContain('scoped thought');
@@ -248,7 +248,7 @@ describe('AgentProcessSourcePanel', () => {
renderPanel(
{}} />
);
- expect(screen.getByText('Run Code')).toBeInTheDocument();
+ expect(screen.getByText('Ran code')).toBeInTheDocument();
expect(screen.getByText(/All checks passed/)).toBeInTheDocument();
expect(screen.queryByText(/pnpm test/)).toBeNull();
});
diff --git a/app/src/features/conversations/components/__tests__/SubagentDrawer.test.tsx b/app/src/features/conversations/components/__tests__/SubagentDrawer.test.tsx
index 0a9ce2dd014..db5327e9614 100644
--- a/app/src/features/conversations/components/__tests__/SubagentDrawer.test.tsx
+++ b/app/src/features/conversations/components/__tests__/SubagentDrawer.test.tsx
@@ -348,7 +348,7 @@ describe('SubagentDrawer', () => {
expect(screen.queryByTestId('assistant-ui-tool-output')).toBeNull();
});
- it('derives a search label from the arguments when the server label degraded to "tool"', () => {
+ it('keeps a degraded "tool" row readable from its arguments, without guessing a web search', () => {
// A provider that hands back a generic `tool` name leaves the row with
// nothing better than "Tool" unless the arguments are there to read. Those
// arguments only survive a reload because the snapshot now carries them
@@ -367,6 +367,9 @@ describe('SubagentDrawer', () => {
render(
{}} />
);
- expect(screen.getByTestId('assistant-ui-tool-call').textContent).toContain('Searched the web');
+ const row = screen.getByTestId('assistant-ui-tool-call');
+ expect(row.textContent).toContain('Used tool');
+ expect(row.textContent).toContain('openhuman turn state');
+ expect(row.textContent).not.toContain('Searched the web');
});
});
diff --git a/app/src/features/conversations/components/__tests__/ToolTimelineBlock.test.tsx b/app/src/features/conversations/components/__tests__/ToolTimelineBlock.test.tsx
index 4c68fa44cd1..2c9229442af 100644
--- a/app/src/features/conversations/components/__tests__/ToolTimelineBlock.test.tsx
+++ b/app/src/features/conversations/components/__tests__/ToolTimelineBlock.test.tsx
@@ -124,10 +124,10 @@ describe('SubagentActivityBlock', () => {
expect(calls[0].textContent).toContain('Searched the web');
expect(calls[0].textContent?.toLowerCase()).toContain('done');
expect(calls[0].textContent).toContain('312ms');
- expect(calls[1].textContent).toContain('Composio Execute');
+ expect(calls[1].textContent).toContain('Running app action');
expect(calls[1].textContent?.toLowerCase()).toContain('running');
expect(calls[1].textContent).not.toContain('·t2');
- expect(calls[2].textContent).toContain('Reading file');
+ expect(calls[2].textContent).toContain('Read file');
expect(calls[2].textContent?.toLowerCase()).toContain('failed');
expect(calls[2].textContent).toContain('50ms');
});
@@ -160,7 +160,9 @@ describe('SubagentActivityBlock', () => {
expect(screen.queryByText(/"content"/)).not.toBeInTheDocument();
});
- it('infers a descriptive search label for a degraded subagent tool name', () => {
+ // A query argument alone no longer makes a call "Searched the web"; that
+ // heuristic mislabelled memory, tool and email searches.
+ it('labels a degraded subagent tool name honestly, keeping its query visible', () => {
renderInStore(
{
/>
);
- expect(screen.getByTestId('assistant-ui-tool-call')).toHaveTextContent('Searched the web');
+ const call = screen.getByTestId('assistant-ui-tool-call');
+ expect(call).toHaveTextContent('Used tool');
+ expect(call).toHaveTextContent('world news');
+ expect(call).not.toHaveTextContent('Searched the web');
});
it('labels cancelled / awaiting-user calls distinctly (not the green "Done" pill)', () => {
@@ -204,7 +209,7 @@ describe('SubagentActivityBlock', () => {
expect(calls[1].textContent?.toLowerCase()).not.toContain('done');
});
- it('prefers the server-supplied label + contextual detail for a child tool call', () => {
+ it('names a connected-app action by its app, with the server detail beside the action', () => {
renderInStore(
{
/>
);
const row = screen.getByTestId('assistant-ui-tool-call');
- expect(row.textContent).toContain('Reading messages');
- expect(row.textContent).toContain('steven@gmail.com');
+ expect(row.textContent).toContain('Used Gmail');
+ expect(row.textContent).toContain('Read messages · steven@gmail.com');
// Never the raw snake_case slug.
expect(row.textContent).not.toContain('GMAIL_READ_MESSAGES');
});
@@ -420,8 +425,8 @@ describe('ToolTimelineBlock — agentic task insights surface', () => {
// Two rows on the timeline rail.
expect(screen.getAllByTestId('agent-timeline-row')).toHaveLength(2);
// Running row name pulses; done row name is solid.
- const running = screen.getByText('Searching: f1');
- const done = screen.getByText('Reading file');
+ const running = screen.getByText('Searching the web');
+ const done = screen.getByText('Read file');
expect(running.className).toContain('animate-pulse');
expect(done.className).not.toContain('animate-pulse');
});
@@ -440,9 +445,9 @@ describe('ToolTimelineBlock — agentic task insights surface', () => {
renderInStore( );
const rows = screen.getAllByTestId('agent-timeline-row');
expect(rows).toHaveLength(3);
- expect(rows[0].textContent).toContain('Searching the web');
- expect(rows[1].textContent).toContain('Reading file');
- expect(rows[2].textContent).toContain('Run Code');
+ expect(rows[0].textContent).toContain('Searched the web');
+ expect(rows[1].textContent).toContain('Read file');
+ expect(rows[2].textContent).toContain('Ran code');
});
it('renders nothing for an empty timeline', () => {
@@ -1454,7 +1459,7 @@ describe('ToolTimelineBlock — sub-agent activity survives the transcript path'
expect(calls[0].textContent).toContain('Searched the web');
expect(calls[0].textContent?.toLowerCase()).toContain('done');
// Human label, not the raw `web_fetch` slug.
- expect(calls[1].textContent).toContain('Fetching');
+ expect(calls[1].textContent).toContain('Reading webpage');
expect(calls[1].textContent?.toLowerCase()).toContain('running');
});
diff --git a/app/src/features/conversations/components/aui/InferenceStatusLine.tsx b/app/src/features/conversations/components/aui/InferenceStatusLine.tsx
index 0f1849c1e19..401dc954aba 100644
--- a/app/src/features/conversations/components/aui/InferenceStatusLine.tsx
+++ b/app/src/features/conversations/components/aui/InferenceStatusLine.tsx
@@ -76,7 +76,8 @@ export function InferenceStatusLine({
round: status.iteration,
seq: 0,
status: 'running',
- }
+ },
+ t
).title
}...`}
{status.phase === 'subagent' &&
@@ -88,7 +89,8 @@ export function InferenceStatusLine({
round: status.iteration,
seq: 0,
status: 'running',
- }
+ },
+ t
).title
}...`}
diff --git a/app/src/features/conversations/derived/mapDisplayItems.test.ts b/app/src/features/conversations/derived/mapDisplayItems.test.ts
index 28c4a3eded0..2efeb4f5068 100644
--- a/app/src/features/conversations/derived/mapDisplayItems.test.ts
+++ b/app/src/features/conversations/derived/mapDisplayItems.test.ts
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest';
import type { DerivedDisplayItem } from '../../../types/derivedTranscript';
+import { formatTimelineEntry } from '../../../utils/toolTimelineFormatting';
import { mapDisplayItems } from './mapDisplayItems';
/**
@@ -188,7 +189,7 @@ describe('mapDisplayItems', () => {
expect(timelines['req-1'][0].failure?.causePlain).toBe('raw error text');
});
- it('derives displayName/detail for a tool row (parity with turn_state rows)', () => {
+ it('derives the detail for a tool row but leaves displayName to the server', () => {
const chronological: DerivedDisplayItem[] = [
{ kind: 'turnBoundary', requestId: 'req-1' },
{
@@ -204,8 +205,11 @@ describe('mapDisplayItems', () => {
const { timelines } = mapDisplayItems(newestFirst(chronological));
const row = timelines['req-1'][0];
- expect(typeof row.displayName).toBe('string');
- expect(row.displayName?.length ?? 0).toBeGreaterThan(0);
+ // A baked client title froze its tense ("Running command" on a finished
+ // row); surfaces resolve the title at render time instead.
+ expect(row.displayName).toBeUndefined();
+ expect(row.detail).toBe('ls -la');
+ expect(formatTimelineEntry(row).title).toBe('Ran command');
});
it('anchors a subagent to its own requestId, not the current turn cursor', () => {
diff --git a/app/src/features/conversations/derived/mapDisplayItems.ts b/app/src/features/conversations/derived/mapDisplayItems.ts
index 5290eed84e9..d415c7e5cad 100644
--- a/app/src/features/conversations/derived/mapDisplayItems.ts
+++ b/app/src/features/conversations/derived/mapDisplayItems.ts
@@ -459,12 +459,12 @@ function pushToolCall(turn: TurnAccumulator, item: DerivedToolCall): void {
// A failed tool renders its "why / next" explanation via `ToolFailureLines`.
const failure = toFailureExplanation(item.failure, item.result);
if (failure) entry.failure = failure;
- // Derive the human label + detail from tool name + args (the same TS
- // formatter the live path runs), so settled rows carry `displayName`/`detail`
- // at parity with `turn_state` rows instead of being unlabelled.
+ // Derive the detail from tool name + args (the same registry the live path
+ // runs). The title is *not* baked into `displayName`: that field carries
+ // only a server label, and a baked title froze its tense at "Reading file"
+ // on a finished row. Surfaces resolve the title at render time.
const formatted = formatTimelineEntry(entry);
- entry.displayName = formatted.title;
- if (formatted.detail !== undefined) entry.detail = formatted.detail;
+ if (entry.detail === undefined && formatted.detail !== undefined) entry.detail = formatted.detail;
turn.entries.push(entry);
turn.transcript.push({ kind: 'toolCall', round: turn.round, seq, callId });
}
diff --git a/app/src/features/conversations/tools/ToolBodies.tsx b/app/src/features/conversations/tools/ToolBodies.tsx
new file mode 100644
index 00000000000..7efa2a5274e
--- /dev/null
+++ b/app/src/features/conversations/tools/ToolBodies.tsx
@@ -0,0 +1,267 @@
+/**
+ * Rich bodies for a tool call, built from assistant-ui's elements
+ * (`components/assistant-ui/elements/`): the web-search element for
+ * searches, the terminal block for commands, the web preview for fetched
+ * pages and the code diff for file edits.
+ *
+ * This file only adapts OpenHuman's tool data onto those elements; it adds no
+ * styling of its own beyond width. Each adapter returns `null` when the call
+ * left nothing to show, so the caller can fall back to the generic
+ * Request / Result panel.
+ */
+import type { ReactNode } from 'react';
+
+import { Source } from '../../../components/ai-elements';
+import { CodeDiff, type DiffLine } from '../../../components/assistant-ui/elements/code-diff';
+import { TerminalBlock } from '../../../components/assistant-ui/elements/terminal-block';
+import { WebPreview } from '../../../components/assistant-ui/elements/web-preview';
+import { WebSearch } from '../../../components/assistant-ui/elements/web-search';
+import { BubbleMarkdown } from '../components/AgentMessageBubble';
+import { parseWebSearchResult } from './parseWebSearchResult';
+import { displayUrl, shortenPath, type ToolArgs } from './toolChips';
+import { fillPlaceholders } from './toolPhrases';
+import type { Translate } from './toolPresentation';
+
+const FULL_WIDTH = 'max-w-none';
+const MAX_LINES = 400;
+
+function renderSearchLink({
+ href,
+ className,
+ children,
+}: {
+ href: string;
+ className: string;
+ children: ReactNode;
+}) {
+ // `Source` is the app's one external-link anchor; the global link guard
+ // routes it to the OS browser. `href` is an http(s) URL vetted by
+ // `parseWebSearchResult`.
+ return (
+
+ {children}
+
+ );
+}
+
+/** A web search through assistant-ui's web-search element. */
+export function WebSearchBody({
+ args,
+ result,
+ structured,
+ searching,
+ t,
+}: {
+ args: ToolArgs;
+ result: unknown;
+ structured?: unknown;
+ searching: boolean;
+ t: Translate;
+}): ReactNode {
+ const parsed = searching ? undefined : parseWebSearchResult(result, structured);
+ if (!searching && !parsed) return null;
+ const argQuery =
+ typeof args.query === 'string'
+ ? args.query
+ : typeof args.objective === 'string'
+ ? args.objective
+ : '';
+ const hits = parsed?.results ?? [];
+ const count =
+ hits.length === 0
+ ? t('conversations.tools.search.none', 'No results')
+ : fillPlaceholders(
+ hits.length === 1
+ ? t('conversations.tools.search.found.one', 'Found {count} result')
+ : t('conversations.tools.search.found.other', 'Found {count} results'),
+ { count: String(hits.length) }
+ );
+ const statusLabel = parsed?.provider
+ ? `${count} · ${fillPlaceholders(t('conversations.tools.search.via', 'via {provider}'), {
+ provider: parsed.provider,
+ })}`
+ : count;
+ return (
+ ({ title: hit.title, domain: hit.domain, url: hit.url }))}
+ visibleResults={hits.length}
+ searching={searching}
+ cycle={0}
+ searchingLabel={t('conversations.tools.search.searching', 'Searching')}
+ statusLabel={statusLabel}
+ renderLink={renderSearchLink}
+ />
+ );
+}
+
+function stringOf(value: unknown): string | undefined {
+ if (typeof value === 'string') return value;
+ if (value === undefined || value === null) return undefined;
+ try {
+ return JSON.stringify(value, null, 2);
+ } catch {
+ return String(value);
+ }
+}
+
+function linesOf(text: string): string[] {
+ const lines = text.replace(/\s+$/, '').split('\n');
+ return lines.length > MAX_LINES ? [...lines.slice(0, MAX_LINES), '…'] : lines;
+}
+
+/** A command and its output through assistant-ui's terminal block. */
+export function ShellBody({
+ args,
+ result,
+ failed,
+ t,
+}: {
+ args: ToolArgs;
+ result: unknown;
+ failed: boolean;
+ t: Translate;
+}): ReactNode {
+ const command =
+ (typeof args.command === 'string' && args.command) ||
+ (typeof args.subcommand === 'string' && `npm ${args.subcommand}`) ||
+ (typeof args.script_path === 'string' && args.script_path) ||
+ (typeof args.inline_code === 'string' && args.inline_code) ||
+ '';
+ const output = stringOf(result) ?? '';
+ if (!command && !output.trim()) return null;
+ const lines = output.trim() ? linesOf(output) : [];
+ return (
+
+ );
+}
+
+/** `status=200 url=https://… content=markdown` header, then the page. */
+function splitFetchOutput(text: string): { status?: string; url?: string; body: string } {
+ const newline = text.indexOf('\n');
+ const head = newline === -1 ? text : text.slice(0, newline);
+ if (!/^status=\d{3}\b/.test(head)) return { body: text };
+ return {
+ status: head.match(/^status=(\d{3})/)?.[1],
+ url: head.match(/\burl=(\S+)/)?.[1],
+ body: newline === -1 ? '' : text.slice(newline + 1),
+ };
+}
+
+function isHttpUrl(value: string): boolean {
+ try {
+ const { protocol } = new URL(value);
+ return protocol === 'http:' || protocol === 'https:';
+ } catch {
+ return false;
+ }
+}
+
+/** A fetched page through assistant-ui's web preview. */
+export function FetchBody({
+ args,
+ result,
+ t,
+ onOpenExternal,
+}: {
+ args: ToolArgs;
+ result: unknown;
+ t: Translate;
+ onOpenExternal?: (url: string) => void;
+}): ReactNode {
+ const text = typeof result === 'string' ? result : undefined;
+ if (!text) return null;
+ const { status, url, body } = splitFetchOutput(text);
+ const source = url ?? (typeof args.url === 'string' ? args.url : undefined);
+ const externalSource = source && isHttpUrl(source) ? source : undefined;
+ const origin = [status, source ? displayUrl(source) : undefined].filter(Boolean).join(' · ');
+ return (
+ onOpenExternal(externalSource) : undefined}
+ openExternalLabel={t('conversations.tools.openInBrowser')}>
+
+
+
+
+ );
+}
+
+/** A file edit, write or read through assistant-ui's code diff. */
+export function FileBody({ args, result }: { args: ToolArgs; result: unknown }): ReactNode {
+ const path =
+ typeof args.path === 'string'
+ ? args.path
+ : typeof args.file_path === 'string'
+ ? args.file_path
+ : '';
+ const filename = shortenPath(path);
+ const oldText = typeof args.old_string === 'string' ? args.old_string : undefined;
+ const newText = typeof args.new_string === 'string' ? args.new_string : undefined;
+ if (oldText !== undefined || newText !== undefined) {
+ const removed = oldText ? linesOf(oldText) : [];
+ const added = newText ? linesOf(newText) : [];
+ const lines: DiffLine[] = [
+ ...removed.map(text => ({ kind: 'removed' as const, text })),
+ ...added.map(text => ({ kind: 'added' as const, text })),
+ ];
+ return (
+
+ );
+ }
+ const written = typeof args.content === 'string' ? args.content : undefined;
+ if (written !== undefined) {
+ if (!written.trim()) return null;
+ const lines = linesOf(written);
+ return (
+ ({ kind: 'added' as const, text }))}
+ cycle={0}
+ />
+ );
+ }
+ // A read changed nothing, so a diff header ("+0 −0") would mislead: show the
+ // content as a fenced code block through the chat's markdown renderer.
+ const content = typeof result === 'string' ? result : '';
+ if (!content.trim()) return null;
+ const language = /\.([a-z0-9]+)$/i.exec(path)?.[1]?.toLowerCase() ?? '';
+ const fence = content.includes('```') ? '````' : '```';
+ return (
+
+
+
+ );
+}
diff --git a/app/src/features/conversations/tools/ToolDataView.tsx b/app/src/features/conversations/tools/ToolDataView.tsx
new file mode 100644
index 00000000000..eb5832d3843
--- /dev/null
+++ b/app/src/features/conversations/tools/ToolDataView.tsx
@@ -0,0 +1,69 @@
+import { BubbleMarkdown } from '../components/AgentMessageBubble';
+
+/**
+ * Generic, readable rendering of a tool's input or output: JSON objects as a
+ * definition list, arrays as a list, strings as markdown. The fallback body
+ * for any tool without a dedicated renderer.
+ */
+
+function friendlyLabel(key: string): string {
+ return key
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
+ .replace(/[_-]+/g, ' ')
+ .replace(/^./, char => char.toUpperCase());
+}
+
+export function parsedValue(value: unknown): unknown {
+ if (typeof value !== 'string') return value;
+ const trimmed = value.trim();
+ if (!(trimmed.startsWith('{') || trimmed.startsWith('['))) return value;
+ try {
+ return JSON.parse(trimmed);
+ } catch {
+ return value;
+ }
+}
+
+export function hasDisplayValue(value: unknown): boolean {
+ if (value === undefined || value === null || value === '') return false;
+ if (Array.isArray(value)) return value.length > 0;
+ if (typeof value === 'object') return Object.keys(value as object).length > 0;
+ return true;
+}
+
+export function ToolDataView({ value }: { value: unknown }) {
+ const parsed = parsedValue(value);
+ if (Array.isArray(parsed)) {
+ return (
+
+ {parsed.map((item, index) => (
+
+
+
+ ))}
+
+ );
+ }
+ if (parsed && typeof parsed === 'object') {
+ const entries = Object.entries(parsed);
+ for (const key of ['content', 'output', 'result', 'message', 'query', 'q']) {
+ const semantic = entries.find(([candidate]) => candidate === key)?.[1];
+ if (hasDisplayValue(semantic)) return ;
+ }
+ return (
+
+ {entries.map(([key, item]) => (
+
+
{friendlyLabel(key)}
+
+
+
+
+ ))}
+
+ );
+ }
+ if (typeof parsed === 'boolean') return {parsed ? 'Yes' : 'No'} ;
+ if (typeof parsed === 'string') return ;
+ return {String(parsed ?? '')} ;
+}
diff --git a/app/src/features/conversations/tools/ToolIcon.tsx b/app/src/features/conversations/tools/ToolIcon.tsx
new file mode 100644
index 00000000000..6107ae95ade
--- /dev/null
+++ b/app/src/features/conversations/tools/ToolIcon.tsx
@@ -0,0 +1,37 @@
+import { useState } from 'react';
+
+import { cn } from '../../../components/assistant-ui/lib/utils';
+import { composioLogoUrl } from '../../../components/composio/toolkitMeta';
+import type { ToolCallPresentation } from './toolPresentation';
+
+/**
+ * The glyph for a tool call: the connected app's logo for an integration
+ * action (the same Composio-hosted logo the Skills page already shows), or the
+ * registry's lucide icon. A logo that fails to load falls back to the icon, so
+ * an unknown toolkit never renders a broken image.
+ */
+export function ToolIcon({
+ presentation,
+ className,
+}: {
+ presentation: Pick;
+ className?: string;
+}) {
+ const [logoFailed, setLogoFailed] = useState(false);
+ const Icon = presentation.icon;
+ const integration = presentation.integration;
+ if (integration?.known && !logoFailed) {
+ return (
+ setLogoFailed(true)}
+ />
+ );
+ }
+ return ;
+}
diff --git a/app/src/features/conversations/tools/__fixtures__/coreToolNames.json b/app/src/features/conversations/tools/__fixtures__/coreToolNames.json
new file mode 100644
index 00000000000..ab8126eec57
--- /dev/null
+++ b/app/src/features/conversations/tools/__fixtures__/coreToolNames.json
@@ -0,0 +1,193 @@
+[
+ "agent_prepare_context",
+ "apply_patch",
+ "artifact_delete",
+ "artifact_get",
+ "artifact_list",
+ "ask_user_clarification",
+ "await_workflow",
+ "browser",
+ "browser_open",
+ "cancel_flow_run",
+ "close_subagent",
+ "config_get_autonomy",
+ "config_get_client_config",
+ "config_get_data_paths",
+ "config_get_runtime_flags",
+ "config_get_search",
+ "config_resolve_api_url",
+ "config_snapshot",
+ "continue_subagent",
+ "cost_get_daily_history",
+ "cost_get_dashboard",
+ "cost_get_summary",
+ "create_skill",
+ "create_workflow",
+ "credential_list",
+ "cron",
+ "cron_add",
+ "cron_list",
+ "cron_remove",
+ "cron_run",
+ "cron_runs",
+ "cron_update",
+ "csv_export",
+ "curl",
+ "current_time",
+ "daemon_host_prefs_get",
+ "daemon_host_prefs_set",
+ "dashboard_model_health",
+ "delegate_graph",
+ "describe_workflow",
+ "detect_tools",
+ "doctor_health",
+ "doctor_models",
+ "dry_run_workflow",
+ "duplicate_flow",
+ "edit",
+ "edit_workflow",
+ "file_read",
+ "file_write",
+ "flow_memory_recall",
+ "flow_memory_remember",
+ "get_flow",
+ "get_flow_history",
+ "get_flow_run",
+ "get_node_kind_contract",
+ "get_tool_contract",
+ "get_tool_output_sample",
+ "git_operations",
+ "gitbooks_get_page",
+ "gitbooks_search",
+ "glob",
+ "gmail_unsubscribe",
+ "goal_complete",
+ "goal_get",
+ "goal_set",
+ "goals",
+ "grep",
+ "health_snapshot",
+ "health_system_info",
+ "http_request",
+ "image_info",
+ "install_tool",
+ "install_workflow_from_url",
+ "learning_cache_stats",
+ "learning_enrich_profile",
+ "learning_forget_facet",
+ "learning_get_facet",
+ "learning_list_facets",
+ "learning_pin_facet",
+ "learning_rebuild_cache",
+ "learning_reset_cache",
+ "learning_save_profile",
+ "learning_unpin_facet",
+ "learning_update_facet",
+ "list",
+ "list_agent_definitions",
+ "list_connectable_toolkits",
+ "list_flow_connections",
+ "list_flow_runs",
+ "list_flows",
+ "list_node_kinds",
+ "list_subagents",
+ "list_workflow_runs",
+ "list_workflows",
+ "mcp_call_tool",
+ "mcp_list_servers",
+ "mcp_list_tools",
+ "mcp_registry_connect",
+ "mcp_registry_disconnect",
+ "mcp_registry_get",
+ "mcp_registry_installed_list",
+ "mcp_registry_list_tools",
+ "mcp_registry_search",
+ "mcp_registry_status",
+ "mcp_registry_tool_call",
+ "mcp_registry_uninstall",
+ "memory",
+ "memory_chunk_context",
+ "memory_doctor",
+ "memory_flavour",
+ "memory_forget",
+ "memory_hybrid_search",
+ "memory_recall",
+ "memory_store",
+ "memory_store_kinds",
+ "memory_store_raw_chunks",
+ "memory_store_raw_search",
+ "memory_tree",
+ "memory_vector_search",
+ "oauth_connect_url",
+ "oauth_list",
+ "plan_exit",
+ "propose_workflow",
+ "proxy_config",
+ "pushover",
+ "python_exec",
+ "read_workflow_resource",
+ "read_workflow_run_log",
+ "read_workspace_state",
+ "remember_preference",
+ "request_plan_review",
+ "resolve_time",
+ "resume_flow_run",
+ "retrieve_tool_output",
+ "revise_workflow",
+ "run_flow",
+ "run_workflow",
+ "save_preference",
+ "save_workflow",
+ "schedule",
+ "search_tool_catalog",
+ "security_policy_info",
+ "service_install",
+ "service_restart",
+ "service_shutdown",
+ "service_start",
+ "service_status",
+ "service_stop",
+ "service_uninstall",
+ "session_state",
+ "shell",
+ "skill_registry_browse",
+ "skill_registry_install",
+ "skill_registry_search",
+ "skill_registry_sources",
+ "skill_registry_uninstall",
+ "skill_runtime_resolve_runtimes",
+ "skill_search",
+ "spawn_async_subagent",
+ "spawn_parallel_agents",
+ "spawn_subagent",
+ "steer_subagent",
+ "suggest_workflows",
+ "task_source_add",
+ "task_source_fetch",
+ "task_source_get",
+ "task_source_list",
+ "task_source_list_tasks",
+ "task_source_preview_filter",
+ "task_source_remove",
+ "task_source_status",
+ "task_source_update",
+ "tinyjuice_retrieve",
+ "todo",
+ "tool_call",
+ "tool_search",
+ "uninstall_workflow",
+ "update_apply",
+ "update_check",
+ "update_memory_md",
+ "use_skill",
+ "validate_workflow",
+ "wait",
+ "wait_loop",
+ "wait_subagent",
+ "web_fetch",
+ "web_search_tool",
+ "workspace_init",
+ "workspace_read_persona",
+ "workspace_reset_persona",
+ "workspace_update_persona"
+]
diff --git a/app/src/features/conversations/tools/parseWebSearchResult.test.ts b/app/src/features/conversations/tools/parseWebSearchResult.test.ts
new file mode 100644
index 00000000000..f7ce0944ea2
--- /dev/null
+++ b/app/src/features/conversations/tools/parseWebSearchResult.test.ts
@@ -0,0 +1,124 @@
+import { describe, expect, it } from 'vitest';
+
+import { extractAgentSources } from '../../../utils/toolTimelineFormatting';
+import { extractSearchProvider, parseWebSearchResult } from './parseWebSearchResult';
+
+const TEXT = [
+ 'Search results for: rust async traits (via Exa)',
+ '1. Async fn in traits are now stable',
+ ' https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html',
+ ' Published: 2023-12-21',
+ ' Rust 1.75 stabilizes async fn in traits.',
+ 'This line wraps from the excerpt.',
+ '2. javascript link',
+ ' javascript:alert(1)',
+ '3. Tokio tutorial',
+ ' https://tokio.rs/tokio/tutorial',
+ ' Learn async Rust.',
+].join('\n');
+
+describe('parseWebSearchResult', () => {
+ it('parses the plain-text rendering every engine returns', () => {
+ const parsed = parseWebSearchResult(TEXT);
+ expect(parsed?.query).toBe('rust async traits');
+ expect(parsed?.provider).toBe('Exa');
+ expect(parsed?.results.map(r => r.domain)).toEqual(['blog.rust-lang.org', 'tokio.rs']);
+ expect(parsed?.results[0]).toMatchObject({
+ title: 'Async fn in traits are now stable',
+ published: '2023-12-21',
+ excerpt: 'Rust 1.75 stabilizes async fn in traits. This line wraps from the excerpt.',
+ });
+ });
+
+ it('drops non-http(s) urls instead of rendering them as links', () => {
+ const urls = parseWebSearchResult(TEXT)?.results.map(r => r.url) ?? [];
+ expect(urls.every(url => url.startsWith('https://'))).toBe(true);
+ });
+
+ it('reports an empty search as empty, not unparseable', () => {
+ expect(parseWebSearchResult('No results found for: zzqx (via Brave)')).toEqual({
+ query: 'zzqx',
+ provider: 'Brave',
+ results: [],
+ empty: true,
+ });
+ });
+
+ it('keeps a "(via …)" inside the query out of the provider', () => {
+ const parsed = parseWebSearchResult('Search results for: login (via OAuth) (via Exa)');
+ expect(parsed?.provider).toBe('Exa');
+ expect(parsed?.query).toBe('login (via OAuth)');
+ expect(extractSearchProvider('Search results for: login (via OAuth) (via Exa)')).toBe('Exa');
+ });
+
+ it('parses the markdown rendering', () => {
+ const md = [
+ '# Search results — `vite plugins` (via Tavily)',
+ '',
+ '## [Vite plugin API](https://vite.dev/guide/api-plugin)',
+ '_Published: 2025-01-01_',
+ '',
+ '> Plugins extend Vite.',
+ ].join('\n');
+ const parsed = parseWebSearchResult(md);
+ expect(parsed?.provider).toBe('Tavily');
+ expect(parsed?.results).toEqual([
+ {
+ title: 'Vite plugin API',
+ url: 'https://vite.dev/guide/api-plugin',
+ domain: 'vite.dev',
+ published: '2025-01-01',
+ excerpt: 'Plugins extend Vite.',
+ },
+ ]);
+ });
+
+ it('prefers the structured payload over the text', () => {
+ const parsed = parseWebSearchResult('Search results for: ignored (via Exa)', {
+ kind: 'web_search',
+ query: 'structured',
+ provider: 'Parallel',
+ results: [{ title: 'A', url: 'https://www.a.dev/x', excerpt: 'e' }, { url: 'file:///etc' }],
+ });
+ expect(parsed).toEqual({
+ query: 'structured',
+ provider: 'Parallel',
+ results: [{ title: 'A', url: 'https://www.a.dev/x', domain: 'a.dev', excerpt: 'e' }],
+ empty: false,
+ });
+ });
+
+ it('returns undefined for output it does not recognise', () => {
+ expect(parseWebSearchResult('some other text')).toBeUndefined();
+ expect(parseWebSearchResult(undefined)).toBeUndefined();
+ });
+});
+
+describe('extractAgentSources', () => {
+ it('lists the hits of a completed web search as sources', () => {
+ const sources = extractAgentSources([
+ {
+ id: 's1',
+ name: 'web_search_tool',
+ round: 1,
+ seq: 0,
+ status: 'success',
+ argsBuffer: '{"query":"rust async traits"}',
+ result: TEXT,
+ },
+ {
+ id: 'f1',
+ name: 'web_fetch',
+ round: 1,
+ seq: 1,
+ status: 'success',
+ argsBuffer: '{"url":"https://tokio.rs/tokio/tutorial"}',
+ },
+ ]);
+ expect(sources.map(s => s.url)).toEqual([
+ 'https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html',
+ 'https://tokio.rs/tokio/tutorial',
+ ]);
+ expect(sources[0].title).toBe('Async fn in traits are now stable');
+ });
+});
diff --git a/app/src/features/conversations/tools/parseWebSearchResult.ts b/app/src/features/conversations/tools/parseWebSearchResult.ts
new file mode 100644
index 00000000000..2644c1e2dcc
--- /dev/null
+++ b/app/src/features/conversations/tools/parseWebSearchResult.ts
@@ -0,0 +1,220 @@
+/**
+ * Turn a web-search tool result into rows the search element can render.
+ *
+ * Three inputs, most trustworthy first:
+ *
+ * 1. The structured payload a current core attaches to `tool_result`
+ * (`{ kind: "web_search", query, provider, results: [...] }`).
+ * 2. The plain-text rendering every engine returns to the model:
+ *
+ * Search results for: (via )
+ * 1.
+ *
+ * Published:
+ *
+ *
+ * 3. The markdown rendering (`## [title](url)` / `> excerpt`) used when the
+ * core prefers markdown.
+ *
+ * Every URL is model- or provider-supplied, so only well-formed `http(s)`
+ * URLs are admitted; anything else is dropped rather than rendered as a
+ * link.
+ */
+/** Upper bound on a provider label, so a malformed marker can't blow up a row. */
+const MAX_SEARCH_PROVIDER_LENGTH = 32;
+
+/**
+ * Extract the resolved search provider from a completed web-search result.
+ * Every search engine tags its output with a `(via )` marker on the
+ * heading line (managed resolves to "Exa" by default, or to whatever the
+ * backend reports; BYOK engines tag "Brave"/"Querit"/"Seltz"/"Tavily"). Reading it back
+ * keeps the attribution dynamic: it is driven by what actually ran, never by
+ * a hardcoded provider name (#5136).
+ *
+ * Only the first line is inspected, and only its *trailing* marker, so neither
+ * a `(via …)` string inside a result excerpt nor one inside the echoed query
+ * (`Search results for: login (via OAuth) (via Exa)`) can be mistaken for the
+ * provider. Returns `undefined` while the call is still running (no result
+ * yet) or if no marker is present.
+ */
+export function extractSearchProvider(result: string | undefined): string | undefined {
+ if (!result) return undefined;
+ const headingLine = result.split('\n', 1)[0];
+ const provider = headingLine?.match(/\(via ([^)]+)\)\s*_?$/i)?.[1]?.trim();
+ if (!provider || provider.length > MAX_SEARCH_PROVIDER_LENGTH) return undefined;
+ return provider;
+}
+
+export interface WebSearchHit {
+ title: string;
+ url: string;
+ domain: string;
+ published?: string;
+ excerpt?: string;
+}
+
+export interface ParsedWebSearch {
+ query?: string;
+ provider?: string;
+ results: WebSearchHit[];
+ /** The call completed and found nothing (distinct from "not parseable"). */
+ empty: boolean;
+}
+
+const MAX_EXCERPT = 280;
+
+function safeHttpUrl(value: string): URL | undefined {
+ try {
+ const url = new URL(value.trim());
+ return url.protocol === 'http:' || url.protocol === 'https:' ? url : undefined;
+ } catch {
+ return undefined;
+ }
+}
+
+function clip(text: string | undefined, max = MAX_EXCERPT): string | undefined {
+ const cleaned = text?.replace(/\s+/g, ' ').trim();
+ if (!cleaned) return undefined;
+ return cleaned.length > max ? `${cleaned.slice(0, max - 1)}…` : cleaned;
+}
+
+function hit(
+ title: string | undefined,
+ rawUrl: string | undefined,
+ published?: string,
+ excerpt?: string
+): WebSearchHit | undefined {
+ const url = rawUrl ? safeHttpUrl(rawUrl) : undefined;
+ if (!url) return undefined;
+ const domain = url.hostname.replace(/^www\./, '');
+ return {
+ title: clip(title, 160) ?? domain,
+ url: url.toString(),
+ domain,
+ ...(clip(published, 40) ? { published: clip(published, 40) } : {}),
+ ...(clip(excerpt) ? { excerpt: clip(excerpt) } : {}),
+ };
+}
+
+function fromStructured(value: unknown): ParsedWebSearch | undefined {
+ if (!value || typeof value !== 'object') return undefined;
+ const payload = value as Record;
+ if (payload.kind !== 'web_search' || !Array.isArray(payload.results)) return undefined;
+ const results = payload.results
+ .map(item => {
+ if (!item || typeof item !== 'object') return undefined;
+ const row = item as Record;
+ const str = (key: string) =>
+ typeof row[key] === 'string' ? (row[key] as string) : undefined;
+ return hit(str('title'), str('url'), str('published'), str('excerpt'));
+ })
+ .filter((row): row is WebSearchHit => row !== undefined);
+ return {
+ query: typeof payload.query === 'string' ? payload.query : undefined,
+ provider: typeof payload.provider === 'string' ? payload.provider : undefined,
+ results,
+ empty: results.length === 0,
+ };
+}
+
+/** Strip the trailing `(via X)` marker from a heading's query part. */
+function headingQuery(heading: string): string | undefined {
+ const query = heading.replace(/\s*\(via [^)]+\)\s*$/i, '').trim();
+ return query.replace(/^`|`$/g, '').trim() || undefined;
+}
+
+function fromText(text: string): ParsedWebSearch | undefined {
+ const lines = text.split('\n');
+ const heading = lines[0]?.trim() ?? '';
+ const provider = extractSearchProvider(heading);
+
+ const emptyMatch = heading.match(/^_?No (?:\w+ )?results (?:found )?for:?\s*(.+?)_?$/i);
+ if (emptyMatch) {
+ return {
+ query: headingQuery(emptyMatch[1].replace(/_$/, '').replace(/[._]+$/, '')),
+ provider,
+ results: [],
+ empty: true,
+ };
+ }
+
+ // Markdown rendering.
+ const mdHeading = heading.match(/^#\s+\w+ results\s*(?:--|:|—)\s*(.+)$/i);
+ if (mdHeading || lines.some(line => /^##\s+\[.+\]\(.+\)\s*$/.test(line))) {
+ const results: WebSearchHit[] = [];
+ let current: { title: string; url: string; published?: string; excerpt: string[] } | null =
+ null;
+ const flush = () => {
+ if (!current) return;
+ const row = hit(current.title, current.url, current.published, current.excerpt.join(' '));
+ if (row) results.push(row);
+ current = null;
+ };
+ for (const line of lines.slice(1)) {
+ const link = line.match(/^##\s+\[(.+)\]\((\S+)\)\s*$/);
+ if (link) {
+ flush();
+ current = { title: link[1], url: link[2], excerpt: [] };
+ continue;
+ }
+ if (!current) continue;
+ const published = line.match(/^_Published:\s*(.+?)_\s*$/);
+ if (published) current.published = published[1];
+ else if (line.startsWith('>')) current.excerpt.push(line.replace(/^>\s?/, ''));
+ }
+ flush();
+ return {
+ query: mdHeading ? headingQuery(mdHeading[1]) : undefined,
+ provider,
+ results,
+ empty: results.length === 0,
+ };
+ }
+
+ // Plain-text rendering.
+ const textHeading = heading.match(/^(?:Search|\w+) results for:\s*(.+)$/i);
+ if (!textHeading) return undefined;
+ // An item is a numbered title line followed by its indented URL line. An
+ // excerpt's continuation lines are not indented, so that shape is what
+ // separates the next item from a wrapped excerpt.
+ const isItemStart = (index: number) =>
+ /^\s*\d+\.\s+\S/.test(lines[index] ?? '') && /^\s{2,}\S/.test(lines[index + 1] ?? '');
+ const results: WebSearchHit[] = [];
+ let i = 1;
+ while (i < lines.length) {
+ if (!isItemStart(i)) {
+ i += 1;
+ continue;
+ }
+ const title = lines[i].replace(/^\s*\d+\.\s+/, '');
+ const urlLine = lines[i + 1].trim();
+ let published: string | undefined;
+ const excerpt: string[] = [];
+ let j = i + 2;
+ for (; j < lines.length && !isItemStart(j); j += 1) {
+ const trimmed = lines[j].trim();
+ const date = trimmed.match(/^Published:\s*(.+)$/);
+ if (date) published = date[1];
+ else if (!/^Author:/.test(trimmed) && trimmed) excerpt.push(trimmed);
+ }
+ const row = hit(title, urlLine, published, excerpt.join(' '));
+ if (row) results.push(row);
+ i = j;
+ }
+ return { query: headingQuery(textHeading[1]), provider, results, empty: results.length === 0 };
+}
+
+/**
+ * Parse a web-search result. `structured` wins when present; otherwise the
+ * text `output` is parsed. Returns `undefined` when neither is recognisable,
+ * so the caller can fall back to the generic output view.
+ */
+export function parseWebSearchResult(
+ output: unknown,
+ structured?: unknown
+): ParsedWebSearch | undefined {
+ const fromPayload = fromStructured(structured) ?? fromStructured(output);
+ if (fromPayload) return fromPayload;
+ if (typeof output !== 'string' || !output.trim()) return undefined;
+ return fromText(output.trim());
+}
diff --git a/app/src/features/conversations/tools/toolChips.ts b/app/src/features/conversations/tools/toolChips.ts
new file mode 100644
index 00000000000..3c75d659538
--- /dev/null
+++ b/app/src/features/conversations/tools/toolChips.ts
@@ -0,0 +1,137 @@
+/**
+ * Chip extractors: the short target shown beside a tool's label ("Read file
+ * `…/src/main.ts`", "Searched the web `rust async traits`").
+ *
+ * Every value here comes from a model-emitted argument, so it is treated as
+ * untrusted display text: trimmed, whitespace-collapsed and length-capped.
+ * Nothing in this file produces a link.
+ */
+
+export type ToolArgs = Record;
+export type ChipRule = (args: ToolArgs) => string | undefined;
+
+const MAX_CHIP_LENGTH = 80;
+
+export function truncateChip(value: string, max = MAX_CHIP_LENGTH): string {
+ const cleaned = value.trim().replace(/\s+/g, ' ');
+ if (cleaned.length <= max) return cleaned;
+ return `${cleaned.slice(0, max - 1)}…`;
+}
+
+function stringArg(args: ToolArgs, key: string): string | undefined {
+ const value = args[key];
+ if (typeof value === 'string' && value.trim()) return value;
+ if (typeof value === 'number' && Number.isFinite(value)) return String(value);
+ return undefined;
+}
+
+/** First non-empty string among `keys`. */
+export function firstArg(args: ToolArgs, ...keys: string[]): string | undefined {
+ for (const key of keys) {
+ const value = stringArg(args, key);
+ if (value) return value;
+ }
+ return undefined;
+}
+
+/** `/a/b/c/d.ts` → `…/c/d.ts`; short paths pass through. */
+export function shortenPath(filePath: string): string {
+ const parts = filePath.split('/');
+ if (parts.length <= 3) return filePath;
+ return `…/${parts.slice(-2).join('/')}`;
+}
+
+/** `https://docs.rs/tokio/latest/x` → `docs.rs/tokio/latest/x`, capped. */
+export function displayUrl(url: string): string {
+ try {
+ const parsed = new URL(url);
+ const path = parsed.pathname === '/' ? '' : parsed.pathname;
+ return truncateChip(`${parsed.hostname}${path}`);
+ } catch {
+ return truncateChip(url);
+ }
+}
+
+export function hostnameOf(url: string): string | undefined {
+ try {
+ return new URL(url).hostname || undefined;
+ } catch {
+ return undefined;
+ }
+}
+
+/** Rule factories, so the spec tables stay declarative. */
+export const chip = {
+ text:
+ (...keys: string[]): ChipRule =>
+ args => {
+ const value = firstArg(args, ...keys);
+ return value ? truncateChip(value) : undefined;
+ },
+ path:
+ (...keys: string[]): ChipRule =>
+ args => {
+ const value = firstArg(args, ...(keys.length ? keys : ['path', 'file_path']));
+ return value ? truncateChip(shortenPath(value.trim())) : undefined;
+ },
+ url:
+ (...keys: string[]): ChipRule =>
+ args => {
+ const value = firstArg(args, ...(keys.length ? keys : ['url', 'uri']));
+ if (value) return displayUrl(value.trim());
+ const list = args.urls;
+ if (Array.isArray(list) && typeof list[0] === 'string') {
+ const first = displayUrl(list[0]);
+ return list.length > 1 ? `${first} +${list.length - 1}` : first;
+ }
+ return undefined;
+ },
+ query: (): ChipRule => args => {
+ const value = firstArg(args, 'query', 'q', 'search_query', 'objective');
+ if (value) return truncateChip(value);
+ const queries = args.search_queries;
+ if (Array.isArray(queries) && typeof queries[0] === 'string') return truncateChip(queries[0]);
+ return undefined;
+ },
+ command:
+ (...keys: string[]): ChipRule =>
+ args => {
+ const value = firstArg(args, ...(keys.length ? keys : ['command']));
+ return value ? truncateChip(value, 120) : undefined;
+ },
+ /** First path among a multi-edit payload (`apply_patch { edits: [{ path }] }`). */
+ editsPath: (): ChipRule => args => {
+ const edits = args.edits;
+ if (!Array.isArray(edits) || edits.length === 0) return firstArg(args, 'path');
+ const first = edits[0] as ToolArgs | undefined;
+ const path = first && typeof first.path === 'string' ? shortenPath(first.path) : undefined;
+ if (!path) return undefined;
+ return edits.length > 1 ? `${path} +${edits.length - 1}` : path;
+ },
+};
+
+/**
+ * Generic chip for a tool the tables do not describe: the same key order the
+ * core's `context_detail_from_args` walks, so an unknown tool still shows its
+ * obvious target.
+ */
+export const genericChip: ChipRule = args => {
+ const value = firstArg(
+ args,
+ 'to',
+ 'recipient',
+ 'email',
+ 'query',
+ 'q',
+ 'url',
+ 'file_path',
+ 'path',
+ 'command',
+ 'subject',
+ 'title',
+ 'channel',
+ 'repo',
+ 'name'
+ );
+ return value ? truncateChip(value) : undefined;
+};
diff --git a/app/src/features/conversations/tools/toolPhrases.test.ts b/app/src/features/conversations/tools/toolPhrases.test.ts
new file mode 100644
index 00000000000..f3e9f00d220
--- /dev/null
+++ b/app/src/features/conversations/tools/toolPhrases.test.ts
@@ -0,0 +1,27 @@
+import { describe, expect, it } from 'vitest';
+
+import en from '../../../lib/i18n/en';
+import { phraseKey, TOOL_PHRASES, type ToolPhraseId } from './toolPhrases';
+
+const enMap = en as Record;
+const placeholders = (value: string) => [...value.matchAll(/\{(\w+)\}/g)].map(m => m[1]).sort();
+
+describe('tool phrases', () => {
+ it.each(Object.keys(TOOL_PHRASES) as ToolPhraseId[])(
+ '%s is served by en.ts with the same English',
+ id => {
+ for (const tense of ['active', 'done'] as const) {
+ expect(enMap[phraseKey(id, tense)]).toBe(TOOL_PHRASES[id][tense]);
+ }
+ }
+ );
+
+ it.each(Object.keys(TOOL_PHRASES) as ToolPhraseId[])(
+ '%s reads differently once done, with the same placeholders',
+ id => {
+ const { active, done } = TOOL_PHRASES[id];
+ expect(active).not.toBe(done);
+ expect(placeholders(active)).toEqual(placeholders(done));
+ }
+ );
+});
diff --git a/app/src/features/conversations/tools/toolPhrases.ts b/app/src/features/conversations/tools/toolPhrases.ts
new file mode 100644
index 00000000000..5a6f2a62a80
--- /dev/null
+++ b/app/src/features/conversations/tools/toolPhrases.ts
@@ -0,0 +1,235 @@
+/**
+ * The vocabulary a tool call is described with.
+ *
+ * Every phrase has two tenses: `active` while the call runs ("Reading file")
+ * and `done` once it settled ("Read file"). A finished row used to keep the
+ * progressive form next to a check mark, so it read as still running.
+ *
+ * Phrases are shared between tools on purpose: the managed search and every
+ * bring-your-own-key engine all read "Searching the web", which keeps the
+ * translation surface to one entry per *meaning* instead of one per tool.
+ *
+ * The English here is the source; each phrase is served through the i18n
+ * keys `conversations.tools..active` / `.done` (see {@link phraseKey}),
+ * so this table and `lib/i18n/en.ts` must agree. `toolPhrases.test.ts`
+ * enforces that.
+ *
+ * Placeholders (`{app}`, `{tool}`) are filled by the caller and must survive
+ * translation unchanged.
+ */
+export const TOOL_PHRASES = {
+ // ── Files and code ──────────────────────────────────────────────────────
+ readFile: { active: 'Reading file', done: 'Read file' },
+ writeFile: { active: 'Writing file', done: 'Wrote file' },
+ editFile: { active: 'Editing file', done: 'Edited file' },
+ applyEdits: { active: 'Applying edits', done: 'Applied edits' },
+ searchCode: { active: 'Searching code', done: 'Searched code' },
+ findFiles: { active: 'Finding files', done: 'Found files' },
+ listFolder: { active: 'Listing folder', done: 'Listed folder' },
+ exportCsv: { active: 'Exporting CSV', done: 'Exported CSV' },
+ updateMemoryNotes: { active: 'Updating memory notes', done: 'Updated memory notes' },
+ runGit: { active: 'Running git', done: 'Ran git' },
+ readChanges: { active: 'Reading changes', done: 'Read changes' },
+ runLinter: { active: 'Running linter', done: 'Ran linter' },
+ runTests: { active: 'Running tests', done: 'Ran tests' },
+ analyzeCode: { active: 'Analyzing code', done: 'Analyzed code' },
+ insertRecord: { active: 'Inserting record', done: 'Inserted record' },
+
+ // ── Shell and system ────────────────────────────────────────────────────
+ runCommand: { active: 'Running command', done: 'Ran command' },
+ runCode: { active: 'Running code', done: 'Ran code' },
+ runPackageManager: { active: 'Running npm', done: 'Ran npm' },
+ checkInstalledTools: { active: 'Checking installed tools', done: 'Checked installed tools' },
+ installTool: { active: 'Installing tool', done: 'Installed tool' },
+ checkTime: { active: 'Checking the time', done: 'Checked the time' },
+ resolveDate: { active: 'Working out the date', done: 'Worked out the date' },
+ retrieveOutput: { active: 'Retrieving full output', done: 'Retrieved full output' },
+ reviewWorkspace: { active: 'Reviewing workspace', done: 'Reviewed workspace' },
+ configureProxy: { active: 'Configuring proxy', done: 'Configured proxy' },
+ checkUpdates: { active: 'Checking for updates', done: 'Checked for updates' },
+ installUpdate: { active: 'Installing update', done: 'Installed update' },
+ sendNotification: { active: 'Sending notification', done: 'Sent notification' },
+ reviewToolUsage: { active: 'Reviewing tool usage', done: 'Reviewed tool usage' },
+ typeKeys: { active: 'Typing', done: 'Typed' },
+ click: { active: 'Clicking', done: 'Clicked' },
+
+ // ── Web ─────────────────────────────────────────────────────────────────
+ searchWeb: { active: 'Searching the web', done: 'Searched the web' },
+ searchNews: { active: 'Searching news', done: 'Searched news' },
+ searchImages: { active: 'Searching images', done: 'Searched images' },
+ searchVideos: { active: 'Searching videos', done: 'Searched videos' },
+ findSimilarPages: { active: 'Finding similar pages', done: 'Found similar pages' },
+ readPages: { active: 'Reading pages', done: 'Read pages' },
+ readWebpage: { active: 'Reading webpage', done: 'Read webpage' },
+ research: { active: 'Researching', done: 'Researched' },
+ enrichData: { active: 'Enriching data', done: 'Enriched data' },
+ buildDataset: { active: 'Building dataset', done: 'Built dataset' },
+ askTheWeb: { active: 'Asking the web', done: 'Asked the web' },
+ browseForYou: { active: 'Browsing for you', done: 'Browsed for you' },
+ callApi: { active: 'Calling API', done: 'Called API' },
+ downloadFile: { active: 'Downloading file', done: 'Downloaded file' },
+ makePaidRequest: { active: 'Making paid request', done: 'Made paid request' },
+ searchDocs: { active: 'Searching docs', done: 'Searched docs' },
+ readDocs: { active: 'Reading docs', done: 'Read docs' },
+
+ // ── Browser ─────────────────────────────────────────────────────────────
+ useBrowser: { active: 'Using browser', done: 'Used browser' },
+ openPage: { active: 'Opening page', done: 'Opened page' },
+ navigate: { active: 'Navigating', done: 'Navigated' },
+ takeScreenshot: { active: 'Taking screenshot', done: 'Took screenshot' },
+ scrollPage: { active: 'Scrolling', done: 'Scrolled' },
+ readPage: { active: 'Reading page', done: 'Read page' },
+
+ // ── Media and documents ────────────────────────────────────────────────
+ analyzeImage: { active: 'Analyzing image', done: 'Analyzed image' },
+ generateImage: { active: 'Generating image', done: 'Generated image' },
+ generateVideo: { active: 'Generating video', done: 'Generated video' },
+ checkMediaModels: { active: 'Checking media models', done: 'Checked media models' },
+ createDocument: { active: 'Creating document', done: 'Created document' },
+ createPresentation: { active: 'Creating presentation', done: 'Created presentation' },
+ generatePodcast: { active: 'Generating podcast', done: 'Generated podcast' },
+ emailPodcast: { active: 'Emailing podcast', done: 'Emailed podcast' },
+ createAndEmailPodcast: {
+ active: 'Creating and emailing podcast',
+ done: 'Created and emailed podcast',
+ },
+
+ // ── Memory ──────────────────────────────────────────────────────────────
+ recallMemories: { active: 'Recalling memories', done: 'Recalled memories' },
+ saveToMemory: { active: 'Saving to memory', done: 'Saved to memory' },
+ forgetMemory: { active: 'Forgetting memory', done: 'Forgot memory' },
+ searchMemory: { active: 'Searching memory', done: 'Searched memory' },
+ inspectMemory: { active: 'Inspecting memory', done: 'Inspected memory' },
+ exploreMemory: { active: 'Exploring memory', done: 'Explored memory' },
+ saveDocumentToMemory: { active: 'Saving document to memory', done: 'Saved document to memory' },
+ updateGoals: { active: 'Updating goals', done: 'Updated goals' },
+ reviewGoals: { active: 'Reviewing goals', done: 'Reviewed goals' },
+ savePreference: { active: 'Saving preference', done: 'Saved preference' },
+ reviewLearnings: { active: 'Reviewing what I learned', done: 'Reviewed what I learned' },
+ updateLearnings: { active: 'Updating what I learned', done: 'Updated what I learned' },
+
+ // ── Agents and delegation ──────────────────────────────────────────────
+ delegateTask: { active: 'Delegating task', done: 'Delegated task' },
+ runAgentsInParallel: { active: 'Running agents in parallel', done: 'Ran agents in parallel' },
+ messageAgent: { active: 'Messaging agent', done: 'Messaged agent' },
+ waitForAgent: { active: 'Waiting for agent', done: 'Waited for agent' },
+ wait: { active: 'Waiting', done: 'Waited' },
+ closeAgent: { active: 'Closing agent', done: 'Closed agent' },
+ checkAgents: { active: 'Checking agents', done: 'Checked agents' },
+ askQuestion: { active: 'Asking you a question', done: 'Asked you a question' },
+ prepareContext: { active: 'Preparing context', done: 'Prepared context' },
+ extractDetails: { active: 'Extracting details', done: 'Extracted details' },
+ planNextSteps: { active: 'Planning next steps', done: 'Planned next steps' },
+ reviewWork: { active: 'Reviewing the work', done: 'Reviewed the work' },
+ scoutContext: { active: 'Scouting context', done: 'Scouted context' },
+ useTools: { active: 'Using tools', done: 'Used tools' },
+ checkConnectedApp: { active: 'Checking your connected app', done: 'Checked your connected app' },
+
+ // ── Planning ────────────────────────────────────────────────────────────
+ updateTodos: { active: 'Updating to-do list', done: 'Updated to-do list' },
+ requestPlanReview: { active: 'Requesting plan review', done: 'Requested plan review' },
+ finishPlan: { active: 'Finishing plan', done: 'Finished plan' },
+ setGoal: { active: 'Setting goal', done: 'Set goal' },
+ checkGoal: { active: 'Checking goal', done: 'Checked goal' },
+ completeGoal: { active: 'Completing goal', done: 'Completed goal' },
+
+ // ── Scheduling ──────────────────────────────────────────────────────────
+ scheduleTask: { active: 'Scheduling task', done: 'Scheduled task' },
+ checkSchedules: { active: 'Checking schedules', done: 'Checked schedules' },
+ updateSchedule: { active: 'Updating scheduled task', done: 'Updated scheduled task' },
+ removeSchedule: { active: 'Removing scheduled task', done: 'Removed scheduled task' },
+ runScheduledTask: { active: 'Running scheduled task', done: 'Ran scheduled task' },
+ checkRunHistory: { active: 'Checking run history', done: 'Checked run history' },
+
+ // ── Connected apps ─────────────────────────────────────────────────────
+ useApp: { active: 'Using {app}', done: 'Used {app}' },
+ checkAvailableApps: { active: 'Checking available apps', done: 'Checked available apps' },
+ checkConnections: { active: 'Checking your connections', done: 'Checked your connections' },
+ connectApp: { active: 'Connecting app', done: 'Connected app' },
+ authorizeApp: { active: 'Authorizing app', done: 'Authorized app' },
+ findAppActions: { active: 'Finding app actions', done: 'Found app actions' },
+ runAppAction: { active: 'Running app action', done: 'Ran app action' },
+ findTools: { active: 'Finding tools', done: 'Found tools' },
+ useTool: { active: 'Using {tool}', done: 'Used {tool}' },
+ unsubscribe: { active: 'Unsubscribing', done: 'Unsubscribed' },
+ searchPlaces: { active: 'Searching places', done: 'Searched places' },
+ lookUpPlace: { active: 'Looking up place', done: 'Looked up place' },
+ checkMarkets: { active: 'Checking markets', done: 'Checked markets' },
+ placeCall: { active: 'Placing call', done: 'Placed call' },
+ checkTaskSources: { active: 'Checking task sources', done: 'Checked task sources' },
+ updateTaskSources: { active: 'Updating task sources', done: 'Updated task sources' },
+ fetchTasks: { active: 'Fetching tasks', done: 'Fetched tasks' },
+
+ // ── MCP ─────────────────────────────────────────────────────────────────
+ checkMcpServers: { active: 'Checking MCP servers', done: 'Checked MCP servers' },
+ checkMcpTools: { active: 'Checking MCP tools', done: 'Checked MCP tools' },
+ callMcpTool: { active: 'Calling {tool}', done: 'Called {tool}' },
+ searchMcpServers: { active: 'Searching MCP servers', done: 'Searched MCP servers' },
+ connectMcpServer: { active: 'Connecting MCP server', done: 'Connected MCP server' },
+ disconnectMcpServer: { active: 'Disconnecting MCP server', done: 'Disconnected MCP server' },
+ removeMcpServer: { active: 'Removing MCP server', done: 'Removed MCP server' },
+
+ // ── Storage and hosting ────────────────────────────────────────────────
+ uploadFile: { active: 'Uploading file', done: 'Uploaded file' },
+ listStoredFiles: { active: 'Listing stored files', done: 'Listed stored files' },
+ createShareLink: { active: 'Creating share link', done: 'Created share link' },
+ deleteFile: { active: 'Deleting file', done: 'Deleted file' },
+ updateFileAccess: { active: 'Updating file access', done: 'Updated file access' },
+ deploySite: { active: 'Deploying site', done: 'Deployed site' },
+ checkHosting: { active: 'Checking hosting', done: 'Checked hosting' },
+ updateHosting: { active: 'Updating hosting', done: 'Updated hosting' },
+ rollBackDeployment: { active: 'Rolling back deployment', done: 'Rolled back deployment' },
+
+ // ── Wallet ──────────────────────────────────────────────────────────────
+ checkWallet: { active: 'Checking wallet', done: 'Checked wallet' },
+ prepareTransfer: { active: 'Preparing transfer', done: 'Prepared transfer' },
+ checkTransaction: { active: 'Checking transaction', done: 'Checked transaction' },
+ getSwapQuote: { active: 'Getting swap quote', done: 'Got swap quote' },
+ swapTokens: { active: 'Swapping tokens', done: 'Swapped tokens' },
+ getBridgeQuote: { active: 'Getting bridge quote', done: 'Got bridge quote' },
+ bridgeTokens: { active: 'Bridging tokens', done: 'Bridged tokens' },
+ callDapp: { active: 'Calling app contract', done: 'Called app contract' },
+
+ // ── Skills and workflows ───────────────────────────────────────────────
+ useSkill: { active: 'Using skill', done: 'Used skill' },
+ searchSkills: { active: 'Searching skills', done: 'Searched skills' },
+ checkSkills: { active: 'Checking skills', done: 'Checked skills' },
+ installSkill: { active: 'Installing skill', done: 'Installed skill' },
+ removeSkill: { active: 'Removing skill', done: 'Removed skill' },
+ createSkill: { active: 'Creating skill', done: 'Created skill' },
+ runWorkflow: { active: 'Running workflow', done: 'Ran workflow' },
+ waitForWorkflow: { active: 'Waiting for workflow', done: 'Waited for workflow' },
+ designWorkflow: { active: 'Designing workflow', done: 'Designed workflow' },
+ saveWorkflow: { active: 'Saving workflow', done: 'Saved workflow' },
+ validateWorkflow: { active: 'Validating workflow', done: 'Validated workflow' },
+ testWorkflow: { active: 'Testing workflow', done: 'Tested workflow' },
+ checkWorkflows: { active: 'Checking workflows', done: 'Checked workflows' },
+ cancelWorkflow: { active: 'Cancelling workflow run', done: 'Cancelled workflow run' },
+ suggestWorkflows: { active: 'Suggesting workflows', done: 'Suggested workflows' },
+
+ // ── Settings and platform ──────────────────────────────────────────────
+ checkSettings: { active: 'Checking settings', done: 'Checked settings' },
+ checkSecurity: { active: 'Checking security', done: 'Checked security' },
+ runDiagnostics: { active: 'Running diagnostics', done: 'Ran diagnostics' },
+ checkUsageCosts: { active: 'Checking usage costs', done: 'Checked usage costs' },
+ manageService: { active: 'Managing background service', done: 'Managed background service' },
+ readPersona: { active: 'Reading persona', done: 'Read persona' },
+ updatePersona: { active: 'Updating persona', done: 'Updated persona' },
+ setUpWorkspace: { active: 'Setting up workspace', done: 'Set up workspace' },
+ checkArtifacts: { active: 'Checking artifacts', done: 'Checked artifacts' },
+ deleteArtifact: { active: 'Deleting artifact', done: 'Deleted artifact' },
+} as const satisfies Record;
+
+export type ToolPhraseId = keyof typeof TOOL_PHRASES;
+export type ToolPhraseTense = 'active' | 'done';
+
+/** The i18n key a phrase is served under. */
+export function phraseKey(id: ToolPhraseId, tense: ToolPhraseTense): string {
+ return `conversations.tools.${id}.${tense}`;
+}
+
+/** Substitute `{name}` placeholders. Unknown placeholders are left in place. */
+export function fillPlaceholders(template: string, params?: Record): string {
+ if (!params) return template;
+ return template.replace(/\{(\w+)\}/g, (match, name: string) => params[name] ?? match);
+}
diff --git a/app/src/features/conversations/tools/toolPresentation.catalog.test.ts b/app/src/features/conversations/tools/toolPresentation.catalog.test.ts
new file mode 100644
index 00000000000..743e2e5405d
--- /dev/null
+++ b/app/src/features/conversations/tools/toolPresentation.catalog.test.ts
@@ -0,0 +1,48 @@
+/**
+ * The "never again" guard for tool labels.
+ *
+ * `__fixtures__/coreToolNames.json` is every tool name the core registers. It
+ * is written and checked by the Rust test next to the core's tool registry
+ * (`UPDATE_TOOL_CATALOG=1` regenerates it), so a tool added to the core
+ * without updating the fixture fails there, and a fixture name the registry
+ * here cannot describe fails here. Between them a new core tool cannot reach
+ * the chat as a raw identifier.
+ */
+import { describe, expect, it } from 'vitest';
+
+import coreToolNames from './__fixtures__/coreToolNames.json';
+import { describeToolCall, type ToolCallStatus, toolLabel } from './toolPresentation';
+
+const NAMES = [...(coreToolNames as string[])].sort();
+const STATUSES: ToolCallStatus[] = ['running', 'success', 'error'];
+/** Brand and protocol words allowed to stay upper-case inside a label. */
+const ALLOWED_CAPS = new Set(['MCP', 'CSV', 'API']);
+
+describe('core tool catalog', () => {
+ it('is not empty', () => {
+ expect(NAMES.length).toBeGreaterThan(100);
+ });
+
+ it.each(NAMES)('%s is described by the registry, not the generic fallback', name => {
+ const presentation = describeToolCall({ name });
+ expect(presentation.source).not.toBe('fallback');
+ expect(presentation.source).not.toBe('server');
+ expect(presentation.icon).toBeTruthy();
+ });
+
+ it.each(NAMES)('%s reads as a human label in every state', name => {
+ const labels = STATUSES.map(status => toolLabel(describeToolCall({ name, status })));
+ for (const label of labels) {
+ expect(label.trim().length).toBeGreaterThan(0);
+ expect(label).not.toBe(name);
+ expect(label).not.toContain('_');
+ expect(label).not.toMatch(/^Using [A-Z]\w*ing\b/);
+ const shouting = label
+ .split(/\s+/)
+ .filter(word => /^[A-Z]{2,}$/.test(word) && !ALLOWED_CAPS.has(word));
+ expect(shouting).toEqual([]);
+ }
+ // The tense changes as the call settles.
+ expect(labels[0]).not.toBe(labels[1]);
+ });
+});
diff --git a/app/src/features/conversations/tools/toolPresentation.test.ts b/app/src/features/conversations/tools/toolPresentation.test.ts
new file mode 100644
index 00000000000..e04e88a5d77
--- /dev/null
+++ b/app/src/features/conversations/tools/toolPresentation.test.ts
@@ -0,0 +1,180 @@
+import { describe, expect, it } from 'vitest';
+
+import {
+ describeToolCall,
+ type DescribeToolCallInput,
+ sentenceCase,
+ toolLabel,
+} from './toolPresentation';
+
+const label = (input: DescribeToolCallInput) => toolLabel(describeToolCall(input));
+const done = (name: string, args?: unknown, extra: Partial = {}) =>
+ label({ name, args, status: 'success', ...extra });
+const active = (name: string, args?: unknown, extra: Partial = {}) =>
+ label({ name, args, status: 'running', ...extra });
+
+/**
+ * One test per mislabelling that shipped. Each names the bug it pins so a
+ * regression reads as that bug coming back, not as a changed string.
+ */
+describe('tool labels: regressions', () => {
+ it('does not call non-web searches "Searched the web" (tool_search, memory, skills)', () => {
+ for (const name of [
+ 'tool_search',
+ 'memory_hybrid_search',
+ 'memory_vector_search',
+ 'skill_registry_search',
+ 'mcp_registry_search',
+ 'search_tool_catalog',
+ 'gitbooks_search',
+ ]) {
+ expect(done(name, { query: 'x' }), name).not.toMatch(/web/i);
+ }
+ });
+
+ it('does not call a Composio fetch with a query argument a web search', () => {
+ expect(done('GMAIL_FETCH_EMAILS', { query: 'from:boss' })).toBe('Used Gmail');
+ expect(describeToolCall({ name: 'GMAIL_FETCH_EMAILS' }).chip).toBe('Fetch emails');
+ });
+
+ it('does not call any tool with a url argument "Fetched from the web"', () => {
+ expect(done('storage_get_link', { url: 'https://x.dev' })).toBe('Created share link');
+ expect(done('gitbooks_get_page', { url: 'https://docs.x' })).toBe('Read docs');
+ expect(done('install_workflow_from_url', { url: 'https://x' })).toBe('Installed skill');
+ });
+
+ it('labels the docs search as a docs search', () => {
+ expect(active('gitbooks_search', { query: 'install' })).toBe('Searching docs');
+ });
+
+ it('never shouts a Composio action slug', () => {
+ expect(done('GMAIL_SEND_EMAIL')).toBe('Used Gmail');
+ expect(describeToolCall({ name: 'GMAIL_SEND_EMAIL' }).chip).toBe('Send email');
+ expect(done('OUTLOOK_SEND_EMAIL')).toBe('Used Outlook');
+ expect(done('GOOGLECALENDAR_CREATE_EVENT')).toBe('Used Google Calendar');
+ expect(describeToolCall({ name: 'GOOGLECALENDAR_CREATE_EVENT' }).chip).toBe('Create event');
+ // An unknown toolkit still reads as words, not a slug.
+ expect(done('ACMECORP_SYNC_ALL_RECORDS')).toBe('Used Acmecorp');
+ expect(describeToolCall({ name: 'ACMECORP_SYNC_ALL_RECORDS' }).chip).toBe('Sync all records');
+ });
+
+ it('names the MCP tool and server instead of "Calling MCP tool"', () => {
+ const p = describeToolCall({
+ name: 'mcp_call_tool',
+ args: { server: 'linear', tool: 'create_issue' },
+ status: 'success',
+ });
+ expect(toolLabel(p)).toBe('Called create_issue');
+ expect(p.chip).toBe('linear');
+ expect(active('mcp_registry_tool_call', { server_id: 'gh', tool_name: 'list_prs' })).toBe(
+ 'Calling list_prs'
+ );
+ });
+
+ it('uses the past tense once a call settles', () => {
+ expect(active('file_read', { path: 'a.ts' })).toBe('Reading file');
+ expect(done('file_read', { path: 'a.ts' })).toBe('Read file');
+ expect(label({ name: 'file_read', status: 'error' })).toBe('Read file');
+ expect(active('web_search_tool')).toBe('Searching the web');
+ expect(done('web_search_tool')).toBe('Searched the web');
+ });
+
+ it('labels the search tool the core actually streams, not only its settings id', () => {
+ expect(done('web_search_tool', { query: 'rust' })).toBe('Searched the web');
+ expect(describeToolCall({ name: 'web_search_tool', args: { query: 'rust' } }).chip).toBe(
+ 'rust'
+ );
+ });
+
+ it('covers every bring-your-own-key search engine', () => {
+ for (const name of [
+ 'exa_search',
+ 'tavily_search',
+ 'querit_search',
+ 'parallel_search',
+ 'tinyfish_search',
+ ]) {
+ expect(done(name), name).toBe('Searched the web');
+ expect(describeToolCall({ name }).body, name).toBe('webSearch');
+ }
+ expect(done('brave_news_search')).toBe('Searched news');
+ expect(done('brave_image_search')).toBe('Searched images');
+ });
+
+ it('describes the deferred-tool bridge as the tool it calls', () => {
+ expect(done('tool_call', { name: 'SLACK_SEND_MESSAGE', arguments: {} })).toBe('Used Slack');
+ expect(done('tool_call', { name: 'file_read', arguments: { path: '/a/b/c/d.ts' } })).toBe(
+ 'Read file'
+ );
+ });
+
+ it('switches collapsed tools on their action argument', () => {
+ expect(done('memory', { action: 'recall', query: 'x' })).toBe('Recalled memories');
+ expect(done('memory', { action: 'store', key: 'k' })).toBe('Saved to memory');
+ expect(done('cron', { action: 'add', name: 'daily' })).toBe('Scheduled task');
+ expect(done('browser', { action: 'click', selector: '#go' })).toBe('Clicked');
+ });
+
+ it('labels named agents and delegations by what they do', () => {
+ expect(done('subagent:researcher')).toBe('Researched');
+ expect(done('spawn_subagent', { agent_id: 'critic' })).toBe('Reviewed the work');
+ expect(done('delegate_gmail')).toBe('Used Gmail');
+ expect(active('run_code')).toBe('Running code');
+ expect(
+ done('spawn_subagent', { agent_id: 'integrations_agent', toolkit: 'notion', prompt: 'p' })
+ ).toBe('Used Notion');
+ });
+
+ it('prefers the server label only for tools it cannot describe', () => {
+ // A known tool ignores the core's humanized label.
+ expect(done('file_read', {}, { serverLabel: 'File Read' })).toBe('Read file');
+ // An unknown tool takes a readable server label.
+ expect(done('frobnicate', {}, { serverLabel: 'Frobnicated the widget' })).toBe(
+ 'Frobnicated the widget'
+ );
+ // A server label that is itself a leaked identifier is not trusted.
+ expect(done('frobnicate', {}, { serverLabel: 'FROB NICATE' })).toBe('Used frobnicate');
+ expect(done('frobnicate', {}, { serverLabel: 'frob_nicate' })).toBe('Used frobnicate');
+ });
+
+ it('falls back to a sentence-cased, tense-aware label', () => {
+ expect(active('some_new_tool')).toBe('Using some new tool');
+ expect(done('some_new_tool')).toBe('Used some new tool');
+ expect(sentenceCase('GMAIL_SEND_EMAIL')).toBe('Gmail send email');
+ });
+
+ it('renders a degraded placeholder name without shouting', () => {
+ expect(done('tool')).toBe('Used tool');
+ expect(done('')).toBe('Used tool');
+ });
+});
+
+describe('tool chips', () => {
+ it('shortens paths and urls', () => {
+ expect(describeToolCall({ name: 'file_read', args: { path: '/a/b/c/d.ts' } }).chip).toBe(
+ '…/c/d.ts'
+ );
+ expect(
+ describeToolCall({ name: 'web_fetch', args: { url: 'https://docs.rs/tokio/latest' } }).chip
+ ).toBe('docs.rs/tokio/latest');
+ });
+
+ it('reads args from a JSON buffer', () => {
+ expect(describeToolCall({ name: 'shell', args: '{"command":"ls -la"}' }).chip).toBe('ls -la');
+ });
+
+ it('caps a chip so model text cannot blow up a row', () => {
+ const chip = describeToolCall({ name: 'grep', args: { pattern: 'x'.repeat(500) } }).chip;
+ expect(chip?.length).toBeLessThanOrEqual(80);
+ });
+});
+
+describe('tool labels: translation', () => {
+ it('serves the label through the phrase key and fills placeholders', () => {
+ const t = (key: string, fallback?: string) =>
+ key === 'conversations.tools.useApp.done' ? '{app} benutzt' : (fallback ?? key);
+ expect(toolLabel(describeToolCall({ name: 'GMAIL_SEND_EMAIL', status: 'success' }), t)).toBe(
+ 'Gmail benutzt'
+ );
+ });
+});
diff --git a/app/src/features/conversations/tools/toolPresentation.ts b/app/src/features/conversations/tools/toolPresentation.ts
new file mode 100644
index 00000000000..a44d3d1f78b
--- /dev/null
+++ b/app/src/features/conversations/tools/toolPresentation.ts
@@ -0,0 +1,393 @@
+/**
+ * The single answer to "how do we show this tool call?".
+ *
+ * Before this module four systems labelled tool calls and disagreed: a name
+ * table, an args-sniffing heuristic that called anything with a `query`
+ * argument "Searched the web", a category icon table, and the core's
+ * humanized name. Every surface (chat card, timeline, processing panel,
+ * status line, mascot) now resolves through {@link describeToolCall}.
+ *
+ * Resolution order, first hit wins:
+ *
+ * 1. `tool_call { name, arguments }`: the harness's deferred-tool bridge is
+ * described as the tool it invokes.
+ * 2. Named agents: `subagent:`, `spawn_subagent { agent_id }`,
+ * `delegate_` and custom delegate names.
+ * 3. Collapsed tools switching on an argument (`memory { action }`).
+ * 4. An exact entry in `toolSpecs.ts`.
+ * 5. A prefix family rule.
+ * 6. A Composio action slug (`GMAIL_SEND_EMAIL` → "Used Gmail · Send email").
+ * 7. The server's display label, for dynamic tools the client cannot know.
+ * 8. A sentence-cased fallback ("Used Frobnicate widget"). Never raw
+ * snake_case, never ALL CAPS.
+ *
+ * Pure and synchronous: safe in reducers, selectors and tests. Translation
+ * happens at the edge through {@link toolLabel} with the caller's `t`.
+ */
+import type { LucideIcon } from 'lucide-react';
+
+import { matchComposioActionSlug } from '../../../components/composio/toolkitMeta';
+import { chip as chipRules, genericChip, type ToolArgs, truncateChip } from './toolChips';
+import {
+ fillPlaceholders,
+ phraseKey,
+ TOOL_PHRASES,
+ type ToolPhraseId,
+ type ToolPhraseTense,
+} from './toolPhrases';
+import {
+ ACTION_TOOL_SPECS,
+ AGENT_SPECS,
+ EXACT_TOOL_SPECS,
+ FALLBACK_ICON,
+ FAMILY_TOOL_SPECS,
+ INTEGRATION_ICON,
+ INTEGRATIONS_AGENT_ID,
+ type ToolBodyKind,
+ type ToolCategory,
+ type ToolSpec,
+} from './toolSpecs';
+
+export type { ToolBodyKind, ToolCategory } from './toolSpecs';
+
+/** Mirrors `ToolTimelineEntryStatus`; kept local so this module has no store import. */
+export type ToolCallStatus = 'running' | 'success' | 'error' | 'awaiting_user' | 'cancelled';
+
+/** How a presentation was resolved. `fallback` is what the catalog test forbids. */
+export type ToolPresentationSource =
+ | 'exact'
+ | 'action'
+ | 'family'
+ | 'agent'
+ | 'integration'
+ | 'server'
+ | 'fallback';
+
+export interface DescribeToolCallInput {
+ /** Tool name as streamed; may carry a `subagent:` prefix. */
+ name: string;
+ /** Parsed args object, or the raw JSON args buffer. */
+ args?: unknown;
+ status?: ToolCallStatus;
+ /** `tool_display_label` from the core, for tools the client cannot know. */
+ serverLabel?: string;
+ /** `tool_display_detail` from the core. */
+ serverDetail?: string;
+ /**
+ * Connected-app slug known from context rather than args: a spawned
+ * `integrations_agent` row carries the `delegate_` tool that
+ * spawned it.
+ */
+ toolkitHint?: string;
+}
+
+export interface ToolCallPresentation {
+ /** Name with any `subagent:` prefix removed. */
+ baseName: string;
+ icon: LucideIcon;
+ category: ToolCategory;
+ body: ToolBodyKind;
+ tense: ToolPhraseTense;
+ /** Translatable phrase. Absent only when {@link literal} carries the label. */
+ phrase?: ToolPhraseId;
+ params?: Record;
+ /** Untranslatable label (a server-supplied one). */
+ literal?: string;
+ /** Short target beside the label: a path, query, host or app action. */
+ chip?: string;
+ /** Connected app, for Composio actions and the integrations agent. */
+ integration?: { slug: string; name: string; known: boolean };
+ source: ToolPresentationSource;
+}
+
+export type Translate = (key: string, fallback?: string) => string;
+
+const ACTIVE_STATUSES = new Set(['running', 'awaiting_user']);
+
+export function tenseForStatus(status: ToolCallStatus | undefined): ToolPhraseTense {
+ return !status || ACTIVE_STATUSES.has(status) ? 'active' : 'done';
+}
+
+/** Parse args from an object or a JSON buffer; anything else is `{}`. */
+export function parseToolArgs(args: unknown): ToolArgs {
+ if (args && typeof args === 'object' && !Array.isArray(args)) return args as ToolArgs;
+ if (typeof args !== 'string' || !args.trim()) return {};
+ try {
+ const parsed: unknown = JSON.parse(args);
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
+ ? (parsed as ToolArgs)
+ : {};
+ } catch {
+ return {};
+ }
+}
+
+/**
+ * `web_search_tool` → "Web search tool", `GMAIL_SEND_EMAIL` → "Gmail send
+ * email", `fooBar` → "Foo bar". Lower-cases everything after the first
+ * letter so a slug never renders shouting.
+ */
+export function sentenceCase(value: string): string {
+ const words = value
+ .replace(/^subagent:/, '')
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
+ .replace(/[_\-.:/]+/g, ' ')
+ .trim()
+ .toLowerCase();
+ return words.charAt(0).toUpperCase() + words.slice(1);
+}
+
+/** Is a server label readable as-is, or is it a leaked identifier? */
+function isReadableLabel(label: string, rawName: string): boolean {
+ const trimmed = label.trim();
+ if (!trimmed || trimmed.toLowerCase() === 'tool') return false;
+ if (trimmed === rawName) return false;
+ if (/[_]/.test(trimmed)) return false;
+ // Two or more consecutive ALL-CAPS words ("GMAIL SEND EMAIL").
+ if (/\b[A-Z]{2,}\b\s+\b[A-Z]{2,}\b/.test(trimmed)) return false;
+ return true;
+}
+
+function fromSpec(
+ spec: ToolSpec,
+ source: ToolPresentationSource,
+ baseName: string,
+ args: ToolArgs,
+ tense: ToolPhraseTense,
+ serverDetail: string | undefined
+): ToolCallPresentation {
+ return {
+ baseName,
+ icon: spec.icon,
+ category: spec.category,
+ body: spec.body ?? 'generic',
+ tense,
+ phrase: spec.phrase,
+ chip: spec.chip?.(args) ?? cleanDetail(serverDetail),
+ source,
+ };
+}
+
+function cleanDetail(detail: string | undefined): string | undefined {
+ return detail?.trim() ? truncateChip(detail) : undefined;
+}
+
+function agentPresentation(
+ agentId: string,
+ baseName: string,
+ args: ToolArgs,
+ tense: ToolPhraseTense,
+ serverDetail: string | undefined,
+ toolkitHint?: string
+): ToolCallPresentation | undefined {
+ if (agentId === INTEGRATIONS_AGENT_ID) {
+ const toolkit = typeof args.toolkit === 'string' ? args.toolkit : toolkitHint;
+ const app = toolkit ? integrationFromToolkit(toolkit) : undefined;
+ const prompt = typeof args.prompt === 'string' ? args.prompt : serverDetail;
+ return {
+ baseName,
+ icon: INTEGRATION_ICON,
+ category: 'app',
+ body: 'generic',
+ tense,
+ ...(app
+ ? { phrase: 'useApp' as const, params: { app: app.name }, integration: app }
+ : { phrase: 'checkConnectedApp' as const }),
+ chip: cleanDetail(prompt),
+ source: 'agent',
+ };
+ }
+ const spec = AGENT_SPECS[agentId];
+ if (!spec) return undefined;
+ const prompt = typeof args.prompt === 'string' ? args.prompt : undefined;
+ return {
+ ...fromSpec(spec, 'agent', baseName, args, tense, serverDetail),
+ chip: cleanDetail(serverDetail) ?? cleanDetail(prompt),
+ };
+}
+
+function integrationFromToolkit(
+ toolkit: string
+): { slug: string; name: string; known: boolean } | undefined {
+ const slug = toolkit.trim().toLowerCase();
+ if (!slug) return undefined;
+ // Reuse the action matcher's catalog lookup with a synthetic action.
+ const match = matchComposioActionSlug(`${slug.toUpperCase()}_X`);
+ return match ? { slug: match.slug, name: match.name, known: match.known } : undefined;
+}
+
+export function describeToolCall(input: DescribeToolCallInput): ToolCallPresentation {
+ const rawName = input.name?.trim() || 'tool';
+ const baseName = rawName.replace(/^subagent:/, '');
+ const args = parseToolArgs(input.args);
+ const tense = tenseForStatus(input.status);
+ const { serverDetail } = input;
+
+ // 1. Deferred-tool bridge: describe the tool it actually calls.
+ if (baseName === 'tool_call' && typeof args.name === 'string' && args.name.trim()) {
+ return describeToolCall({
+ ...input,
+ name: args.name,
+ args: args.arguments,
+ serverLabel: undefined,
+ });
+ }
+
+ // 2. Named agents.
+ if (rawName.startsWith('subagent:') || baseName === INTEGRATIONS_AGENT_ID) {
+ const hint = input.toolkitHint?.replace(/^delegate_/, '');
+ const agent = agentPresentation(baseName, baseName, args, tense, serverDetail, hint);
+ if (agent) return agent;
+ }
+ if (
+ (baseName === 'spawn_subagent' || baseName === 'spawn_async_subagent') &&
+ typeof args.agent_id === 'string'
+ ) {
+ const agent = agentPresentation(args.agent_id, baseName, args, tense, serverDetail);
+ if (agent) return agent;
+ }
+ if (baseName.startsWith('delegate_') && !EXACT_TOOL_SPECS[baseName]) {
+ const id = baseName.slice('delegate_'.length);
+ // An app named in the args (`delegate_tools_agent { toolkit: "github" }`)
+ // says more than the generic agent does, so it wins over the agent spec.
+ const argApp =
+ typeof args.toolkit === 'string' ? integrationFromToolkit(args.toolkit) : undefined;
+ const app = argApp?.known ? argApp : integrationFromToolkit(id);
+ const agent = argApp?.known
+ ? undefined
+ : agentPresentation(id, baseName, args, tense, serverDetail);
+ if (agent) return agent;
+ if (app?.known) {
+ return {
+ baseName,
+ icon: INTEGRATION_ICON,
+ category: 'app',
+ body: 'generic',
+ tense,
+ phrase: 'useApp',
+ params: { app: app.name },
+ integration: app,
+ chip: cleanDetail(typeof args.prompt === 'string' ? args.prompt : serverDetail),
+ source: 'agent',
+ };
+ }
+ return {
+ baseName,
+ icon: EXACT_TOOL_SPECS.delegate.icon,
+ category: 'agent',
+ body: 'generic',
+ tense,
+ phrase: 'delegateTask',
+ chip: sentenceCase(id),
+ source: 'agent',
+ };
+ }
+ if (AGENT_SPECS[baseName] && !EXACT_TOOL_SPECS[baseName]) {
+ const agent = agentPresentation(baseName, baseName, args, tense, serverDetail);
+ if (agent) return agent;
+ }
+
+ // 3. Collapsed tools that switch on an argument.
+ const action = ACTION_TOOL_SPECS[baseName];
+ if (action) {
+ const value = args[action.arg];
+ const actionSpec = typeof value === 'string' ? action.specs[value] : undefined;
+ if (actionSpec) return fromSpec(actionSpec, 'action', baseName, args, tense, serverDetail);
+ }
+
+ // 4. Exact entry.
+ const exact = EXACT_TOOL_SPECS[baseName];
+ if (exact) {
+ const presentation = fromSpec(exact, 'exact', baseName, args, tense, serverDetail);
+ if (baseName === 'mcp_call_tool' || baseName === 'mcp_registry_tool_call') {
+ const tool =
+ typeof args.tool === 'string'
+ ? args.tool
+ : typeof args.tool_name === 'string'
+ ? args.tool_name
+ : undefined;
+ if (tool?.trim()) presentation.params = { tool: truncateChip(tool, 48) };
+ else presentation.phrase = 'checkMcpTools';
+ }
+ if (baseName === 'composio_execute' && typeof args.tool === 'string') {
+ const match = matchComposioActionSlug(args.tool);
+ if (match) {
+ return {
+ ...presentation,
+ phrase: 'useApp',
+ params: { app: match.name },
+ integration: { slug: match.slug, name: match.name, known: match.known },
+ chip: match.action,
+ };
+ }
+ }
+ return presentation;
+ }
+
+ // 5. Prefix family.
+ const family = FAMILY_TOOL_SPECS.find(rule => rule.test.test(baseName));
+ if (family) return fromSpec(family.spec, 'family', baseName, args, tense, serverDetail);
+
+ // 6. Composio action slug.
+ const composio = matchComposioActionSlug(baseName);
+ if (composio) {
+ // The action names what was done; the server detail (or the obvious
+ // argument, e.g. a recipient) names what it was done to.
+ const target = cleanDetail(serverDetail) ?? genericChip(args);
+ return {
+ baseName,
+ icon: INTEGRATION_ICON,
+ category: 'app',
+ body: 'generic',
+ tense,
+ phrase: 'useApp',
+ params: { app: composio.name },
+ integration: { slug: composio.slug, name: composio.name, known: composio.known },
+ chip: target ? truncateChip(`${composio.action} · ${target}`) : composio.action,
+ source: 'integration',
+ };
+ }
+
+ // 7. Server label for a dynamic tool.
+ const serverLabel = input.serverLabel?.trim();
+ if (serverLabel && isReadableLabel(serverLabel, rawName)) {
+ return {
+ baseName,
+ icon: FALLBACK_ICON,
+ category: 'other',
+ body: 'generic',
+ tense,
+ literal: serverLabel,
+ chip: cleanDetail(serverDetail) ?? genericChip(args),
+ source: 'server',
+ };
+ }
+
+ // 8. Fallback.
+ return {
+ baseName,
+ icon: FALLBACK_ICON,
+ category: 'other',
+ body: 'generic',
+ tense,
+ phrase: 'useTool',
+ params: { tool: sentenceCase(baseName).toLowerCase() },
+ chip: cleanDetail(serverDetail) ?? genericChip(args),
+ source: 'fallback',
+ };
+}
+
+/** English label, for logs and non-React callers. */
+export function toolLabel(presentation: ToolCallPresentation, t?: Translate): string {
+ if (presentation.literal) return presentation.literal;
+ const id = presentation.phrase ?? 'useTool';
+ const english = TOOL_PHRASES[id][presentation.tense];
+ const template = t ? t(phraseKey(id, presentation.tense), english) : english;
+ const label = fillPlaceholders(template, presentation.params);
+ // `useTool` with a lower-cased tool name reads "Using frobnicate"; lift the
+ // first letter of the whole label only.
+ return label.charAt(0).toUpperCase() + label.slice(1);
+}
+
+/** Every chip rule, exported for the gallery and tests. */
+export const TOOL_CHIP_RULES = chipRules;
diff --git a/app/src/features/conversations/tools/toolSpecs.ts b/app/src/features/conversations/tools/toolSpecs.ts
new file mode 100644
index 00000000000..f9b896b0afe
--- /dev/null
+++ b/app/src/features/conversations/tools/toolSpecs.ts
@@ -0,0 +1,622 @@
+/**
+ * How each core tool is presented: icon, phrase, category, target chip and
+ * the rich body its detail panel renders.
+ *
+ * Resolution order lives in `toolPresentation.ts`; this file is only data.
+ * Three layers:
+ *
+ * - {@link EXACT_TOOL_SPECS}: one entry per registered tool name.
+ * - {@link ACTION_TOOL_SPECS}: collapsed tools that switch on an argument
+ * (`memory { action: "recall" }`, `browser { action: "click" }`).
+ * - {@link FAMILY_TOOL_SPECS}: prefix rules for tool families whose members
+ * share a meaning (`hosting_*`, `wallet_*`), so a new member of a known
+ * family is labelled without an edit here.
+ *
+ * `toolPresentation.catalog.test.ts` walks every name the core registers
+ * (`__fixtures__/coreToolNames.json`) and fails if any falls through to the
+ * generic fallback, so a new core tool cannot ship unlabelled.
+ */
+import {
+ AppWindowIcon,
+ ArchiveRestoreIcon,
+ ArrowLeftRightIcon,
+ BellIcon,
+ BlocksIcon,
+ BookOpenIcon,
+ BotIcon,
+ BrainCircuitIcon,
+ BrainIcon,
+ CalendarClockIcon,
+ CameraIcon,
+ ChartBarIcon,
+ ClapperboardIcon,
+ ClipboardCheckIcon,
+ ClockIcon,
+ CodeIcon,
+ CoinsIcon,
+ DatabaseIcon,
+ DownloadIcon,
+ EraserIcon,
+ FilePenIcon,
+ FilePlusIcon,
+ FileSpreadsheetIcon,
+ FileTextIcon,
+ FlagIcon,
+ FolderOpenIcon,
+ FolderSearchIcon,
+ GitBranchIcon,
+ GitCompareIcon,
+ GlobeIcon,
+ GraduationCapIcon,
+ HardDriveIcon,
+ HeartIcon,
+ HourglassIcon,
+ ImageIcon,
+ ImagePlusIcon,
+ KeyboardIcon,
+ LayersIcon,
+ Link2Icon,
+ LinkIcon,
+ ListChecksIcon,
+ ListTodoIcon,
+ type LucideIcon,
+ MailXIcon,
+ MapPinIcon,
+ MessageCircleQuestionIcon,
+ MessageSquareReplyIcon,
+ MousePointerClickIcon,
+ NetworkIcon,
+ NewspaperIcon,
+ PackageIcon,
+ PackagePlusIcon,
+ PackageSearchIcon,
+ PhoneIcon,
+ PlugIcon,
+ PodcastIcon,
+ PowerIcon,
+ PresentationIcon,
+ ReceiptIcon,
+ RocketIcon,
+ SaveIcon,
+ ScanEyeIcon,
+ ScanSearchIcon,
+ ScrollTextIcon,
+ ServerIcon,
+ SettingsIcon,
+ ShieldIcon,
+ SparklesIcon,
+ SquareTerminalIcon,
+ StethoscopeIcon,
+ TargetIcon,
+ TelescopeIcon,
+ TextSearchIcon,
+ TrendingUpIcon,
+ UserRoundIcon,
+ UsersIcon,
+ VideoIcon,
+ WalletIcon,
+ WorkflowIcon,
+ WrenchIcon,
+} from 'lucide-react';
+
+import { chip, type ChipRule } from './toolChips';
+import type { ToolPhraseId } from './toolPhrases';
+
+/** Broad activity category, used for grouping summaries and the timeline. */
+export type ToolCategory =
+ | 'file'
+ | 'code'
+ | 'shell'
+ | 'web'
+ | 'browser'
+ | 'media'
+ | 'memory'
+ | 'agent'
+ | 'plan'
+ | 'schedule'
+ | 'app'
+ | 'mcp'
+ | 'storage'
+ | 'wallet'
+ | 'skill'
+ | 'system'
+ | 'other';
+
+/** Which rich body the expanded row renders. `generic` is the Input/Output view. */
+export type ToolBodyKind = 'webSearch' | 'webFetch' | 'shell' | 'file' | 'mcp' | 'generic';
+
+export interface ToolSpec {
+ phrase: ToolPhraseId;
+ icon: LucideIcon;
+ category: ToolCategory;
+ chip?: ChipRule;
+ body?: ToolBodyKind;
+}
+
+const spec = (
+ phrase: ToolPhraseId,
+ icon: LucideIcon,
+ category: ToolCategory,
+ extra: Partial> = {}
+): ToolSpec => ({ phrase, icon, category, ...extra });
+
+const webSearch = (phrase: ToolPhraseId, icon: LucideIcon = GlobeIcon) =>
+ spec(phrase, icon, 'web', { chip: chip.query(), body: 'webSearch' });
+const readPages = spec('readPages', LinkIcon, 'web', { chip: chip.url(), body: 'webFetch' });
+
+export const FALLBACK_ICON = WrenchIcon;
+export const INTEGRATION_ICON = PlugIcon;
+
+export const EXACT_TOOL_SPECS: Record = {
+ // ── Files and code ──────────────────────────────────────────────────────
+ file_read: spec('readFile', FileTextIcon, 'file', { chip: chip.path(), body: 'file' }),
+ file_write: spec('writeFile', FilePlusIcon, 'file', { chip: chip.path(), body: 'file' }),
+ edit: spec('editFile', FilePenIcon, 'file', { chip: chip.path(), body: 'file' }),
+ apply_patch: spec('applyEdits', FilePenIcon, 'file', { chip: chip.editsPath(), body: 'file' }),
+ vault_write_markdown: spec('writeFile', FilePlusIcon, 'file', { chip: chip.path() }),
+ // Older and foreign spellings of the file tools, seen in persisted
+ // transcripts and other harnesses' tool names.
+ read_file: spec('readFile', FileTextIcon, 'file', { chip: chip.path(), body: 'file' }),
+ write_file: spec('writeFile', FilePlusIcon, 'file', { chip: chip.path(), body: 'file' }),
+ grep: spec('searchCode', TextSearchIcon, 'code', { chip: chip.text('pattern') }),
+ glob: spec('findFiles', FolderSearchIcon, 'file', { chip: chip.text('pattern') }),
+ list: spec('listFolder', FolderOpenIcon, 'file', { chip: chip.path() }),
+ csv_export: spec('exportCsv', FileSpreadsheetIcon, 'file', { chip: chip.text('filename') }),
+ update_memory_md: spec('updateMemoryNotes', ScrollTextIcon, 'memory', {
+ chip: chip.text('file'),
+ }),
+ git_operations: spec('runGit', GitBranchIcon, 'code', {
+ chip: chip.text('operation', 'command'),
+ }),
+ read_diff: spec('readChanges', GitCompareIcon, 'code', { chip: chip.path() }),
+ run_linter: spec('runLinter', ListChecksIcon, 'code'),
+ run_tests: spec('runTests', ListChecksIcon, 'code'),
+ lsp: spec('analyzeCode', CodeIcon, 'code', { chip: chip.path() }),
+ insert_sql_record: spec('insertRecord', DatabaseIcon, 'system', { chip: chip.text('table') }),
+
+ // ── Shell and system ────────────────────────────────────────────────────
+ shell: spec('runCommand', SquareTerminalIcon, 'shell', { chip: chip.command(), body: 'shell' }),
+ node_exec: spec('runCode', SquareTerminalIcon, 'shell', {
+ chip: chip.command('script_path', 'inline_code'),
+ body: 'shell',
+ }),
+ python_exec: spec('runCode', SquareTerminalIcon, 'shell', {
+ chip: chip.command('script_path', 'inline_code'),
+ body: 'shell',
+ }),
+ npm_exec: spec('runPackageManager', SquareTerminalIcon, 'shell', {
+ chip: chip.command('subcommand'),
+ body: 'shell',
+ }),
+ detect_tools: spec('checkInstalledTools', ScanSearchIcon, 'system'),
+ install_tool: spec('installTool', PackagePlusIcon, 'system', {
+ chip: chip.text('package', 'tool_name'),
+ }),
+ current_time: spec('checkTime', ClockIcon, 'system'),
+ resolve_time: spec('resolveDate', ClockIcon, 'system', { chip: chip.text('expr') }),
+ retrieve_tool_output: spec('retrieveOutput', ArchiveRestoreIcon, 'system'),
+ tinyjuice_retrieve: spec('retrieveOutput', ArchiveRestoreIcon, 'system'),
+ read_workspace_state: spec('reviewWorkspace', FolderOpenIcon, 'system'),
+ proxy_config: spec('configureProxy', SettingsIcon, 'system', { chip: chip.text('action') }),
+ update_check: spec('checkUpdates', DownloadIcon, 'system'),
+ update_apply: spec('installUpdate', DownloadIcon, 'system'),
+ pushover: spec('sendNotification', BellIcon, 'system', { chip: chip.text('title', 'message') }),
+ tool_stats: spec('reviewToolUsage', ChartBarIcon, 'system'),
+ keyboard: spec('typeKeys', KeyboardIcon, 'browser', { chip: chip.text('text', 'key') }),
+ mouse: spec('click', MousePointerClickIcon, 'browser'),
+
+ // ── Web ─────────────────────────────────────────────────────────────────
+ web_search: webSearch('searchWeb'),
+ web_search_tool: webSearch('searchWeb'),
+ exa_search: webSearch('searchWeb'),
+ tavily_search: webSearch('searchWeb'),
+ querit_search: webSearch('searchWeb'),
+ parallel_search: webSearch('searchWeb'),
+ tinyfish_search: webSearch('searchWeb'),
+ searxng_search: webSearch('searchWeb'),
+ seltz_search: webSearch('searchWeb'),
+ brave_news_search: webSearch('searchNews', NewspaperIcon),
+ brave_image_search: webSearch('searchImages', ImageIcon),
+ brave_video_search: webSearch('searchVideos', VideoIcon),
+ exa_find_similar: spec('findSimilarPages', GlobeIcon, 'web', {
+ chip: chip.url(),
+ body: 'webSearch',
+ }),
+ exa_get_contents: readPages,
+ tavily_extract: readPages,
+ parallel_extract: readPages,
+ tinyfish_fetch: readPages,
+ parallel_research: spec('research', TelescopeIcon, 'web', { chip: chip.query() }),
+ parallel_chat: spec('askTheWeb', GlobeIcon, 'web', { chip: chip.query() }),
+ parallel_enrich: spec('enrichData', SparklesIcon, 'web', { chip: chip.query() }),
+ parallel_dataset: spec('buildDataset', DatabaseIcon, 'web', { chip: chip.query() }),
+ tinyfish_agent_run: spec('browseForYou', MousePointerClickIcon, 'web', {
+ chip: chip.text('goal', 'url'),
+ }),
+ web_fetch: spec('readWebpage', LinkIcon, 'web', { chip: chip.url(), body: 'webFetch' }),
+ http_request: spec('callApi', ArrowLeftRightIcon, 'web', { chip: chip.url(), body: 'webFetch' }),
+ curl: spec('downloadFile', DownloadIcon, 'web', { chip: chip.url() }),
+ x402_request: spec('makePaidRequest', CoinsIcon, 'web', { chip: chip.url() }),
+ gitbooks_search: spec('searchDocs', BookOpenIcon, 'web', { chip: chip.query() }),
+ gitbooks_get_page: spec('readDocs', BookOpenIcon, 'web', { chip: chip.url() }),
+
+ // ── Browser ─────────────────────────────────────────────────────────────
+ browser: spec('useBrowser', AppWindowIcon, 'browser', { chip: chip.url() }),
+ browser_open: spec('openPage', AppWindowIcon, 'browser', { chip: chip.url() }),
+
+ // ── Media and documents ────────────────────────────────────────────────
+ image_info: spec('analyzeImage', ScanEyeIcon, 'media', { chip: chip.path() }),
+ media_generate_image: spec('generateImage', ImagePlusIcon, 'media', {
+ chip: chip.text('prompt'),
+ }),
+ media_generate_video: spec('generateVideo', ClapperboardIcon, 'media', {
+ chip: chip.text('prompt'),
+ }),
+ media_list_models: spec('checkMediaModels', ImageIcon, 'media'),
+ generate_document: spec('createDocument', FileTextIcon, 'media', { chip: chip.text('title') }),
+ generate_presentation: spec('createPresentation', PresentationIcon, 'media', {
+ chip: chip.text('title'),
+ }),
+ audio_generate_podcast: spec('generatePodcast', PodcastIcon, 'media', {
+ chip: chip.text('title', 'topic'),
+ }),
+ audio_email_podcast: spec('emailPodcast', PodcastIcon, 'media', { chip: chip.text('to') }),
+ audio_generate_and_email_podcast: spec('createAndEmailPodcast', PodcastIcon, 'media', {
+ chip: chip.text('title', 'topic'),
+ }),
+
+ // ── Memory ──────────────────────────────────────────────────────────────
+ memory: spec('searchMemory', BrainIcon, 'memory', { chip: chip.query() }),
+ memory_store: spec('saveToMemory', SaveIcon, 'memory', { chip: chip.text('key', 'content') }),
+ memory_recall: spec('recallMemories', BrainCircuitIcon, 'memory', { chip: chip.query() }),
+ memory_forget: spec('forgetMemory', EraserIcon, 'memory', { chip: chip.text('key') }),
+ memory_hybrid_search: spec('searchMemory', BrainIcon, 'memory', { chip: chip.query() }),
+ memory_vector_search: spec('searchMemory', BrainIcon, 'memory', { chip: chip.query() }),
+ memory_chunk_context: spec('inspectMemory', BrainIcon, 'memory'),
+ memory_store_raw_search: spec('searchMemory', BrainIcon, 'memory', { chip: chip.query() }),
+ memory_store_raw_chunks: spec('inspectMemory', BrainIcon, 'memory'),
+ memory_store_kinds: spec('inspectMemory', BrainIcon, 'memory'),
+ memory_doctor: spec('inspectMemory', StethoscopeIcon, 'memory'),
+ memory_flavour: spec('inspectMemory', BrainIcon, 'memory'),
+ memory_tree: spec('exploreMemory', NetworkIcon, 'memory', { chip: chip.query() }),
+ goals: spec('reviewGoals', TargetIcon, 'memory'),
+ remember_preference: spec('savePreference', HeartIcon, 'memory', {
+ chip: chip.text('preference', 'key'),
+ }),
+ save_preference: spec('savePreference', HeartIcon, 'memory', {
+ chip: chip.text('preference', 'key'),
+ }),
+ flow_memory_recall: spec('recallMemories', BrainCircuitIcon, 'memory', { chip: chip.query() }),
+ flow_memory_remember: spec('saveToMemory', SaveIcon, 'memory', { chip: chip.text('key') }),
+ memory_tools_list: spec('inspectMemory', BrainIcon, 'memory'),
+ memory_tools_put: spec('saveToMemory', SaveIcon, 'memory'),
+ call_memory_agent: spec('searchMemory', BrainIcon, 'memory', { chip: chip.query() }),
+
+ // ── Agents and delegation ──────────────────────────────────────────────
+ spawn_subagent: spec('delegateTask', BotIcon, 'agent', { chip: chip.text('agent_id') }),
+ spawn_async_subagent: spec('delegateTask', BotIcon, 'agent', { chip: chip.text('agent_id') }),
+ spawn_worker_thread: spec('delegateTask', BotIcon, 'agent', { chip: chip.text('agent_id') }),
+ delegate_graph: spec('delegateTask', BotIcon, 'agent', { chip: chip.text('agent_id') }),
+ delegate: spec('delegateTask', BotIcon, 'agent', { chip: chip.text('agent') }),
+ delegate_to: spec('delegateTask', BotIcon, 'agent', { chip: chip.text('agent', 'agent_id') }),
+ spawn_parallel_agents: spec('runAgentsInParallel', UsersIcon, 'agent'),
+ continue_subagent: spec('messageAgent', MessageSquareReplyIcon, 'agent', {
+ chip: chip.text('agent_id'),
+ }),
+ steer_subagent: spec('messageAgent', MessageSquareReplyIcon, 'agent', {
+ chip: chip.text('agent_id'),
+ }),
+ wait_subagent: spec('waitForAgent', HourglassIcon, 'agent'),
+ close_subagent: spec('closeAgent', BotIcon, 'agent'),
+ list_subagents: spec('checkAgents', BotIcon, 'agent'),
+ wait: spec('wait', HourglassIcon, 'agent'),
+ wait_loop: spec('wait', HourglassIcon, 'agent'),
+ ask_user_clarification: spec('askQuestion', MessageCircleQuestionIcon, 'agent', {
+ chip: chip.text('question'),
+ }),
+ agent_prepare_context: spec('prepareContext', LayersIcon, 'agent', {
+ chip: chip.text('question'),
+ }),
+ extract_from_result: spec('extractDetails', LayersIcon, 'agent'),
+
+ // ── Planning ────────────────────────────────────────────────────────────
+ todo: spec('updateTodos', ListTodoIcon, 'plan'),
+ request_plan_review: spec('requestPlanReview', ClipboardCheckIcon, 'plan'),
+ plan_exit: spec('finishPlan', ClipboardCheckIcon, 'plan'),
+ goal_set: spec('setGoal', FlagIcon, 'plan', { chip: chip.text('objective') }),
+ goal_get: spec('checkGoal', FlagIcon, 'plan'),
+ goal_complete: spec('completeGoal', FlagIcon, 'plan'),
+
+ // ── Scheduling ──────────────────────────────────────────────────────────
+ cron: spec('checkSchedules', CalendarClockIcon, 'schedule'),
+ cron_add: spec('scheduleTask', CalendarClockIcon, 'schedule', { chip: chip.text('name') }),
+ cron_list: spec('checkSchedules', CalendarClockIcon, 'schedule'),
+ cron_update: spec('updateSchedule', CalendarClockIcon, 'schedule', { chip: chip.text('name') }),
+ cron_remove: spec('removeSchedule', CalendarClockIcon, 'schedule'),
+ cron_run: spec('runScheduledTask', CalendarClockIcon, 'schedule'),
+ cron_runs: spec('checkRunHistory', CalendarClockIcon, 'schedule'),
+ schedule: spec('scheduleTask', CalendarClockIcon, 'schedule'),
+
+ // ── Connected apps ─────────────────────────────────────────────────────
+ composio_list_toolkits: spec('checkAvailableApps', PlugIcon, 'app'),
+ composio_list_connections: spec('checkConnections', PlugIcon, 'app'),
+ composio_connect: spec('connectApp', Link2Icon, 'app', { chip: chip.text('toolkit') }),
+ composio_authorize: spec('authorizeApp', Link2Icon, 'app', { chip: chip.text('toolkit') }),
+ composio_list_tools: spec('findAppActions', PlugIcon, 'app', {
+ chip: chip.text('toolkits', 'toolkit'),
+ }),
+ composio_execute: spec('runAppAction', PlugIcon, 'app', { chip: chip.text('tool') }),
+ tool_search: spec('findTools', PackageSearchIcon, 'system', { chip: chip.query() }),
+ // The deferred-tool bridge is described as the tool it calls
+ // (`toolPresentation.ts`); this entry covers it before its args arrive.
+ tool_call: spec('useTools', WrenchIcon, 'system', { chip: chip.text('name') }),
+ search_tool_catalog: spec('findTools', PackageSearchIcon, 'system', { chip: chip.query() }),
+ gmail_unsubscribe: spec('unsubscribe', MailXIcon, 'app', { chip: chip.text('sender', 'email') }),
+ google_places_search: spec('searchPlaces', MapPinIcon, 'app', { chip: chip.query() }),
+ google_places_details: spec('lookUpPlace', MapPinIcon, 'app', { chip: chip.text('place_id') }),
+ twilio_call: spec('placeCall', PhoneIcon, 'app', { chip: chip.text('to') }),
+
+ // ── MCP ─────────────────────────────────────────────────────────────────
+ mcp_list_servers: spec('checkMcpServers', ServerIcon, 'mcp'),
+ mcp_list_tools: spec('checkMcpTools', BlocksIcon, 'mcp', { chip: chip.text('server') }),
+ mcp_call_tool: spec('callMcpTool', BlocksIcon, 'mcp', { chip: chip.text('server'), body: 'mcp' }),
+ mcp_registry_tool_call: spec('callMcpTool', BlocksIcon, 'mcp', {
+ chip: chip.text('server_id'),
+ body: 'mcp',
+ }),
+ mcp_registry_search: spec('searchMcpServers', ServerIcon, 'mcp', { chip: chip.query() }),
+ mcp_registry_get: spec('checkMcpServers', ServerIcon, 'mcp', {
+ chip: chip.text('qualified_name'),
+ }),
+ mcp_registry_installed_list: spec('checkMcpServers', ServerIcon, 'mcp'),
+ mcp_registry_status: spec('checkMcpServers', ServerIcon, 'mcp'),
+ mcp_registry_list_tools: spec('checkMcpTools', BlocksIcon, 'mcp', {
+ chip: chip.text('server_id'),
+ }),
+ mcp_registry_connect: spec('connectMcpServer', ServerIcon, 'mcp', {
+ chip: chip.text('qualified_name', 'server_id'),
+ }),
+ mcp_registry_disconnect: spec('disconnectMcpServer', ServerIcon, 'mcp', {
+ chip: chip.text('server_id'),
+ }),
+ mcp_registry_uninstall: spec('removeMcpServer', ServerIcon, 'mcp', {
+ chip: chip.text('server_id'),
+ }),
+
+ // ── Storage and hosting ────────────────────────────────────────────────
+ storage_upload_file: spec('uploadFile', HardDriveIcon, 'storage', { chip: chip.path() }),
+ storage_download_file: spec('downloadFile', HardDriveIcon, 'storage', {
+ chip: chip.text('key', 'name'),
+ }),
+ storage_list_files: spec('listStoredFiles', HardDriveIcon, 'storage'),
+ storage_get_link: spec('createShareLink', LinkIcon, 'storage', {
+ chip: chip.text('key', 'name'),
+ }),
+ storage_delete_file: spec('deleteFile', HardDriveIcon, 'storage', {
+ chip: chip.text('key', 'name'),
+ }),
+ storage_set_visibility: spec('updateFileAccess', HardDriveIcon, 'storage', {
+ chip: chip.text('key', 'name'),
+ }),
+ hosting_launch_site: spec('deploySite', RocketIcon, 'storage', { chip: chip.text('name') }),
+ hosting_rollback: spec('rollBackDeployment', RocketIcon, 'storage'),
+
+ // ── Wallet ──────────────────────────────────────────────────────────────
+ wallet_prepare_transfer: spec('prepareTransfer', WalletIcon, 'wallet', { chip: chip.text('to') }),
+ web3_swap_quote: spec('getSwapQuote', ArrowLeftRightIcon, 'wallet'),
+ web3_swap_routes: spec('getSwapQuote', ArrowLeftRightIcon, 'wallet'),
+ web3_swap_execute: spec('swapTokens', ArrowLeftRightIcon, 'wallet'),
+ web3_bridge_quote: spec('getBridgeQuote', ArrowLeftRightIcon, 'wallet'),
+ web3_bridge_execute: spec('bridgeTokens', ArrowLeftRightIcon, 'wallet'),
+ web3_dapp_call: spec('callDapp', CoinsIcon, 'wallet'),
+ web3_dapp_execute: spec('callDapp', CoinsIcon, 'wallet'),
+
+ // ── Skills and workflows ───────────────────────────────────────────────
+ use_skill: spec('useSkill', SparklesIcon, 'skill', { chip: chip.text('skill') }),
+ skill_search: spec('searchSkills', SparklesIcon, 'skill', { chip: chip.query() }),
+ create_skill: spec('createSkill', SparklesIcon, 'skill', { chip: chip.text('name') }),
+ install_workflow_from_url: spec('installSkill', SparklesIcon, 'skill', { chip: chip.url() }),
+ uninstall_workflow: spec('removeSkill', SparklesIcon, 'skill', { chip: chip.text('name', 'id') }),
+ run_workflow: spec('runWorkflow', WorkflowIcon, 'skill', { chip: chip.text('workflow_id') }),
+ await_workflow: spec('waitForWorkflow', HourglassIcon, 'skill'),
+ run_flow: spec('runWorkflow', WorkflowIcon, 'skill', { chip: chip.text('name', 'flow_id') }),
+ propose_workflow: spec('designWorkflow', WorkflowIcon, 'skill', { chip: chip.text('name') }),
+ revise_workflow: spec('designWorkflow', WorkflowIcon, 'skill'),
+ edit_workflow: spec('designWorkflow', WorkflowIcon, 'skill'),
+ create_workflow: spec('designWorkflow', WorkflowIcon, 'skill', { chip: chip.text('name') }),
+ duplicate_flow: spec('saveWorkflow', WorkflowIcon, 'skill'),
+ save_workflow: spec('saveWorkflow', WorkflowIcon, 'skill', { chip: chip.text('name') }),
+ validate_workflow: spec('validateWorkflow', WorkflowIcon, 'skill'),
+ dry_run_workflow: spec('testWorkflow', WorkflowIcon, 'skill'),
+ cancel_flow_run: spec('cancelWorkflow', WorkflowIcon, 'skill'),
+ resume_flow_run: spec('runWorkflow', WorkflowIcon, 'skill'),
+ suggest_workflows: spec('suggestWorkflows', WorkflowIcon, 'skill'),
+
+ // ── Settings and platform ──────────────────────────────────────────────
+ security_policy_info: spec('checkSecurity', ShieldIcon, 'system'),
+ credential_list: spec('checkSecurity', ShieldIcon, 'system'),
+ session_state: spec('checkSecurity', ShieldIcon, 'system'),
+ oauth_connect_url: spec('connectApp', Link2Icon, 'app', { chip: chip.text('provider') }),
+ oauth_list: spec('checkConnections', PlugIcon, 'app'),
+ dashboard_model_health: spec('runDiagnostics', StethoscopeIcon, 'system'),
+ workspace_read_persona: spec('readPersona', UserRoundIcon, 'system'),
+ workspace_update_persona: spec('updatePersona', UserRoundIcon, 'system'),
+ workspace_reset_persona: spec('updatePersona', UserRoundIcon, 'system'),
+ workspace_init: spec('setUpWorkspace', FolderOpenIcon, 'system'),
+ artifact_delete: spec('deleteArtifact', PackageIcon, 'system'),
+};
+
+/**
+ * Collapsed tools that do different things per argument. Keyed by tool name,
+ * then by the argument named in `arg`. A value the table does not list falls
+ * back to the tool's {@link EXACT_TOOL_SPECS} entry.
+ */
+export const ACTION_TOOL_SPECS: Record }> = {
+ memory: {
+ arg: 'action',
+ specs: {
+ recall: spec('recallMemories', BrainCircuitIcon, 'memory', { chip: chip.query() }),
+ store: spec('saveToMemory', SaveIcon, 'memory', { chip: chip.text('key', 'content') }),
+ forget: spec('forgetMemory', EraserIcon, 'memory', { chip: chip.text('key') }),
+ hybrid_search: spec('searchMemory', BrainIcon, 'memory', { chip: chip.query() }),
+ vector_search: spec('searchMemory', BrainIcon, 'memory', { chip: chip.query() }),
+ raw_search: spec('searchMemory', BrainIcon, 'memory', { chip: chip.query() }),
+ chunk_context: spec('inspectMemory', BrainIcon, 'memory'),
+ raw_chunks: spec('inspectMemory', BrainIcon, 'memory'),
+ kinds: spec('inspectMemory', BrainIcon, 'memory'),
+ flavour: spec('inspectMemory', BrainIcon, 'memory'),
+ doctor: spec('inspectMemory', StethoscopeIcon, 'memory'),
+ },
+ },
+ memory_tree: {
+ arg: 'mode',
+ specs: {
+ ingest_document: spec('saveDocumentToMemory', SaveIcon, 'memory', {
+ chip: chip.text('title', 'path'),
+ }),
+ },
+ },
+ goals: {
+ arg: 'op',
+ specs: {
+ list: spec('reviewGoals', TargetIcon, 'memory'),
+ add: spec('updateGoals', TargetIcon, 'memory', { chip: chip.text('text', 'goal') }),
+ edit: spec('updateGoals', TargetIcon, 'memory', { chip: chip.text('text', 'goal') }),
+ delete: spec('updateGoals', TargetIcon, 'memory'),
+ },
+ },
+ cron: {
+ arg: 'action',
+ specs: {
+ list: spec('checkSchedules', CalendarClockIcon, 'schedule'),
+ add: spec('scheduleTask', CalendarClockIcon, 'schedule', { chip: chip.text('name') }),
+ update: spec('updateSchedule', CalendarClockIcon, 'schedule', { chip: chip.text('name') }),
+ remove: spec('removeSchedule', CalendarClockIcon, 'schedule'),
+ run: spec('runScheduledTask', CalendarClockIcon, 'schedule'),
+ runs: spec('checkRunHistory', CalendarClockIcon, 'schedule'),
+ },
+ },
+ schedule: {
+ arg: 'action',
+ specs: {
+ list: spec('checkSchedules', CalendarClockIcon, 'schedule'),
+ get: spec('checkSchedules', CalendarClockIcon, 'schedule'),
+ cancel: spec('removeSchedule', CalendarClockIcon, 'schedule'),
+ remove: spec('removeSchedule', CalendarClockIcon, 'schedule'),
+ pause: spec('updateSchedule', CalendarClockIcon, 'schedule'),
+ resume: spec('updateSchedule', CalendarClockIcon, 'schedule'),
+ },
+ },
+ browser: {
+ arg: 'action',
+ specs: {
+ open: spec('openPage', AppWindowIcon, 'browser', { chip: chip.url() }),
+ snapshot: spec('takeScreenshot', CameraIcon, 'browser'),
+ click: spec('click', MousePointerClickIcon, 'browser', { chip: chip.text('selector') }),
+ mouse_click: spec('click', MousePointerClickIcon, 'browser'),
+ hover: spec('click', MousePointerClickIcon, 'browser', { chip: chip.text('selector') }),
+ fill: spec('typeKeys', KeyboardIcon, 'browser', { chip: chip.text('selector') }),
+ type: spec('typeKeys', KeyboardIcon, 'browser', { chip: chip.text('selector') }),
+ key_type: spec('typeKeys', KeyboardIcon, 'browser'),
+ key_press: spec('typeKeys', KeyboardIcon, 'browser', { chip: chip.text('key') }),
+ press: spec('typeKeys', KeyboardIcon, 'browser', { chip: chip.text('key') }),
+ scroll: spec('scrollPage', AppWindowIcon, 'browser'),
+ get_text: spec('readPage', AppWindowIcon, 'browser'),
+ get_title: spec('readPage', AppWindowIcon, 'browser'),
+ get_url: spec('readPage', AppWindowIcon, 'browser'),
+ find: spec('readPage', AppWindowIcon, 'browser', { chip: chip.text('value', 'selector') }),
+ is_visible: spec('readPage', AppWindowIcon, 'browser'),
+ wait: spec('wait', HourglassIcon, 'browser'),
+ },
+ },
+};
+
+/**
+ * Prefix families. Ordered: the first matching rule wins, so a narrower rule
+ * must precede a broader one that shares its prefix.
+ */
+export const FAMILY_TOOL_SPECS: ReadonlyArray<{ test: RegExp; spec: ToolSpec }> = [
+ { test: /^memory_/, spec: spec('searchMemory', BrainIcon, 'memory', { chip: chip.query() }) },
+ {
+ test: /^learning_(update|pin|unpin|forget|rebuild|reset|save|enrich)/,
+ spec: spec('updateLearnings', GraduationCapIcon, 'memory'),
+ },
+ { test: /^learning_/, spec: spec('reviewLearnings', GraduationCapIcon, 'memory') },
+ {
+ test: /^skill_registry_(install)/,
+ spec: spec('installSkill', SparklesIcon, 'skill', { chip: chip.text('name', 'id') }),
+ },
+ { test: /^skill_registry_uninstall/, spec: spec('removeSkill', SparklesIcon, 'skill') },
+ {
+ test: /^skill_registry_search/,
+ spec: spec('searchSkills', SparklesIcon, 'skill', { chip: chip.query() }),
+ },
+ { test: /^(skill_|skill_runtime_)/, spec: spec('checkSkills', SparklesIcon, 'skill') },
+ {
+ test: /^(list_workflows|describe_workflow|read_workflow_|list_workflow_runs|list_flows|get_flow|list_flow_|get_tool_|list_agent_definitions|list_connectable_toolkits|list_node_kinds|get_node_kind_contract)/,
+ spec: spec('checkWorkflows', WorkflowIcon, 'skill'),
+ },
+ {
+ test: /^task_source_(add|update|remove)/,
+ spec: spec('updateTaskSources', ListChecksIcon, 'app'),
+ },
+ { test: /^task_source_(fetch|list_tasks)/, spec: spec('fetchTasks', ListChecksIcon, 'app') },
+ { test: /^task_source_/, spec: spec('checkTaskSources', ListChecksIcon, 'app') },
+ { test: /^hosting_(set_env|add_domain)/, spec: spec('updateHosting', RocketIcon, 'storage') },
+ { test: /^hosting_/, spec: spec('checkHosting', RocketIcon, 'storage') },
+ { test: /^storage_/, spec: spec('listStoredFiles', HardDriveIcon, 'storage') },
+ {
+ test: /^stock_/,
+ spec: spec('checkMarkets', TrendingUpIcon, 'app', { chip: chip.text('symbol') }),
+ },
+ {
+ test: /^wallet_(tx_|lookup_tx)/,
+ spec: spec('checkTransaction', WalletIcon, 'wallet', { chip: chip.text('tx_hash', 'hash') }),
+ },
+ { test: /^(wallet_|web3_)/, spec: spec('checkWallet', WalletIcon, 'wallet') },
+ { test: /^composio_/, spec: spec('runAppAction', PlugIcon, 'app') },
+ { test: /^mcp_/, spec: spec('checkMcpServers', ServerIcon, 'mcp') },
+ { test: /^config_/, spec: spec('checkSettings', SettingsIcon, 'system') },
+ { test: /^(daemon_host_prefs_|service_)/, spec: spec('manageService', PowerIcon, 'system') },
+ { test: /^(doctor_|health_)/, spec: spec('runDiagnostics', StethoscopeIcon, 'system') },
+ { test: /^cost_/, spec: spec('checkUsageCosts', ReceiptIcon, 'system') },
+ { test: /^artifact_/, spec: spec('checkArtifacts', PackageIcon, 'system') },
+ { test: /^cron_/, spec: spec('checkSchedules', CalendarClockIcon, 'schedule') },
+ { test: /^goal_/, spec: spec('checkGoal', FlagIcon, 'plan') },
+];
+
+/**
+ * Named agents, reached as `subagent:`, as `spawn_subagent { agent_id }`,
+ * as `delegate_`, or as the custom delegate tool names agent TOMLs
+ * declare (`delegate_name`).
+ */
+export const AGENT_SPECS: Record = {
+ researcher: spec('research', TelescopeIcon, 'agent'),
+ research: spec('research', TelescopeIcon, 'agent'),
+ context_scout: spec('scoutContext', LayersIcon, 'agent'),
+ orchestrator: spec('planNextSteps', BotIcon, 'agent'),
+ plan: spec('planNextSteps', BotIcon, 'agent'),
+ planner: spec('planNextSteps', BotIcon, 'agent'),
+ critic: spec('reviewWork', ClipboardCheckIcon, 'agent'),
+ review_code: spec('reviewWork', ClipboardCheckIcon, 'agent'),
+ tools_agent: spec('useTools', WrenchIcon, 'agent'),
+ code_executor: spec('runCode', SquareTerminalIcon, 'agent'),
+ run_code: spec('runCode', SquareTerminalIcon, 'agent'),
+ ask_docs: spec('searchDocs', BookOpenIcon, 'agent'),
+ create_image: spec('generateImage', ImagePlusIcon, 'agent'),
+ create_video: spec('generateVideo', ClapperboardIcon, 'agent'),
+ analyze_image: spec('analyzeImage', ScanEyeIcon, 'agent'),
+ make_presentation: spec('createPresentation', PresentationIcon, 'agent'),
+ do_crypto: spec('checkWallet', WalletIcon, 'agent'),
+ schedule_task: spec('scheduleTask', CalendarClockIcon, 'agent'),
+ manage_tasks: spec('checkTaskSources', ListChecksIcon, 'agent'),
+ manage_settings: spec('checkSettings', SettingsIcon, 'agent'),
+ use_mcp_server: spec('checkMcpTools', BlocksIcon, 'agent'),
+ curate_goals: spec('updateGoals', TargetIcon, 'agent'),
+ manage_profile_memory: spec('updateLearnings', GraduationCapIcon, 'agent'),
+ archive_session: spec('saveToMemory', SaveIcon, 'agent'),
+ retrieve_flow_context: spec('prepareContext', LayersIcon, 'agent'),
+};
+
+/** The integrations agent: labelled by the app it works in, when known. */
+export const INTEGRATIONS_AGENT_ID = 'integrations_agent';
diff --git a/app/src/features/human/SubMascotLayer.test.tsx b/app/src/features/human/SubMascotLayer.test.tsx
index c2a0285cd0b..7203056dd9a 100644
--- a/app/src/features/human/SubMascotLayer.test.tsx
+++ b/app/src/features/human/SubMascotLayer.test.tsx
@@ -76,7 +76,7 @@ describe('subMascotModelsFromTimeline', () => {
}),
]);
- expect(running?.activity).toBe('Using Read File');
+ expect(running?.activity).toBe('Reading file');
expect(running?.face).toBe('thinking');
// success and error are filtered out — only 1 model returned.
expect(
@@ -133,7 +133,9 @@ describe('subMascotModelsFromTimeline', () => {
}),
]);
- expect(models[0].activity).toBe('Using Fetching');
+ // The label is already an activity; the old "Using " prefix made this
+ // "Using Fetching".
+ expect(models[0].activity).toBe('Reading webpage');
});
});
diff --git a/app/src/features/human/SubMascotLayer.tsx b/app/src/features/human/SubMascotLayer.tsx
index 6cf9803159e..4a18335f8bc 100644
--- a/app/src/features/human/SubMascotLayer.tsx
+++ b/app/src/features/human/SubMascotLayer.tsx
@@ -85,7 +85,9 @@ function activityForEntry(entry: ToolTimelineEntry): string {
const lastRunningTool = [...subagent.toolCalls].reverse().find(call => call.status === 'running');
if (lastRunningTool) {
- return `Using ${formatToolName(lastRunningTool.toolName)}`;
+ // The label is already a present-tense activity ("Searching the web");
+ // prefixing "Using" produced "Using Searching the web".
+ return formatToolName(lastRunningTool.toolName);
}
if (subagent.childIteration) {
diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts
index a07ba02958b..06f60457a33 100644
--- a/app/src/lib/i18n/ar.ts
+++ b/app/src/lib/i18n/ar.ts
@@ -3239,6 +3239,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'لا يوجد ناتج بعد',
'conversations.subagent.input': 'المدخلات',
'conversations.subagent.output': 'المخرجات',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} خطوة',
+ 'conversations.tools.steps.other': '{count} خطوات',
+ 'conversations.tools.working': 'جارٍ العمل',
+ 'conversations.tools.noOutput': 'لا توجد مخرجات',
+ 'conversations.tools.delegatedTo': 'تم التفويض إلى {agent}',
+ 'conversations.tools.openInBrowser': 'فتح في المتصفح',
+ 'conversations.tools.status.running': 'قيد التشغيل',
+ 'conversations.tools.status.done': 'مكتمل',
+ 'conversations.tools.status.failed': 'فشل',
+ 'conversations.tools.status.cancelled': 'ملغى',
+ 'conversations.tools.status.awaiting': 'بانتظار الإدخال',
+ 'conversations.tools.search.searching': 'جارٍ البحث',
+ 'conversations.tools.search.none': 'لا توجد نتائج',
+ 'conversations.tools.search.found.one': 'تم العثور على {count} نتيجة',
+ 'conversations.tools.search.found.other': 'تم العثور على {count} نتائج',
+ 'conversations.tools.search.via': 'عبر {provider}',
+ 'conversations.tools.readFile.active': 'جارٍ قراءة الملف',
+ 'conversations.tools.readFile.done': 'تمت قراءة الملف',
+ 'conversations.tools.writeFile.active': 'جارٍ كتابة الملف',
+ 'conversations.tools.writeFile.done': 'تمت كتابة الملف',
+ 'conversations.tools.editFile.active': 'جارٍ تعديل الملف',
+ 'conversations.tools.editFile.done': 'تم تعديل الملف',
+ 'conversations.tools.applyEdits.active': 'جارٍ تطبيق التعديلات',
+ 'conversations.tools.applyEdits.done': 'تم تطبيق التعديلات',
+ 'conversations.tools.searchCode.active': 'جارٍ البحث في الكود',
+ 'conversations.tools.searchCode.done': 'تم البحث في الكود',
+ 'conversations.tools.findFiles.active': 'جارٍ البحث عن الملفات',
+ 'conversations.tools.findFiles.done': 'تم العثور على الملفات',
+ 'conversations.tools.listFolder.active': 'جارٍ عرض محتويات المجلد',
+ 'conversations.tools.listFolder.done': 'تم عرض محتويات المجلد',
+ 'conversations.tools.exportCsv.active': 'جارٍ تصدير CSV',
+ 'conversations.tools.exportCsv.done': 'تم تصدير CSV',
+ 'conversations.tools.updateMemoryNotes.active': 'جارٍ تحديث ملاحظات الذاكرة',
+ 'conversations.tools.updateMemoryNotes.done': 'تم تحديث ملاحظات الذاكرة',
+ 'conversations.tools.runGit.active': 'جارٍ تشغيل git',
+ 'conversations.tools.runGit.done': 'تم تشغيل git',
+ 'conversations.tools.readChanges.active': 'جارٍ قراءة التغييرات',
+ 'conversations.tools.readChanges.done': 'تمت قراءة التغييرات',
+ 'conversations.tools.runLinter.active': 'جارٍ تشغيل أداة فحص الكود',
+ 'conversations.tools.runLinter.done': 'تم تشغيل أداة فحص الكود',
+ 'conversations.tools.runTests.active': 'جارٍ تشغيل الاختبارات',
+ 'conversations.tools.runTests.done': 'تم تشغيل الاختبارات',
+ 'conversations.tools.analyzeCode.active': 'جارٍ تحليل الكود',
+ 'conversations.tools.analyzeCode.done': 'تم تحليل الكود',
+ 'conversations.tools.insertRecord.active': 'جارٍ إدراج سجل',
+ 'conversations.tools.insertRecord.done': 'تم إدراج السجل',
+ 'conversations.tools.runCommand.active': 'جارٍ تشغيل الأمر',
+ 'conversations.tools.runCommand.done': 'تم تشغيل الأمر',
+ 'conversations.tools.runCode.active': 'جارٍ تشغيل الكود',
+ 'conversations.tools.runCode.done': 'تم تشغيل الكود',
+ 'conversations.tools.runPackageManager.active': 'جارٍ تشغيل npm',
+ 'conversations.tools.runPackageManager.done': 'تم تشغيل npm',
+ 'conversations.tools.checkInstalledTools.active': 'جارٍ فحص الأدوات المثبتة',
+ 'conversations.tools.checkInstalledTools.done': 'تم فحص الأدوات المثبتة',
+ 'conversations.tools.installTool.active': 'جارٍ تثبيت الأداة',
+ 'conversations.tools.installTool.done': 'تم تثبيت الأداة',
+ 'conversations.tools.checkTime.active': 'جارٍ معرفة الوقت',
+ 'conversations.tools.checkTime.done': 'تمت معرفة الوقت',
+ 'conversations.tools.resolveDate.active': 'جارٍ تحديد التاريخ',
+ 'conversations.tools.resolveDate.done': 'تم تحديد التاريخ',
+ 'conversations.tools.retrieveOutput.active': 'جارٍ جلب المخرجات الكاملة',
+ 'conversations.tools.retrieveOutput.done': 'تم جلب المخرجات الكاملة',
+ 'conversations.tools.reviewWorkspace.active': 'جارٍ مراجعة مساحة العمل',
+ 'conversations.tools.reviewWorkspace.done': 'تمت مراجعة مساحة العمل',
+ 'conversations.tools.configureProxy.active': 'جارٍ إعداد الوكيل',
+ 'conversations.tools.configureProxy.done': 'تم إعداد الوكيل',
+ 'conversations.tools.checkUpdates.active': 'جارٍ البحث عن تحديثات',
+ 'conversations.tools.checkUpdates.done': 'تم البحث عن تحديثات',
+ 'conversations.tools.installUpdate.active': 'جارٍ تثبيت التحديث',
+ 'conversations.tools.installUpdate.done': 'تم تثبيت التحديث',
+ 'conversations.tools.sendNotification.active': 'جارٍ إرسال إشعار',
+ 'conversations.tools.sendNotification.done': 'تم إرسال الإشعار',
+ 'conversations.tools.reviewToolUsage.active': 'جارٍ مراجعة استخدام الأدوات',
+ 'conversations.tools.reviewToolUsage.done': 'تمت مراجعة استخدام الأدوات',
+ 'conversations.tools.typeKeys.active': 'جارٍ الكتابة',
+ 'conversations.tools.typeKeys.done': 'تمت الكتابة',
+ 'conversations.tools.click.active': 'جارٍ النقر',
+ 'conversations.tools.click.done': 'تم النقر',
+ 'conversations.tools.searchWeb.active': 'جارٍ البحث في الويب',
+ 'conversations.tools.searchWeb.done': 'تم البحث في الويب',
+ 'conversations.tools.searchNews.active': 'جارٍ البحث في الأخبار',
+ 'conversations.tools.searchNews.done': 'تم البحث في الأخبار',
+ 'conversations.tools.searchImages.active': 'جارٍ البحث عن الصور',
+ 'conversations.tools.searchImages.done': 'تم البحث عن الصور',
+ 'conversations.tools.searchVideos.active': 'جارٍ البحث عن الفيديوهات',
+ 'conversations.tools.searchVideos.done': 'تم البحث عن الفيديوهات',
+ 'conversations.tools.findSimilarPages.active': 'جارٍ البحث عن صفحات مشابهة',
+ 'conversations.tools.findSimilarPages.done': 'تم العثور على صفحات مشابهة',
+ 'conversations.tools.readPages.active': 'جارٍ قراءة الصفحات',
+ 'conversations.tools.readPages.done': 'تمت قراءة الصفحات',
+ 'conversations.tools.readWebpage.active': 'جارٍ قراءة صفحة الويب',
+ 'conversations.tools.readWebpage.done': 'تمت قراءة صفحة الويب',
+ 'conversations.tools.research.active': 'جارٍ البحث والتقصي',
+ 'conversations.tools.research.done': 'تم البحث والتقصي',
+ 'conversations.tools.enrichData.active': 'جارٍ إثراء البيانات',
+ 'conversations.tools.enrichData.done': 'تم إثراء البيانات',
+ 'conversations.tools.buildDataset.active': 'جارٍ بناء مجموعة البيانات',
+ 'conversations.tools.buildDataset.done': 'تم بناء مجموعة البيانات',
+ 'conversations.tools.askTheWeb.active': 'جارٍ سؤال الويب',
+ 'conversations.tools.askTheWeb.done': 'تم سؤال الويب',
+ 'conversations.tools.browseForYou.active': 'جارٍ التصفح نيابةً عنك',
+ 'conversations.tools.browseForYou.done': 'تم التصفح نيابةً عنك',
+ 'conversations.tools.callApi.active': 'جارٍ استدعاء API',
+ 'conversations.tools.callApi.done': 'تم استدعاء API',
+ 'conversations.tools.downloadFile.active': 'جارٍ تنزيل الملف',
+ 'conversations.tools.downloadFile.done': 'تم تنزيل الملف',
+ 'conversations.tools.makePaidRequest.active': 'جارٍ إرسال طلب مدفوع',
+ 'conversations.tools.makePaidRequest.done': 'تم إرسال طلب مدفوع',
+ 'conversations.tools.searchDocs.active': 'جارٍ البحث في الوثائق',
+ 'conversations.tools.searchDocs.done': 'تم البحث في الوثائق',
+ 'conversations.tools.readDocs.active': 'جارٍ قراءة الوثائق',
+ 'conversations.tools.readDocs.done': 'تمت قراءة الوثائق',
+ 'conversations.tools.useBrowser.active': 'جارٍ استخدام المتصفح',
+ 'conversations.tools.useBrowser.done': 'تم استخدام المتصفح',
+ 'conversations.tools.openPage.active': 'جارٍ فتح الصفحة',
+ 'conversations.tools.openPage.done': 'تم فتح الصفحة',
+ 'conversations.tools.navigate.active': 'جارٍ التنقل',
+ 'conversations.tools.navigate.done': 'تم التنقل',
+ 'conversations.tools.takeScreenshot.active': 'جارٍ التقاط لقطة شاشة',
+ 'conversations.tools.takeScreenshot.done': 'تم التقاط لقطة شاشة',
+ 'conversations.tools.scrollPage.active': 'جارٍ التمرير',
+ 'conversations.tools.scrollPage.done': 'تم التمرير',
+ 'conversations.tools.readPage.active': 'جارٍ قراءة الصفحة',
+ 'conversations.tools.readPage.done': 'تمت قراءة الصفحة',
+ 'conversations.tools.analyzeImage.active': 'جارٍ تحليل الصورة',
+ 'conversations.tools.analyzeImage.done': 'تم تحليل الصورة',
+ 'conversations.tools.generateImage.active': 'جارٍ إنشاء صورة',
+ 'conversations.tools.generateImage.done': 'تم إنشاء الصورة',
+ 'conversations.tools.generateVideo.active': 'جارٍ إنشاء فيديو',
+ 'conversations.tools.generateVideo.done': 'تم إنشاء الفيديو',
+ 'conversations.tools.checkMediaModels.active': 'جارٍ فحص نماذج الوسائط',
+ 'conversations.tools.checkMediaModels.done': 'تم فحص نماذج الوسائط',
+ 'conversations.tools.createDocument.active': 'جارٍ إنشاء مستند',
+ 'conversations.tools.createDocument.done': 'تم إنشاء المستند',
+ 'conversations.tools.createPresentation.active': 'جارٍ إنشاء عرض تقديمي',
+ 'conversations.tools.createPresentation.done': 'تم إنشاء العرض التقديمي',
+ 'conversations.tools.generatePodcast.active': 'جارٍ إنشاء بودكاست',
+ 'conversations.tools.generatePodcast.done': 'تم إنشاء البودكاست',
+ 'conversations.tools.emailPodcast.active': 'جارٍ إرسال البودكاست بالبريد',
+ 'conversations.tools.emailPodcast.done': 'تم إرسال البودكاست بالبريد',
+ 'conversations.tools.createAndEmailPodcast.active': 'جارٍ إنشاء البودكاست وإرساله بالبريد',
+ 'conversations.tools.createAndEmailPodcast.done': 'تم إنشاء البودكاست وإرساله بالبريد',
+ 'conversations.tools.recallMemories.active': 'جارٍ استرجاع الذكريات',
+ 'conversations.tools.recallMemories.done': 'تم استرجاع الذكريات',
+ 'conversations.tools.saveToMemory.active': 'جارٍ الحفظ في الذاكرة',
+ 'conversations.tools.saveToMemory.done': 'تم الحفظ في الذاكرة',
+ 'conversations.tools.forgetMemory.active': 'جارٍ حذف ذكرى',
+ 'conversations.tools.forgetMemory.done': 'تم حذف الذكرى',
+ 'conversations.tools.searchMemory.active': 'جارٍ البحث في الذاكرة',
+ 'conversations.tools.searchMemory.done': 'تم البحث في الذاكرة',
+ 'conversations.tools.inspectMemory.active': 'جارٍ فحص الذاكرة',
+ 'conversations.tools.inspectMemory.done': 'تم فحص الذاكرة',
+ 'conversations.tools.exploreMemory.active': 'جارٍ استكشاف الذاكرة',
+ 'conversations.tools.exploreMemory.done': 'تم استكشاف الذاكرة',
+ 'conversations.tools.saveDocumentToMemory.active': 'جارٍ حفظ المستند في الذاكرة',
+ 'conversations.tools.saveDocumentToMemory.done': 'تم حفظ المستند في الذاكرة',
+ 'conversations.tools.updateGoals.active': 'جارٍ تحديث الأهداف',
+ 'conversations.tools.updateGoals.done': 'تم تحديث الأهداف',
+ 'conversations.tools.reviewGoals.active': 'جارٍ مراجعة الأهداف',
+ 'conversations.tools.reviewGoals.done': 'تمت مراجعة الأهداف',
+ 'conversations.tools.savePreference.active': 'جارٍ حفظ التفضيل',
+ 'conversations.tools.savePreference.done': 'تم حفظ التفضيل',
+ 'conversations.tools.reviewLearnings.active': 'جارٍ مراجعة ما تعلمته',
+ 'conversations.tools.reviewLearnings.done': 'تمت مراجعة ما تعلمته',
+ 'conversations.tools.updateLearnings.active': 'جارٍ تحديث ما تعلمته',
+ 'conversations.tools.updateLearnings.done': 'تم تحديث ما تعلمته',
+ 'conversations.tools.delegateTask.active': 'جارٍ تفويض المهمة',
+ 'conversations.tools.delegateTask.done': 'تم تفويض المهمة',
+ 'conversations.tools.runAgentsInParallel.active': 'جارٍ تشغيل الوكلاء بالتوازي',
+ 'conversations.tools.runAgentsInParallel.done': 'تم تشغيل الوكلاء بالتوازي',
+ 'conversations.tools.messageAgent.active': 'جارٍ مراسلة الوكيل',
+ 'conversations.tools.messageAgent.done': 'تمت مراسلة الوكيل',
+ 'conversations.tools.waitForAgent.active': 'جارٍ انتظار الوكيل',
+ 'conversations.tools.waitForAgent.done': 'تم انتظار الوكيل',
+ 'conversations.tools.wait.active': 'جارٍ الانتظار',
+ 'conversations.tools.wait.done': 'تم الانتظار',
+ 'conversations.tools.closeAgent.active': 'جارٍ إغلاق الوكيل',
+ 'conversations.tools.closeAgent.done': 'تم إغلاق الوكيل',
+ 'conversations.tools.checkAgents.active': 'جارٍ فحص الوكلاء',
+ 'conversations.tools.checkAgents.done': 'تم فحص الوكلاء',
+ 'conversations.tools.askQuestion.active': 'جارٍ طرح سؤال عليك',
+ 'conversations.tools.askQuestion.done': 'تم طرح سؤال عليك',
+ 'conversations.tools.prepareContext.active': 'جارٍ تجهيز السياق',
+ 'conversations.tools.prepareContext.done': 'تم تجهيز السياق',
+ 'conversations.tools.extractDetails.active': 'جارٍ استخراج التفاصيل',
+ 'conversations.tools.extractDetails.done': 'تم استخراج التفاصيل',
+ 'conversations.tools.planNextSteps.active': 'جارٍ تخطيط الخطوات التالية',
+ 'conversations.tools.planNextSteps.done': 'تم تخطيط الخطوات التالية',
+ 'conversations.tools.reviewWork.active': 'جارٍ مراجعة العمل',
+ 'conversations.tools.reviewWork.done': 'تمت مراجعة العمل',
+ 'conversations.tools.scoutContext.active': 'جارٍ استطلاع السياق',
+ 'conversations.tools.scoutContext.done': 'تم استطلاع السياق',
+ 'conversations.tools.useTools.active': 'جارٍ استخدام الأدوات',
+ 'conversations.tools.useTools.done': 'تم استخدام الأدوات',
+ 'conversations.tools.checkConnectedApp.active': 'جارٍ فحص تطبيقك المتصل',
+ 'conversations.tools.checkConnectedApp.done': 'تم فحص تطبيقك المتصل',
+ 'conversations.tools.updateTodos.active': 'جارٍ تحديث قائمة المهام',
+ 'conversations.tools.updateTodos.done': 'تم تحديث قائمة المهام',
+ 'conversations.tools.requestPlanReview.active': 'جارٍ طلب مراجعة الخطة',
+ 'conversations.tools.requestPlanReview.done': 'تم طلب مراجعة الخطة',
+ 'conversations.tools.finishPlan.active': 'جارٍ إنهاء الخطة',
+ 'conversations.tools.finishPlan.done': 'تم إنهاء الخطة',
+ 'conversations.tools.setGoal.active': 'جارٍ تحديد الهدف',
+ 'conversations.tools.setGoal.done': 'تم تحديد الهدف',
+ 'conversations.tools.checkGoal.active': 'جارٍ فحص الهدف',
+ 'conversations.tools.checkGoal.done': 'تم فحص الهدف',
+ 'conversations.tools.completeGoal.active': 'جارٍ إكمال الهدف',
+ 'conversations.tools.completeGoal.done': 'تم إكمال الهدف',
+ 'conversations.tools.scheduleTask.active': 'جارٍ جدولة المهمة',
+ 'conversations.tools.scheduleTask.done': 'تمت جدولة المهمة',
+ 'conversations.tools.checkSchedules.active': 'جارٍ فحص الجداول',
+ 'conversations.tools.checkSchedules.done': 'تم فحص الجداول',
+ 'conversations.tools.updateSchedule.active': 'جارٍ تحديث المهمة المجدولة',
+ 'conversations.tools.updateSchedule.done': 'تم تحديث المهمة المجدولة',
+ 'conversations.tools.removeSchedule.active': 'جارٍ إزالة المهمة المجدولة',
+ 'conversations.tools.removeSchedule.done': 'تمت إزالة المهمة المجدولة',
+ 'conversations.tools.runScheduledTask.active': 'جارٍ تشغيل المهمة المجدولة',
+ 'conversations.tools.runScheduledTask.done': 'تم تشغيل المهمة المجدولة',
+ 'conversations.tools.checkRunHistory.active': 'جارٍ فحص سجل التشغيل',
+ 'conversations.tools.checkRunHistory.done': 'تم فحص سجل التشغيل',
+ 'conversations.tools.useApp.active': 'جارٍ استخدام {app}',
+ 'conversations.tools.useApp.done': 'تم استخدام {app}',
+ 'conversations.tools.checkAvailableApps.active': 'جارٍ فحص التطبيقات المتاحة',
+ 'conversations.tools.checkAvailableApps.done': 'تم فحص التطبيقات المتاحة',
+ 'conversations.tools.checkConnections.active': 'جارٍ فحص اتصالاتك',
+ 'conversations.tools.checkConnections.done': 'تم فحص اتصالاتك',
+ 'conversations.tools.connectApp.active': 'جارٍ ربط التطبيق',
+ 'conversations.tools.connectApp.done': 'تم ربط التطبيق',
+ 'conversations.tools.authorizeApp.active': 'جارٍ تفويض التطبيق',
+ 'conversations.tools.authorizeApp.done': 'تم تفويض التطبيق',
+ 'conversations.tools.findAppActions.active': 'جارٍ البحث عن إجراءات التطبيق',
+ 'conversations.tools.findAppActions.done': 'تم العثور على إجراءات التطبيق',
+ 'conversations.tools.runAppAction.active': 'جارٍ تنفيذ إجراء التطبيق',
+ 'conversations.tools.runAppAction.done': 'تم تنفيذ إجراء التطبيق',
+ 'conversations.tools.findTools.active': 'جارٍ البحث عن الأدوات',
+ 'conversations.tools.findTools.done': 'تم العثور على الأدوات',
+ 'conversations.tools.useTool.active': 'جارٍ استخدام {tool}',
+ 'conversations.tools.useTool.done': 'تم استخدام {tool}',
+ 'conversations.tools.unsubscribe.active': 'جارٍ إلغاء الاشتراك',
+ 'conversations.tools.unsubscribe.done': 'تم إلغاء الاشتراك',
+ 'conversations.tools.searchPlaces.active': 'جارٍ البحث عن أماكن',
+ 'conversations.tools.searchPlaces.done': 'تم البحث عن أماكن',
+ 'conversations.tools.lookUpPlace.active': 'جارٍ الاستعلام عن المكان',
+ 'conversations.tools.lookUpPlace.done': 'تم الاستعلام عن المكان',
+ 'conversations.tools.checkMarkets.active': 'جارٍ متابعة الأسواق',
+ 'conversations.tools.checkMarkets.done': 'تمت متابعة الأسواق',
+ 'conversations.tools.placeCall.active': 'جارٍ إجراء مكالمة',
+ 'conversations.tools.placeCall.done': 'تم إجراء المكالمة',
+ 'conversations.tools.checkTaskSources.active': 'جارٍ فحص مصادر المهام',
+ 'conversations.tools.checkTaskSources.done': 'تم فحص مصادر المهام',
+ 'conversations.tools.updateTaskSources.active': 'جارٍ تحديث مصادر المهام',
+ 'conversations.tools.updateTaskSources.done': 'تم تحديث مصادر المهام',
+ 'conversations.tools.fetchTasks.active': 'جارٍ جلب المهام',
+ 'conversations.tools.fetchTasks.done': 'تم جلب المهام',
+ 'conversations.tools.checkMcpServers.active': 'جارٍ فحص خوادم MCP',
+ 'conversations.tools.checkMcpServers.done': 'تم فحص خوادم MCP',
+ 'conversations.tools.checkMcpTools.active': 'جارٍ فحص أدوات MCP',
+ 'conversations.tools.checkMcpTools.done': 'تم فحص أدوات MCP',
+ 'conversations.tools.callMcpTool.active': 'جارٍ استدعاء {tool}',
+ 'conversations.tools.callMcpTool.done': 'تم استدعاء {tool}',
+ 'conversations.tools.searchMcpServers.active': 'جارٍ البحث عن خوادم MCP',
+ 'conversations.tools.searchMcpServers.done': 'تم البحث عن خوادم MCP',
+ 'conversations.tools.connectMcpServer.active': 'جارٍ الاتصال بخادم MCP',
+ 'conversations.tools.connectMcpServer.done': 'تم الاتصال بخادم MCP',
+ 'conversations.tools.disconnectMcpServer.active': 'جارٍ قطع الاتصال بخادم MCP',
+ 'conversations.tools.disconnectMcpServer.done': 'تم قطع الاتصال بخادم MCP',
+ 'conversations.tools.removeMcpServer.active': 'جارٍ إزالة خادم MCP',
+ 'conversations.tools.removeMcpServer.done': 'تمت إزالة خادم MCP',
+ 'conversations.tools.uploadFile.active': 'جارٍ رفع الملف',
+ 'conversations.tools.uploadFile.done': 'تم رفع الملف',
+ 'conversations.tools.listStoredFiles.active': 'جارٍ عرض الملفات المخزنة',
+ 'conversations.tools.listStoredFiles.done': 'تم عرض الملفات المخزنة',
+ 'conversations.tools.createShareLink.active': 'جارٍ إنشاء رابط مشاركة',
+ 'conversations.tools.createShareLink.done': 'تم إنشاء رابط المشاركة',
+ 'conversations.tools.deleteFile.active': 'جارٍ حذف الملف',
+ 'conversations.tools.deleteFile.done': 'تم حذف الملف',
+ 'conversations.tools.updateFileAccess.active': 'جارٍ تحديث صلاحيات الوصول للملف',
+ 'conversations.tools.updateFileAccess.done': 'تم تحديث صلاحيات الوصول للملف',
+ 'conversations.tools.deploySite.active': 'جارٍ نشر الموقع',
+ 'conversations.tools.deploySite.done': 'تم نشر الموقع',
+ 'conversations.tools.checkHosting.active': 'جارٍ فحص الاستضافة',
+ 'conversations.tools.checkHosting.done': 'تم فحص الاستضافة',
+ 'conversations.tools.updateHosting.active': 'جارٍ تحديث الاستضافة',
+ 'conversations.tools.updateHosting.done': 'تم تحديث الاستضافة',
+ 'conversations.tools.rollBackDeployment.active': 'جارٍ التراجع عن النشر',
+ 'conversations.tools.rollBackDeployment.done': 'تم التراجع عن النشر',
+ 'conversations.tools.checkWallet.active': 'جارٍ فحص المحفظة',
+ 'conversations.tools.checkWallet.done': 'تم فحص المحفظة',
+ 'conversations.tools.prepareTransfer.active': 'جارٍ تجهيز التحويل',
+ 'conversations.tools.prepareTransfer.done': 'تم تجهيز التحويل',
+ 'conversations.tools.checkTransaction.active': 'جارٍ فحص المعاملة',
+ 'conversations.tools.checkTransaction.done': 'تم فحص المعاملة',
+ 'conversations.tools.getSwapQuote.active': 'جارٍ جلب عرض سعر المبادلة',
+ 'conversations.tools.getSwapQuote.done': 'تم جلب عرض سعر المبادلة',
+ 'conversations.tools.swapTokens.active': 'جارٍ مبادلة الرموز',
+ 'conversations.tools.swapTokens.done': 'تمت مبادلة الرموز',
+ 'conversations.tools.getBridgeQuote.active': 'جارٍ جلب عرض سعر الجسر',
+ 'conversations.tools.getBridgeQuote.done': 'تم جلب عرض سعر الجسر',
+ 'conversations.tools.bridgeTokens.active': 'جارٍ نقل الرموز عبر الجسر',
+ 'conversations.tools.bridgeTokens.done': 'تم نقل الرموز عبر الجسر',
+ 'conversations.tools.callDapp.active': 'جارٍ استدعاء عقد التطبيق',
+ 'conversations.tools.callDapp.done': 'تم استدعاء عقد التطبيق',
+ 'conversations.tools.useSkill.active': 'جارٍ استخدام المهارة',
+ 'conversations.tools.useSkill.done': 'تم استخدام المهارة',
+ 'conversations.tools.searchSkills.active': 'جارٍ البحث عن المهارات',
+ 'conversations.tools.searchSkills.done': 'تم البحث عن المهارات',
+ 'conversations.tools.checkSkills.active': 'جارٍ فحص المهارات',
+ 'conversations.tools.checkSkills.done': 'تم فحص المهارات',
+ 'conversations.tools.installSkill.active': 'جارٍ تثبيت المهارة',
+ 'conversations.tools.installSkill.done': 'تم تثبيت المهارة',
+ 'conversations.tools.removeSkill.active': 'جارٍ إزالة المهارة',
+ 'conversations.tools.removeSkill.done': 'تمت إزالة المهارة',
+ 'conversations.tools.createSkill.active': 'جارٍ إنشاء المهارة',
+ 'conversations.tools.createSkill.done': 'تم إنشاء المهارة',
+ 'conversations.tools.runWorkflow.active': 'جارٍ تشغيل سير العمل',
+ 'conversations.tools.runWorkflow.done': 'تم تشغيل سير العمل',
+ 'conversations.tools.waitForWorkflow.active': 'جارٍ انتظار سير العمل',
+ 'conversations.tools.waitForWorkflow.done': 'تم انتظار سير العمل',
+ 'conversations.tools.designWorkflow.active': 'جارٍ تصميم سير العمل',
+ 'conversations.tools.designWorkflow.done': 'تم تصميم سير العمل',
+ 'conversations.tools.saveWorkflow.active': 'جارٍ حفظ سير العمل',
+ 'conversations.tools.saveWorkflow.done': 'تم حفظ سير العمل',
+ 'conversations.tools.validateWorkflow.active': 'جارٍ التحقق من سير العمل',
+ 'conversations.tools.validateWorkflow.done': 'تم التحقق من سير العمل',
+ 'conversations.tools.testWorkflow.active': 'جارٍ اختبار سير العمل',
+ 'conversations.tools.testWorkflow.done': 'تم اختبار سير العمل',
+ 'conversations.tools.checkWorkflows.active': 'جارٍ فحص مسارات العمل',
+ 'conversations.tools.checkWorkflows.done': 'تم فحص مسارات العمل',
+ 'conversations.tools.cancelWorkflow.active': 'جارٍ إلغاء تشغيل سير العمل',
+ 'conversations.tools.cancelWorkflow.done': 'تم إلغاء تشغيل سير العمل',
+ 'conversations.tools.suggestWorkflows.active': 'جارٍ اقتراح مسارات عمل',
+ 'conversations.tools.suggestWorkflows.done': 'تم اقتراح مسارات عمل',
+ 'conversations.tools.checkSettings.active': 'جارٍ فحص الإعدادات',
+ 'conversations.tools.checkSettings.done': 'تم فحص الإعدادات',
+ 'conversations.tools.checkSecurity.active': 'جارٍ فحص الأمان',
+ 'conversations.tools.checkSecurity.done': 'تم فحص الأمان',
+ 'conversations.tools.runDiagnostics.active': 'جارٍ تشغيل التشخيص',
+ 'conversations.tools.runDiagnostics.done': 'تم تشغيل التشخيص',
+ 'conversations.tools.checkUsageCosts.active': 'جارٍ فحص تكاليف الاستخدام',
+ 'conversations.tools.checkUsageCosts.done': 'تم فحص تكاليف الاستخدام',
+ 'conversations.tools.manageService.active': 'جارٍ إدارة خدمة الخلفية',
+ 'conversations.tools.manageService.done': 'تمت إدارة خدمة الخلفية',
+ 'conversations.tools.readPersona.active': 'جارٍ قراءة الشخصية',
+ 'conversations.tools.readPersona.done': 'تمت قراءة الشخصية',
+ 'conversations.tools.updatePersona.active': 'جارٍ تحديث الشخصية',
+ 'conversations.tools.updatePersona.done': 'تم تحديث الشخصية',
+ 'conversations.tools.setUpWorkspace.active': 'جارٍ إعداد مساحة العمل',
+ 'conversations.tools.setUpWorkspace.done': 'تم إعداد مساحة العمل',
+ 'conversations.tools.checkArtifacts.active': 'جارٍ فحص الملفات المُنشأة',
+ 'conversations.tools.checkArtifacts.done': 'تم فحص الملفات المُنشأة',
+ 'conversations.tools.deleteArtifact.active': 'جارٍ حذف الملف المُنشأ',
+ 'conversations.tools.deleteArtifact.done': 'تم حذف الملف المُنشأ',
'conversations.subagent.noOutput': 'لم يتم إرجاع أي مخرجات',
'conversations.subagent.close': 'إغلاق',
'conversations.subagent.cancel': 'إلغاء المهمة',
diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts
index 8898d5d5c67..e2559fee698 100644
--- a/app/src/lib/i18n/bn.ts
+++ b/app/src/lib/i18n/bn.ts
@@ -3316,6 +3316,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'এখনও কোনো আউটপুট নেই',
'conversations.subagent.input': 'ইনপুট',
'conversations.subagent.output': 'আউটপুট',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count}টি ধাপ',
+ 'conversations.tools.steps.other': '{count}টি ধাপ',
+ 'conversations.tools.working': 'কাজ চলছে',
+ 'conversations.tools.noOutput': 'কোনো আউটপুট নেই',
+ 'conversations.tools.delegatedTo': '{agent}-কে দায়িত্ব দেওয়া হয়েছে',
+ 'conversations.tools.openInBrowser': 'ব্রাউজারে খুলুন',
+ 'conversations.tools.status.running': 'চলছে',
+ 'conversations.tools.status.done': 'সম্পন্ন',
+ 'conversations.tools.status.failed': 'ব্যর্থ',
+ 'conversations.tools.status.cancelled': 'বাতিল',
+ 'conversations.tools.status.awaiting': 'ইনপুটের অপেক্ষায়',
+ 'conversations.tools.search.searching': 'খোঁজা হচ্ছে',
+ 'conversations.tools.search.none': 'কোনো ফলাফল নেই',
+ 'conversations.tools.search.found.one': '{count}টি ফলাফল পাওয়া গেছে',
+ 'conversations.tools.search.found.other': '{count}টি ফলাফল পাওয়া গেছে',
+ 'conversations.tools.search.via': '{provider}-এর মাধ্যমে',
+ 'conversations.tools.readFile.active': 'ফাইল পড়া হচ্ছে',
+ 'conversations.tools.readFile.done': 'ফাইল পড়া হয়েছে',
+ 'conversations.tools.writeFile.active': 'ফাইল লেখা হচ্ছে',
+ 'conversations.tools.writeFile.done': 'ফাইল লেখা হয়েছে',
+ 'conversations.tools.editFile.active': 'ফাইল সম্পাদনা করা হচ্ছে',
+ 'conversations.tools.editFile.done': 'ফাইল সম্পাদনা করা হয়েছে',
+ 'conversations.tools.applyEdits.active': 'সম্পাদনা প্রয়োগ করা হচ্ছে',
+ 'conversations.tools.applyEdits.done': 'সম্পাদনা প্রয়োগ করা হয়েছে',
+ 'conversations.tools.searchCode.active': 'কোড খোঁজা হচ্ছে',
+ 'conversations.tools.searchCode.done': 'কোড খোঁজা হয়েছে',
+ 'conversations.tools.findFiles.active': 'ফাইল খোঁজা হচ্ছে',
+ 'conversations.tools.findFiles.done': 'ফাইল পাওয়া গেছে',
+ 'conversations.tools.listFolder.active': 'ফোল্ডারের তালিকা তৈরি হচ্ছে',
+ 'conversations.tools.listFolder.done': 'ফোল্ডারের তালিকা তৈরি হয়েছে',
+ 'conversations.tools.exportCsv.active': 'CSV রপ্তানি করা হচ্ছে',
+ 'conversations.tools.exportCsv.done': 'CSV রপ্তানি করা হয়েছে',
+ 'conversations.tools.updateMemoryNotes.active': 'মেমরি নোট হালনাগাদ করা হচ্ছে',
+ 'conversations.tools.updateMemoryNotes.done': 'মেমরি নোট হালনাগাদ করা হয়েছে',
+ 'conversations.tools.runGit.active': 'git চালানো হচ্ছে',
+ 'conversations.tools.runGit.done': 'git চালানো হয়েছে',
+ 'conversations.tools.readChanges.active': 'পরিবর্তন পড়া হচ্ছে',
+ 'conversations.tools.readChanges.done': 'পরিবর্তন পড়া হয়েছে',
+ 'conversations.tools.runLinter.active': 'লিন্টার চালানো হচ্ছে',
+ 'conversations.tools.runLinter.done': 'লিন্টার চালানো হয়েছে',
+ 'conversations.tools.runTests.active': 'টেস্ট চালানো হচ্ছে',
+ 'conversations.tools.runTests.done': 'টেস্ট চালানো হয়েছে',
+ 'conversations.tools.analyzeCode.active': 'কোড বিশ্লেষণ করা হচ্ছে',
+ 'conversations.tools.analyzeCode.done': 'কোড বিশ্লেষণ করা হয়েছে',
+ 'conversations.tools.insertRecord.active': 'রেকর্ড যোগ করা হচ্ছে',
+ 'conversations.tools.insertRecord.done': 'রেকর্ড যোগ করা হয়েছে',
+ 'conversations.tools.runCommand.active': 'কমান্ড চালানো হচ্ছে',
+ 'conversations.tools.runCommand.done': 'কমান্ড চালানো হয়েছে',
+ 'conversations.tools.runCode.active': 'কোড চালানো হচ্ছে',
+ 'conversations.tools.runCode.done': 'কোড চালানো হয়েছে',
+ 'conversations.tools.runPackageManager.active': 'npm চালানো হচ্ছে',
+ 'conversations.tools.runPackageManager.done': 'npm চালানো হয়েছে',
+ 'conversations.tools.checkInstalledTools.active': 'ইনস্টল করা টুল যাচাই করা হচ্ছে',
+ 'conversations.tools.checkInstalledTools.done': 'ইনস্টল করা টুল যাচাই করা হয়েছে',
+ 'conversations.tools.installTool.active': 'টুল ইনস্টল করা হচ্ছে',
+ 'conversations.tools.installTool.done': 'টুল ইনস্টল করা হয়েছে',
+ 'conversations.tools.checkTime.active': 'সময় দেখা হচ্ছে',
+ 'conversations.tools.checkTime.done': 'সময় দেখা হয়েছে',
+ 'conversations.tools.resolveDate.active': 'তারিখ নির্ণয় করা হচ্ছে',
+ 'conversations.tools.resolveDate.done': 'তারিখ নির্ণয় করা হয়েছে',
+ 'conversations.tools.retrieveOutput.active': 'সম্পূর্ণ আউটপুট আনা হচ্ছে',
+ 'conversations.tools.retrieveOutput.done': 'সম্পূর্ণ আউটপুট আনা হয়েছে',
+ 'conversations.tools.reviewWorkspace.active': 'ওয়ার্কস্পেস পর্যালোচনা করা হচ্ছে',
+ 'conversations.tools.reviewWorkspace.done': 'ওয়ার্কস্পেস পর্যালোচনা করা হয়েছে',
+ 'conversations.tools.configureProxy.active': 'প্রক্সি কনফিগার করা হচ্ছে',
+ 'conversations.tools.configureProxy.done': 'প্রক্সি কনফিগার করা হয়েছে',
+ 'conversations.tools.checkUpdates.active': 'আপডেট খোঁজা হচ্ছে',
+ 'conversations.tools.checkUpdates.done': 'আপডেট খোঁজা হয়েছে',
+ 'conversations.tools.installUpdate.active': 'আপডেট ইনস্টল করা হচ্ছে',
+ 'conversations.tools.installUpdate.done': 'আপডেট ইনস্টল করা হয়েছে',
+ 'conversations.tools.sendNotification.active': 'বিজ্ঞপ্তি পাঠানো হচ্ছে',
+ 'conversations.tools.sendNotification.done': 'বিজ্ঞপ্তি পাঠানো হয়েছে',
+ 'conversations.tools.reviewToolUsage.active': 'টুল ব্যবহার পর্যালোচনা করা হচ্ছে',
+ 'conversations.tools.reviewToolUsage.done': 'টুল ব্যবহার পর্যালোচনা করা হয়েছে',
+ 'conversations.tools.typeKeys.active': 'টাইপ করা হচ্ছে',
+ 'conversations.tools.typeKeys.done': 'টাইপ করা হয়েছে',
+ 'conversations.tools.click.active': 'ক্লিক করা হচ্ছে',
+ 'conversations.tools.click.done': 'ক্লিক করা হয়েছে',
+ 'conversations.tools.searchWeb.active': 'ওয়েবে খোঁজা হচ্ছে',
+ 'conversations.tools.searchWeb.done': 'ওয়েবে খোঁজা হয়েছে',
+ 'conversations.tools.searchNews.active': 'খবর খোঁজা হচ্ছে',
+ 'conversations.tools.searchNews.done': 'খবর খোঁজা হয়েছে',
+ 'conversations.tools.searchImages.active': 'ছবি খোঁজা হচ্ছে',
+ 'conversations.tools.searchImages.done': 'ছবি খোঁজা হয়েছে',
+ 'conversations.tools.searchVideos.active': 'ভিডিও খোঁজা হচ্ছে',
+ 'conversations.tools.searchVideos.done': 'ভিডিও খোঁজা হয়েছে',
+ 'conversations.tools.findSimilarPages.active': 'অনুরূপ পেজ খোঁজা হচ্ছে',
+ 'conversations.tools.findSimilarPages.done': 'অনুরূপ পেজ পাওয়া গেছে',
+ 'conversations.tools.readPages.active': 'পেজগুলো পড়া হচ্ছে',
+ 'conversations.tools.readPages.done': 'পেজগুলো পড়া হয়েছে',
+ 'conversations.tools.readWebpage.active': 'ওয়েবপেজ পড়া হচ্ছে',
+ 'conversations.tools.readWebpage.done': 'ওয়েবপেজ পড়া হয়েছে',
+ 'conversations.tools.research.active': 'গবেষণা করা হচ্ছে',
+ 'conversations.tools.research.done': 'গবেষণা করা হয়েছে',
+ 'conversations.tools.enrichData.active': 'ডেটা সমৃদ্ধ করা হচ্ছে',
+ 'conversations.tools.enrichData.done': 'ডেটা সমৃদ্ধ করা হয়েছে',
+ 'conversations.tools.buildDataset.active': 'ডেটাসেট তৈরি হচ্ছে',
+ 'conversations.tools.buildDataset.done': 'ডেটাসেট তৈরি হয়েছে',
+ 'conversations.tools.askTheWeb.active': 'ওয়েবে জিজ্ঞাসা করা হচ্ছে',
+ 'conversations.tools.askTheWeb.done': 'ওয়েবে জিজ্ঞাসা করা হয়েছে',
+ 'conversations.tools.browseForYou.active': 'আপনার জন্য ব্রাউজ করা হচ্ছে',
+ 'conversations.tools.browseForYou.done': 'আপনার জন্য ব্রাউজ করা হয়েছে',
+ 'conversations.tools.callApi.active': 'API কল করা হচ্ছে',
+ 'conversations.tools.callApi.done': 'API কল করা হয়েছে',
+ 'conversations.tools.downloadFile.active': 'ফাইল ডাউনলোড হচ্ছে',
+ 'conversations.tools.downloadFile.done': 'ফাইল ডাউনলোড হয়েছে',
+ 'conversations.tools.makePaidRequest.active': 'পেইড অনুরোধ পাঠানো হচ্ছে',
+ 'conversations.tools.makePaidRequest.done': 'পেইড অনুরোধ পাঠানো হয়েছে',
+ 'conversations.tools.searchDocs.active': 'ডকুমেন্টেশন খোঁজা হচ্ছে',
+ 'conversations.tools.searchDocs.done': 'ডকুমেন্টেশন খোঁজা হয়েছে',
+ 'conversations.tools.readDocs.active': 'ডকুমেন্টেশন পড়া হচ্ছে',
+ 'conversations.tools.readDocs.done': 'ডকুমেন্টেশন পড়া হয়েছে',
+ 'conversations.tools.useBrowser.active': 'ব্রাউজার ব্যবহার করা হচ্ছে',
+ 'conversations.tools.useBrowser.done': 'ব্রাউজার ব্যবহার করা হয়েছে',
+ 'conversations.tools.openPage.active': 'পেজ খোলা হচ্ছে',
+ 'conversations.tools.openPage.done': 'পেজ খোলা হয়েছে',
+ 'conversations.tools.navigate.active': 'নেভিগেট করা হচ্ছে',
+ 'conversations.tools.navigate.done': 'নেভিগেট করা হয়েছে',
+ 'conversations.tools.takeScreenshot.active': 'স্ক্রিনশট নেওয়া হচ্ছে',
+ 'conversations.tools.takeScreenshot.done': 'স্ক্রিনশট নেওয়া হয়েছে',
+ 'conversations.tools.scrollPage.active': 'স্ক্রল করা হচ্ছে',
+ 'conversations.tools.scrollPage.done': 'স্ক্রল করা হয়েছে',
+ 'conversations.tools.readPage.active': 'পেজ পড়া হচ্ছে',
+ 'conversations.tools.readPage.done': 'পেজ পড়া হয়েছে',
+ 'conversations.tools.analyzeImage.active': 'ছবি বিশ্লেষণ করা হচ্ছে',
+ 'conversations.tools.analyzeImage.done': 'ছবি বিশ্লেষণ করা হয়েছে',
+ 'conversations.tools.generateImage.active': 'ছবি তৈরি হচ্ছে',
+ 'conversations.tools.generateImage.done': 'ছবি তৈরি হয়েছে',
+ 'conversations.tools.generateVideo.active': 'ভিডিও তৈরি হচ্ছে',
+ 'conversations.tools.generateVideo.done': 'ভিডিও তৈরি হয়েছে',
+ 'conversations.tools.checkMediaModels.active': 'মিডিয়া মডেল যাচাই করা হচ্ছে',
+ 'conversations.tools.checkMediaModels.done': 'মিডিয়া মডেল যাচাই করা হয়েছে',
+ 'conversations.tools.createDocument.active': 'ডকুমেন্ট তৈরি হচ্ছে',
+ 'conversations.tools.createDocument.done': 'ডকুমেন্ট তৈরি হয়েছে',
+ 'conversations.tools.createPresentation.active': 'প্রেজেন্টেশন তৈরি হচ্ছে',
+ 'conversations.tools.createPresentation.done': 'প্রেজেন্টেশন তৈরি হয়েছে',
+ 'conversations.tools.generatePodcast.active': 'পডকাস্ট তৈরি হচ্ছে',
+ 'conversations.tools.generatePodcast.done': 'পডকাস্ট তৈরি হয়েছে',
+ 'conversations.tools.emailPodcast.active': 'পডকাস্ট ইমেইল করা হচ্ছে',
+ 'conversations.tools.emailPodcast.done': 'পডকাস্ট ইমেইল করা হয়েছে',
+ 'conversations.tools.createAndEmailPodcast.active': 'পডকাস্ট তৈরি করে ইমেইল করা হচ্ছে',
+ 'conversations.tools.createAndEmailPodcast.done': 'পডকাস্ট তৈরি করে ইমেইল করা হয়েছে',
+ 'conversations.tools.recallMemories.active': 'স্মৃতি মনে করা হচ্ছে',
+ 'conversations.tools.recallMemories.done': 'স্মৃতি মনে করা হয়েছে',
+ 'conversations.tools.saveToMemory.active': 'মেমরিতে সংরক্ষণ করা হচ্ছে',
+ 'conversations.tools.saveToMemory.done': 'মেমরিতে সংরক্ষণ করা হয়েছে',
+ 'conversations.tools.forgetMemory.active': 'মেমরি মুছে ফেলা হচ্ছে',
+ 'conversations.tools.forgetMemory.done': 'মেমরি মুছে ফেলা হয়েছে',
+ 'conversations.tools.searchMemory.active': 'মেমরিতে খোঁজা হচ্ছে',
+ 'conversations.tools.searchMemory.done': 'মেমরিতে খোঁজা হয়েছে',
+ 'conversations.tools.inspectMemory.active': 'মেমরি পরীক্ষা করা হচ্ছে',
+ 'conversations.tools.inspectMemory.done': 'মেমরি পরীক্ষা করা হয়েছে',
+ 'conversations.tools.exploreMemory.active': 'মেমরি ঘেঁটে দেখা হচ্ছে',
+ 'conversations.tools.exploreMemory.done': 'মেমরি ঘেঁটে দেখা হয়েছে',
+ 'conversations.tools.saveDocumentToMemory.active': 'ডকুমেন্ট মেমরিতে সংরক্ষণ করা হচ্ছে',
+ 'conversations.tools.saveDocumentToMemory.done': 'ডকুমেন্ট মেমরিতে সংরক্ষণ করা হয়েছে',
+ 'conversations.tools.updateGoals.active': 'লক্ষ্য হালনাগাদ করা হচ্ছে',
+ 'conversations.tools.updateGoals.done': 'লক্ষ্য হালনাগাদ করা হয়েছে',
+ 'conversations.tools.reviewGoals.active': 'লক্ষ্য পর্যালোচনা করা হচ্ছে',
+ 'conversations.tools.reviewGoals.done': 'লক্ষ্য পর্যালোচনা করা হয়েছে',
+ 'conversations.tools.savePreference.active': 'পছন্দ সংরক্ষণ করা হচ্ছে',
+ 'conversations.tools.savePreference.done': 'পছন্দ সংরক্ষণ করা হয়েছে',
+ 'conversations.tools.reviewLearnings.active': 'যা শিখেছি তা পর্যালোচনা করা হচ্ছে',
+ 'conversations.tools.reviewLearnings.done': 'যা শিখেছি তা পর্যালোচনা করা হয়েছে',
+ 'conversations.tools.updateLearnings.active': 'যা শিখেছি তা হালনাগাদ করা হচ্ছে',
+ 'conversations.tools.updateLearnings.done': 'যা শিখেছি তা হালনাগাদ করা হয়েছে',
+ 'conversations.tools.delegateTask.active': 'কাজ অর্পণ করা হচ্ছে',
+ 'conversations.tools.delegateTask.done': 'কাজ অর্পণ করা হয়েছে',
+ 'conversations.tools.runAgentsInParallel.active': 'এজেন্টগুলো একসাথে চালানো হচ্ছে',
+ 'conversations.tools.runAgentsInParallel.done': 'এজেন্টগুলো একসাথে চালানো হয়েছে',
+ 'conversations.tools.messageAgent.active': 'এজেন্টকে বার্তা পাঠানো হচ্ছে',
+ 'conversations.tools.messageAgent.done': 'এজেন্টকে বার্তা পাঠানো হয়েছে',
+ 'conversations.tools.waitForAgent.active': 'এজেন্টের জন্য অপেক্ষা করা হচ্ছে',
+ 'conversations.tools.waitForAgent.done': 'এজেন্টের জন্য অপেক্ষা করা হয়েছে',
+ 'conversations.tools.wait.active': 'অপেক্ষা করা হচ্ছে',
+ 'conversations.tools.wait.done': 'অপেক্ষা করা হয়েছে',
+ 'conversations.tools.closeAgent.active': 'এজেন্ট বন্ধ করা হচ্ছে',
+ 'conversations.tools.closeAgent.done': 'এজেন্ট বন্ধ করা হয়েছে',
+ 'conversations.tools.checkAgents.active': 'এজেন্টগুলো যাচাই করা হচ্ছে',
+ 'conversations.tools.checkAgents.done': 'এজেন্টগুলো যাচাই করা হয়েছে',
+ 'conversations.tools.askQuestion.active': 'আপনাকে একটি প্রশ্ন করা হচ্ছে',
+ 'conversations.tools.askQuestion.done': 'আপনাকে একটি প্রশ্ন করা হয়েছে',
+ 'conversations.tools.prepareContext.active': 'প্রসঙ্গ প্রস্তুত করা হচ্ছে',
+ 'conversations.tools.prepareContext.done': 'প্রসঙ্গ প্রস্তুত করা হয়েছে',
+ 'conversations.tools.extractDetails.active': 'বিস্তারিত বের করা হচ্ছে',
+ 'conversations.tools.extractDetails.done': 'বিস্তারিত বের করা হয়েছে',
+ 'conversations.tools.planNextSteps.active': 'পরবর্তী ধাপের পরিকল্পনা করা হচ্ছে',
+ 'conversations.tools.planNextSteps.done': 'পরবর্তী ধাপের পরিকল্পনা করা হয়েছে',
+ 'conversations.tools.reviewWork.active': 'কাজ পর্যালোচনা করা হচ্ছে',
+ 'conversations.tools.reviewWork.done': 'কাজ পর্যালোচনা করা হয়েছে',
+ 'conversations.tools.scoutContext.active': 'প্রসঙ্গ অনুসন্ধান করা হচ্ছে',
+ 'conversations.tools.scoutContext.done': 'প্রসঙ্গ অনুসন্ধান করা হয়েছে',
+ 'conversations.tools.useTools.active': 'টুল ব্যবহার করা হচ্ছে',
+ 'conversations.tools.useTools.done': 'টুল ব্যবহার করা হয়েছে',
+ 'conversations.tools.checkConnectedApp.active': 'আপনার সংযুক্ত অ্যাপ যাচাই করা হচ্ছে',
+ 'conversations.tools.checkConnectedApp.done': 'আপনার সংযুক্ত অ্যাপ যাচাই করা হয়েছে',
+ 'conversations.tools.updateTodos.active': 'করণীয় তালিকা হালনাগাদ করা হচ্ছে',
+ 'conversations.tools.updateTodos.done': 'করণীয় তালিকা হালনাগাদ করা হয়েছে',
+ 'conversations.tools.requestPlanReview.active': 'পরিকল্পনা পর্যালোচনার অনুরোধ করা হচ্ছে',
+ 'conversations.tools.requestPlanReview.done': 'পরিকল্পনা পর্যালোচনার অনুরোধ করা হয়েছে',
+ 'conversations.tools.finishPlan.active': 'পরিকল্পনা চূড়ান্ত করা হচ্ছে',
+ 'conversations.tools.finishPlan.done': 'পরিকল্পনা চূড়ান্ত করা হয়েছে',
+ 'conversations.tools.setGoal.active': 'লক্ষ্য নির্ধারণ করা হচ্ছে',
+ 'conversations.tools.setGoal.done': 'লক্ষ্য নির্ধারণ করা হয়েছে',
+ 'conversations.tools.checkGoal.active': 'লক্ষ্য যাচাই করা হচ্ছে',
+ 'conversations.tools.checkGoal.done': 'লক্ষ্য যাচাই করা হয়েছে',
+ 'conversations.tools.completeGoal.active': 'লক্ষ্য সম্পন্ন করা হচ্ছে',
+ 'conversations.tools.completeGoal.done': 'লক্ষ্য সম্পন্ন করা হয়েছে',
+ 'conversations.tools.scheduleTask.active': 'কাজের সময়সূচি নির্ধারণ করা হচ্ছে',
+ 'conversations.tools.scheduleTask.done': 'কাজের সময়সূচি নির্ধারণ করা হয়েছে',
+ 'conversations.tools.checkSchedules.active': 'সময়সূচি যাচাই করা হচ্ছে',
+ 'conversations.tools.checkSchedules.done': 'সময়সূচি যাচাই করা হয়েছে',
+ 'conversations.tools.updateSchedule.active': 'নির্ধারিত কাজ হালনাগাদ করা হচ্ছে',
+ 'conversations.tools.updateSchedule.done': 'নির্ধারিত কাজ হালনাগাদ করা হয়েছে',
+ 'conversations.tools.removeSchedule.active': 'নির্ধারিত কাজ সরানো হচ্ছে',
+ 'conversations.tools.removeSchedule.done': 'নির্ধারিত কাজ সরানো হয়েছে',
+ 'conversations.tools.runScheduledTask.active': 'নির্ধারিত কাজ চালানো হচ্ছে',
+ 'conversations.tools.runScheduledTask.done': 'নির্ধারিত কাজ চালানো হয়েছে',
+ 'conversations.tools.checkRunHistory.active': 'চালানোর ইতিহাস যাচাই করা হচ্ছে',
+ 'conversations.tools.checkRunHistory.done': 'চালানোর ইতিহাস যাচাই করা হয়েছে',
+ 'conversations.tools.useApp.active': '{app} ব্যবহার করা হচ্ছে',
+ 'conversations.tools.useApp.done': '{app} ব্যবহার করা হয়েছে',
+ 'conversations.tools.checkAvailableApps.active': 'উপলব্ধ অ্যাপ যাচাই করা হচ্ছে',
+ 'conversations.tools.checkAvailableApps.done': 'উপলব্ধ অ্যাপ যাচাই করা হয়েছে',
+ 'conversations.tools.checkConnections.active': 'আপনার সংযোগ যাচাই করা হচ্ছে',
+ 'conversations.tools.checkConnections.done': 'আপনার সংযোগ যাচাই করা হয়েছে',
+ 'conversations.tools.connectApp.active': 'অ্যাপ সংযুক্ত করা হচ্ছে',
+ 'conversations.tools.connectApp.done': 'অ্যাপ সংযুক্ত করা হয়েছে',
+ 'conversations.tools.authorizeApp.active': 'অ্যাপ অনুমোদন করা হচ্ছে',
+ 'conversations.tools.authorizeApp.done': 'অ্যাপ অনুমোদন করা হয়েছে',
+ 'conversations.tools.findAppActions.active': 'অ্যাপের অ্যাকশন খোঁজা হচ্ছে',
+ 'conversations.tools.findAppActions.done': 'অ্যাপের অ্যাকশন পাওয়া গেছে',
+ 'conversations.tools.runAppAction.active': 'অ্যাপের অ্যাকশন চালানো হচ্ছে',
+ 'conversations.tools.runAppAction.done': 'অ্যাপের অ্যাকশন চালানো হয়েছে',
+ 'conversations.tools.findTools.active': 'টুল খোঁজা হচ্ছে',
+ 'conversations.tools.findTools.done': 'টুল পাওয়া গেছে',
+ 'conversations.tools.useTool.active': '{tool} ব্যবহার করা হচ্ছে',
+ 'conversations.tools.useTool.done': '{tool} ব্যবহার করা হয়েছে',
+ 'conversations.tools.unsubscribe.active': 'সদস্যতা বাতিল করা হচ্ছে',
+ 'conversations.tools.unsubscribe.done': 'সদস্যতা বাতিল করা হয়েছে',
+ 'conversations.tools.searchPlaces.active': 'স্থান খোঁজা হচ্ছে',
+ 'conversations.tools.searchPlaces.done': 'স্থান খোঁজা হয়েছে',
+ 'conversations.tools.lookUpPlace.active': 'স্থানের তথ্য দেখা হচ্ছে',
+ 'conversations.tools.lookUpPlace.done': 'স্থানের তথ্য দেখা হয়েছে',
+ 'conversations.tools.checkMarkets.active': 'বাজার দেখা হচ্ছে',
+ 'conversations.tools.checkMarkets.done': 'বাজার দেখা হয়েছে',
+ 'conversations.tools.placeCall.active': 'কল করা হচ্ছে',
+ 'conversations.tools.placeCall.done': 'কল করা হয়েছে',
+ 'conversations.tools.checkTaskSources.active': 'কাজের উৎস যাচাই করা হচ্ছে',
+ 'conversations.tools.checkTaskSources.done': 'কাজের উৎস যাচাই করা হয়েছে',
+ 'conversations.tools.updateTaskSources.active': 'কাজের উৎস হালনাগাদ করা হচ্ছে',
+ 'conversations.tools.updateTaskSources.done': 'কাজের উৎস হালনাগাদ করা হয়েছে',
+ 'conversations.tools.fetchTasks.active': 'কাজ আনা হচ্ছে',
+ 'conversations.tools.fetchTasks.done': 'কাজ আনা হয়েছে',
+ 'conversations.tools.checkMcpServers.active': 'MCP সার্ভার যাচাই করা হচ্ছে',
+ 'conversations.tools.checkMcpServers.done': 'MCP সার্ভার যাচাই করা হয়েছে',
+ 'conversations.tools.checkMcpTools.active': 'MCP টুল যাচাই করা হচ্ছে',
+ 'conversations.tools.checkMcpTools.done': 'MCP টুল যাচাই করা হয়েছে',
+ 'conversations.tools.callMcpTool.active': '{tool} কল করা হচ্ছে',
+ 'conversations.tools.callMcpTool.done': '{tool} কল করা হয়েছে',
+ 'conversations.tools.searchMcpServers.active': 'MCP সার্ভার খোঁজা হচ্ছে',
+ 'conversations.tools.searchMcpServers.done': 'MCP সার্ভার খোঁজা হয়েছে',
+ 'conversations.tools.connectMcpServer.active': 'MCP সার্ভার সংযুক্ত করা হচ্ছে',
+ 'conversations.tools.connectMcpServer.done': 'MCP সার্ভার সংযুক্ত করা হয়েছে',
+ 'conversations.tools.disconnectMcpServer.active': 'MCP সার্ভার বিচ্ছিন্ন করা হচ্ছে',
+ 'conversations.tools.disconnectMcpServer.done': 'MCP সার্ভার বিচ্ছিন্ন করা হয়েছে',
+ 'conversations.tools.removeMcpServer.active': 'MCP সার্ভার সরানো হচ্ছে',
+ 'conversations.tools.removeMcpServer.done': 'MCP সার্ভার সরানো হয়েছে',
+ 'conversations.tools.uploadFile.active': 'ফাইল আপলোড হচ্ছে',
+ 'conversations.tools.uploadFile.done': 'ফাইল আপলোড হয়েছে',
+ 'conversations.tools.listStoredFiles.active': 'সংরক্ষিত ফাইলের তালিকা তৈরি হচ্ছে',
+ 'conversations.tools.listStoredFiles.done': 'সংরক্ষিত ফাইলের তালিকা তৈরি হয়েছে',
+ 'conversations.tools.createShareLink.active': 'শেয়ার লিংক তৈরি হচ্ছে',
+ 'conversations.tools.createShareLink.done': 'শেয়ার লিংক তৈরি হয়েছে',
+ 'conversations.tools.deleteFile.active': 'ফাইল মোছা হচ্ছে',
+ 'conversations.tools.deleteFile.done': 'ফাইল মোছা হয়েছে',
+ 'conversations.tools.updateFileAccess.active': 'ফাইলের অ্যাক্সেস হালনাগাদ করা হচ্ছে',
+ 'conversations.tools.updateFileAccess.done': 'ফাইলের অ্যাক্সেস হালনাগাদ করা হয়েছে',
+ 'conversations.tools.deploySite.active': 'সাইট ডিপ্লয় করা হচ্ছে',
+ 'conversations.tools.deploySite.done': 'সাইট ডিপ্লয় করা হয়েছে',
+ 'conversations.tools.checkHosting.active': 'হোস্টিং যাচাই করা হচ্ছে',
+ 'conversations.tools.checkHosting.done': 'হোস্টিং যাচাই করা হয়েছে',
+ 'conversations.tools.updateHosting.active': 'হোস্টিং হালনাগাদ করা হচ্ছে',
+ 'conversations.tools.updateHosting.done': 'হোস্টিং হালনাগাদ করা হয়েছে',
+ 'conversations.tools.rollBackDeployment.active': 'ডিপ্লয়মেন্ট আগের অবস্থায় ফেরানো হচ্ছে',
+ 'conversations.tools.rollBackDeployment.done': 'ডিপ্লয়মেন্ট আগের অবস্থায় ফেরানো হয়েছে',
+ 'conversations.tools.checkWallet.active': 'ওয়ালেট যাচাই করা হচ্ছে',
+ 'conversations.tools.checkWallet.done': 'ওয়ালেট যাচাই করা হয়েছে',
+ 'conversations.tools.prepareTransfer.active': 'ট্রান্সফার প্রস্তুত করা হচ্ছে',
+ 'conversations.tools.prepareTransfer.done': 'ট্রান্সফার প্রস্তুত করা হয়েছে',
+ 'conversations.tools.checkTransaction.active': 'লেনদেন যাচাই করা হচ্ছে',
+ 'conversations.tools.checkTransaction.done': 'লেনদেন যাচাই করা হয়েছে',
+ 'conversations.tools.getSwapQuote.active': 'সোয়াপ কোট আনা হচ্ছে',
+ 'conversations.tools.getSwapQuote.done': 'সোয়াপ কোট আনা হয়েছে',
+ 'conversations.tools.swapTokens.active': 'টোকেন সোয়াপ করা হচ্ছে',
+ 'conversations.tools.swapTokens.done': 'টোকেন সোয়াপ করা হয়েছে',
+ 'conversations.tools.getBridgeQuote.active': 'ব্রিজ কোট আনা হচ্ছে',
+ 'conversations.tools.getBridgeQuote.done': 'ব্রিজ কোট আনা হয়েছে',
+ 'conversations.tools.bridgeTokens.active': 'টোকেন ব্রিজ করা হচ্ছে',
+ 'conversations.tools.bridgeTokens.done': 'টোকেন ব্রিজ করা হয়েছে',
+ 'conversations.tools.callDapp.active': 'অ্যাপ কন্ট্র্যাক্ট কল করা হচ্ছে',
+ 'conversations.tools.callDapp.done': 'অ্যাপ কন্ট্র্যাক্ট কল করা হয়েছে',
+ 'conversations.tools.useSkill.active': 'স্কিল ব্যবহার করা হচ্ছে',
+ 'conversations.tools.useSkill.done': 'স্কিল ব্যবহার করা হয়েছে',
+ 'conversations.tools.searchSkills.active': 'স্কিল খোঁজা হচ্ছে',
+ 'conversations.tools.searchSkills.done': 'স্কিল খোঁজা হয়েছে',
+ 'conversations.tools.checkSkills.active': 'স্কিল যাচাই করা হচ্ছে',
+ 'conversations.tools.checkSkills.done': 'স্কিল যাচাই করা হয়েছে',
+ 'conversations.tools.installSkill.active': 'স্কিল ইনস্টল করা হচ্ছে',
+ 'conversations.tools.installSkill.done': 'স্কিল ইনস্টল করা হয়েছে',
+ 'conversations.tools.removeSkill.active': 'স্কিল সরানো হচ্ছে',
+ 'conversations.tools.removeSkill.done': 'স্কিল সরানো হয়েছে',
+ 'conversations.tools.createSkill.active': 'স্কিল তৈরি হচ্ছে',
+ 'conversations.tools.createSkill.done': 'স্কিল তৈরি হয়েছে',
+ 'conversations.tools.runWorkflow.active': 'ওয়ার্কফ্লো চালানো হচ্ছে',
+ 'conversations.tools.runWorkflow.done': 'ওয়ার্কফ্লো চালানো হয়েছে',
+ 'conversations.tools.waitForWorkflow.active': 'ওয়ার্কফ্লোর জন্য অপেক্ষা করা হচ্ছে',
+ 'conversations.tools.waitForWorkflow.done': 'ওয়ার্কফ্লোর জন্য অপেক্ষা করা হয়েছে',
+ 'conversations.tools.designWorkflow.active': 'ওয়ার্কফ্লো ডিজাইন করা হচ্ছে',
+ 'conversations.tools.designWorkflow.done': 'ওয়ার্কফ্লো ডিজাইন করা হয়েছে',
+ 'conversations.tools.saveWorkflow.active': 'ওয়ার্কফ্লো সংরক্ষণ করা হচ্ছে',
+ 'conversations.tools.saveWorkflow.done': 'ওয়ার্কফ্লো সংরক্ষণ করা হয়েছে',
+ 'conversations.tools.validateWorkflow.active': 'ওয়ার্কফ্লো যাচাই করা হচ্ছে',
+ 'conversations.tools.validateWorkflow.done': 'ওয়ার্কফ্লো যাচাই করা হয়েছে',
+ 'conversations.tools.testWorkflow.active': 'ওয়ার্কফ্লো পরীক্ষা করা হচ্ছে',
+ 'conversations.tools.testWorkflow.done': 'ওয়ার্কফ্লো পরীক্ষা করা হয়েছে',
+ 'conversations.tools.checkWorkflows.active': 'ওয়ার্কফ্লোগুলো দেখা হচ্ছে',
+ 'conversations.tools.checkWorkflows.done': 'ওয়ার্কফ্লোগুলো দেখা হয়েছে',
+ 'conversations.tools.cancelWorkflow.active': 'ওয়ার্কফ্লো রান বাতিল করা হচ্ছে',
+ 'conversations.tools.cancelWorkflow.done': 'ওয়ার্কফ্লো রান বাতিল করা হয়েছে',
+ 'conversations.tools.suggestWorkflows.active': 'ওয়ার্কফ্লো প্রস্তাব করা হচ্ছে',
+ 'conversations.tools.suggestWorkflows.done': 'ওয়ার্কফ্লো প্রস্তাব করা হয়েছে',
+ 'conversations.tools.checkSettings.active': 'সেটিংস যাচাই করা হচ্ছে',
+ 'conversations.tools.checkSettings.done': 'সেটিংস যাচাই করা হয়েছে',
+ 'conversations.tools.checkSecurity.active': 'নিরাপত্তা যাচাই করা হচ্ছে',
+ 'conversations.tools.checkSecurity.done': 'নিরাপত্তা যাচাই করা হয়েছে',
+ 'conversations.tools.runDiagnostics.active': 'ডায়াগনস্টিক চালানো হচ্ছে',
+ 'conversations.tools.runDiagnostics.done': 'ডায়াগনস্টিক চালানো হয়েছে',
+ 'conversations.tools.checkUsageCosts.active': 'ব্যবহারের খরচ যাচাই করা হচ্ছে',
+ 'conversations.tools.checkUsageCosts.done': 'ব্যবহারের খরচ যাচাই করা হয়েছে',
+ 'conversations.tools.manageService.active': 'ব্যাকগ্রাউন্ড সার্ভিস পরিচালনা করা হচ্ছে',
+ 'conversations.tools.manageService.done': 'ব্যাকগ্রাউন্ড সার্ভিস পরিচালনা করা হয়েছে',
+ 'conversations.tools.readPersona.active': 'পারসোনা পড়া হচ্ছে',
+ 'conversations.tools.readPersona.done': 'পারসোনা পড়া হয়েছে',
+ 'conversations.tools.updatePersona.active': 'পারসোনা হালনাগাদ করা হচ্ছে',
+ 'conversations.tools.updatePersona.done': 'পারসোনা হালনাগাদ করা হয়েছে',
+ 'conversations.tools.setUpWorkspace.active': 'ওয়ার্কস্পেস সেট আপ করা হচ্ছে',
+ 'conversations.tools.setUpWorkspace.done': 'ওয়ার্কস্পেস সেট আপ করা হয়েছে',
+ 'conversations.tools.checkArtifacts.active': 'আর্টিফ্যাক্ট যাচাই করা হচ্ছে',
+ 'conversations.tools.checkArtifacts.done': 'আর্টিফ্যাক্ট যাচাই করা হয়েছে',
+ 'conversations.tools.deleteArtifact.active': 'আর্টিফ্যাক্ট মোছা হচ্ছে',
+ 'conversations.tools.deleteArtifact.done': 'আর্টিফ্যাক্ট মোছা হয়েছে',
'conversations.subagent.noOutput': 'কোনো আউটপুট ফেরত আসেনি',
'conversations.subagent.close': 'বন্ধ করুন',
'conversations.subagent.cancel': 'কাজ বাতিল করুন',
diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts
index b6e617482ef..b3305c903d2 100644
--- a/app/src/lib/i18n/de.ts
+++ b/app/src/lib/i18n/de.ts
@@ -3410,6 +3410,360 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'Noch keine Ausgabe',
'conversations.subagent.input': 'Eingabe',
'conversations.subagent.output': 'Ausgabe',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} Schritt',
+ 'conversations.tools.steps.other': '{count} Schritte',
+ 'conversations.tools.working': 'Arbeitet',
+ 'conversations.tools.noOutput': 'Keine Ausgabe',
+ 'conversations.tools.delegatedTo': 'An {agent} delegiert',
+ 'conversations.tools.openInBrowser': 'Im Browser öffnen',
+ 'conversations.tools.status.running': 'läuft',
+ 'conversations.tools.status.done': 'fertig',
+ 'conversations.tools.status.failed': 'fehlgeschlagen',
+ 'conversations.tools.status.cancelled': 'abgebrochen',
+ 'conversations.tools.status.awaiting': 'wartet auf Eingabe',
+ 'conversations.tools.search.searching': 'Suche läuft',
+ 'conversations.tools.search.none': 'Keine Ergebnisse',
+ 'conversations.tools.search.found.one': '{count} Ergebnis gefunden',
+ 'conversations.tools.search.found.other': '{count} Ergebnisse gefunden',
+ 'conversations.tools.search.via': 'über {provider}',
+ 'conversations.tools.readFile.active': 'Datei wird gelesen',
+ 'conversations.tools.readFile.done': 'Datei gelesen',
+ 'conversations.tools.writeFile.active': 'Datei wird geschrieben',
+ 'conversations.tools.writeFile.done': 'Datei geschrieben',
+ 'conversations.tools.editFile.active': 'Datei wird bearbeitet',
+ 'conversations.tools.editFile.done': 'Datei bearbeitet',
+ 'conversations.tools.applyEdits.active': 'Änderungen werden angewendet',
+ 'conversations.tools.applyEdits.done': 'Änderungen angewendet',
+ 'conversations.tools.searchCode.active': 'Code wird durchsucht',
+ 'conversations.tools.searchCode.done': 'Code durchsucht',
+ 'conversations.tools.findFiles.active': 'Dateien werden gesucht',
+ 'conversations.tools.findFiles.done': 'Dateien gefunden',
+ 'conversations.tools.listFolder.active': 'Ordner wird aufgelistet',
+ 'conversations.tools.listFolder.done': 'Ordner aufgelistet',
+ 'conversations.tools.exportCsv.active': 'CSV wird exportiert',
+ 'conversations.tools.exportCsv.done': 'CSV exportiert',
+ 'conversations.tools.updateMemoryNotes.active': 'Gedächtnisnotizen werden aktualisiert',
+ 'conversations.tools.updateMemoryNotes.done': 'Gedächtnisnotizen aktualisiert',
+ 'conversations.tools.runGit.active': 'git wird ausgeführt',
+ 'conversations.tools.runGit.done': 'git ausgeführt',
+ 'conversations.tools.readChanges.active': 'Änderungen werden gelesen',
+ 'conversations.tools.readChanges.done': 'Änderungen gelesen',
+ 'conversations.tools.runLinter.active': 'Linter wird ausgeführt',
+ 'conversations.tools.runLinter.done': 'Linter ausgeführt',
+ 'conversations.tools.runTests.active': 'Tests werden ausgeführt',
+ 'conversations.tools.runTests.done': 'Tests ausgeführt',
+ 'conversations.tools.analyzeCode.active': 'Code wird analysiert',
+ 'conversations.tools.analyzeCode.done': 'Code analysiert',
+ 'conversations.tools.insertRecord.active': 'Datensatz wird eingefügt',
+ 'conversations.tools.insertRecord.done': 'Datensatz eingefügt',
+ 'conversations.tools.runCommand.active': 'Befehl wird ausgeführt',
+ 'conversations.tools.runCommand.done': 'Befehl ausgeführt',
+ 'conversations.tools.runCode.active': 'Code wird ausgeführt',
+ 'conversations.tools.runCode.done': 'Code ausgeführt',
+ 'conversations.tools.runPackageManager.active': 'npm wird ausgeführt',
+ 'conversations.tools.runPackageManager.done': 'npm ausgeführt',
+ 'conversations.tools.checkInstalledTools.active': 'Installierte Tools werden geprüft',
+ 'conversations.tools.checkInstalledTools.done': 'Installierte Tools geprüft',
+ 'conversations.tools.installTool.active': 'Tool wird installiert',
+ 'conversations.tools.installTool.done': 'Tool installiert',
+ 'conversations.tools.checkTime.active': 'Uhrzeit wird geprüft',
+ 'conversations.tools.checkTime.done': 'Uhrzeit geprüft',
+ 'conversations.tools.resolveDate.active': 'Datum wird ermittelt',
+ 'conversations.tools.resolveDate.done': 'Datum ermittelt',
+ 'conversations.tools.retrieveOutput.active': 'Vollständige Ausgabe wird abgerufen',
+ 'conversations.tools.retrieveOutput.done': 'Vollständige Ausgabe abgerufen',
+ 'conversations.tools.reviewWorkspace.active': 'Arbeitsbereich wird geprüft',
+ 'conversations.tools.reviewWorkspace.done': 'Arbeitsbereich geprüft',
+ 'conversations.tools.configureProxy.active': 'Proxy wird konfiguriert',
+ 'conversations.tools.configureProxy.done': 'Proxy konfiguriert',
+ 'conversations.tools.checkUpdates.active': 'Nach Updates wird gesucht',
+ 'conversations.tools.checkUpdates.done': 'Nach Updates gesucht',
+ 'conversations.tools.installUpdate.active': 'Update wird installiert',
+ 'conversations.tools.installUpdate.done': 'Update installiert',
+ 'conversations.tools.sendNotification.active': 'Benachrichtigung wird gesendet',
+ 'conversations.tools.sendNotification.done': 'Benachrichtigung gesendet',
+ 'conversations.tools.reviewToolUsage.active': 'Tool-Nutzung wird geprüft',
+ 'conversations.tools.reviewToolUsage.done': 'Tool-Nutzung geprüft',
+ 'conversations.tools.typeKeys.active': 'Tippt',
+ 'conversations.tools.typeKeys.done': 'Getippt',
+ 'conversations.tools.click.active': 'Klickt',
+ 'conversations.tools.click.done': 'Geklickt',
+ 'conversations.tools.searchWeb.active': 'Web wird durchsucht',
+ 'conversations.tools.searchWeb.done': 'Web durchsucht',
+ 'conversations.tools.searchNews.active': 'Nachrichten werden durchsucht',
+ 'conversations.tools.searchNews.done': 'Nachrichten durchsucht',
+ 'conversations.tools.searchImages.active': 'Bilder werden gesucht',
+ 'conversations.tools.searchImages.done': 'Bilder gesucht',
+ 'conversations.tools.searchVideos.active': 'Videos werden gesucht',
+ 'conversations.tools.searchVideos.done': 'Videos gesucht',
+ 'conversations.tools.findSimilarPages.active': 'Ähnliche Seiten werden gesucht',
+ 'conversations.tools.findSimilarPages.done': 'Ähnliche Seiten gefunden',
+ 'conversations.tools.readPages.active': 'Seiten werden gelesen',
+ 'conversations.tools.readPages.done': 'Seiten gelesen',
+ 'conversations.tools.readWebpage.active': 'Webseite wird gelesen',
+ 'conversations.tools.readWebpage.done': 'Webseite gelesen',
+ 'conversations.tools.research.active': 'Recherche läuft',
+ 'conversations.tools.research.done': 'Recherche abgeschlossen',
+ 'conversations.tools.enrichData.active': 'Daten werden angereichert',
+ 'conversations.tools.enrichData.done': 'Daten angereichert',
+ 'conversations.tools.buildDataset.active': 'Datensatz wird erstellt',
+ 'conversations.tools.buildDataset.done': 'Datensatz erstellt',
+ 'conversations.tools.askTheWeb.active': 'Web wird befragt',
+ 'conversations.tools.askTheWeb.done': 'Web befragt',
+ 'conversations.tools.browseForYou.active': 'Surft für dich',
+ 'conversations.tools.browseForYou.done': 'Für dich gesurft',
+ 'conversations.tools.callApi.active': 'API wird aufgerufen',
+ 'conversations.tools.callApi.done': 'API aufgerufen',
+ 'conversations.tools.downloadFile.active': 'Datei wird heruntergeladen',
+ 'conversations.tools.downloadFile.done': 'Datei heruntergeladen',
+ 'conversations.tools.makePaidRequest.active': 'Kostenpflichtige Anfrage läuft',
+ 'conversations.tools.makePaidRequest.done': 'Kostenpflichtige Anfrage gesendet',
+ 'conversations.tools.searchDocs.active': 'Dokumentation wird durchsucht',
+ 'conversations.tools.searchDocs.done': 'Dokumentation durchsucht',
+ 'conversations.tools.readDocs.active': 'Dokumentation wird gelesen',
+ 'conversations.tools.readDocs.done': 'Dokumentation gelesen',
+ 'conversations.tools.useBrowser.active': 'Browser wird verwendet',
+ 'conversations.tools.useBrowser.done': 'Browser verwendet',
+ 'conversations.tools.openPage.active': 'Seite wird geöffnet',
+ 'conversations.tools.openPage.done': 'Seite geöffnet',
+ 'conversations.tools.navigate.active': 'Navigation läuft',
+ 'conversations.tools.navigate.done': 'Navigiert',
+ 'conversations.tools.takeScreenshot.active': 'Screenshot wird erstellt',
+ 'conversations.tools.takeScreenshot.done': 'Screenshot erstellt',
+ 'conversations.tools.scrollPage.active': 'Scrollt',
+ 'conversations.tools.scrollPage.done': 'Gescrollt',
+ 'conversations.tools.readPage.active': 'Seite wird gelesen',
+ 'conversations.tools.readPage.done': 'Seite gelesen',
+ 'conversations.tools.analyzeImage.active': 'Bild wird analysiert',
+ 'conversations.tools.analyzeImage.done': 'Bild analysiert',
+ 'conversations.tools.generateImage.active': 'Bild wird generiert',
+ 'conversations.tools.generateImage.done': 'Bild generiert',
+ 'conversations.tools.generateVideo.active': 'Video wird generiert',
+ 'conversations.tools.generateVideo.done': 'Video generiert',
+ 'conversations.tools.checkMediaModels.active': 'Medienmodelle werden geprüft',
+ 'conversations.tools.checkMediaModels.done': 'Medienmodelle geprüft',
+ 'conversations.tools.createDocument.active': 'Dokument wird erstellt',
+ 'conversations.tools.createDocument.done': 'Dokument erstellt',
+ 'conversations.tools.createPresentation.active': 'Präsentation wird erstellt',
+ 'conversations.tools.createPresentation.done': 'Präsentation erstellt',
+ 'conversations.tools.generatePodcast.active': 'Podcast wird generiert',
+ 'conversations.tools.generatePodcast.done': 'Podcast generiert',
+ 'conversations.tools.emailPodcast.active': 'Podcast wird per E-Mail gesendet',
+ 'conversations.tools.emailPodcast.done': 'Podcast per E-Mail gesendet',
+ 'conversations.tools.createAndEmailPodcast.active':
+ 'Podcast wird erstellt und per E-Mail gesendet',
+ 'conversations.tools.createAndEmailPodcast.done': 'Podcast erstellt und per E-Mail gesendet',
+ 'conversations.tools.recallMemories.active': 'Erinnerungen werden abgerufen',
+ 'conversations.tools.recallMemories.done': 'Erinnerungen abgerufen',
+ 'conversations.tools.saveToMemory.active': 'Wird im Gedächtnis gespeichert',
+ 'conversations.tools.saveToMemory.done': 'Im Gedächtnis gespeichert',
+ 'conversations.tools.forgetMemory.active': 'Erinnerung wird vergessen',
+ 'conversations.tools.forgetMemory.done': 'Erinnerung vergessen',
+ 'conversations.tools.searchMemory.active': 'Gedächtnis wird durchsucht',
+ 'conversations.tools.searchMemory.done': 'Gedächtnis durchsucht',
+ 'conversations.tools.inspectMemory.active': 'Gedächtnis wird untersucht',
+ 'conversations.tools.inspectMemory.done': 'Gedächtnis untersucht',
+ 'conversations.tools.exploreMemory.active': 'Gedächtnis wird erkundet',
+ 'conversations.tools.exploreMemory.done': 'Gedächtnis erkundet',
+ 'conversations.tools.saveDocumentToMemory.active': 'Dokument wird im Gedächtnis gespeichert',
+ 'conversations.tools.saveDocumentToMemory.done': 'Dokument im Gedächtnis gespeichert',
+ 'conversations.tools.updateGoals.active': 'Ziele werden aktualisiert',
+ 'conversations.tools.updateGoals.done': 'Ziele aktualisiert',
+ 'conversations.tools.reviewGoals.active': 'Ziele werden geprüft',
+ 'conversations.tools.reviewGoals.done': 'Ziele geprüft',
+ 'conversations.tools.savePreference.active': 'Einstellung wird gespeichert',
+ 'conversations.tools.savePreference.done': 'Einstellung gespeichert',
+ 'conversations.tools.reviewLearnings.active': 'Gelerntes wird geprüft',
+ 'conversations.tools.reviewLearnings.done': 'Gelerntes geprüft',
+ 'conversations.tools.updateLearnings.active': 'Gelerntes wird aktualisiert',
+ 'conversations.tools.updateLearnings.done': 'Gelerntes aktualisiert',
+ 'conversations.tools.delegateTask.active': 'Aufgabe wird delegiert',
+ 'conversations.tools.delegateTask.done': 'Aufgabe delegiert',
+ 'conversations.tools.runAgentsInParallel.active': 'Agenten laufen parallel',
+ 'conversations.tools.runAgentsInParallel.done': 'Agenten parallel ausgeführt',
+ 'conversations.tools.messageAgent.active': 'Nachricht an Agenten wird gesendet',
+ 'conversations.tools.messageAgent.done': 'Nachricht an Agenten gesendet',
+ 'conversations.tools.waitForAgent.active': 'Wartet auf Agenten',
+ 'conversations.tools.waitForAgent.done': 'Auf Agenten gewartet',
+ 'conversations.tools.wait.active': 'Wartet',
+ 'conversations.tools.wait.done': 'Gewartet',
+ 'conversations.tools.closeAgent.active': 'Agent wird geschlossen',
+ 'conversations.tools.closeAgent.done': 'Agent geschlossen',
+ 'conversations.tools.checkAgents.active': 'Agenten werden geprüft',
+ 'conversations.tools.checkAgents.done': 'Agenten geprüft',
+ 'conversations.tools.askQuestion.active': 'Stellt dir eine Frage',
+ 'conversations.tools.askQuestion.done': 'Dir eine Frage gestellt',
+ 'conversations.tools.prepareContext.active': 'Kontext wird vorbereitet',
+ 'conversations.tools.prepareContext.done': 'Kontext vorbereitet',
+ 'conversations.tools.extractDetails.active': 'Details werden extrahiert',
+ 'conversations.tools.extractDetails.done': 'Details extrahiert',
+ 'conversations.tools.planNextSteps.active': 'Nächste Schritte werden geplant',
+ 'conversations.tools.planNextSteps.done': 'Nächste Schritte geplant',
+ 'conversations.tools.reviewWork.active': 'Arbeit wird geprüft',
+ 'conversations.tools.reviewWork.done': 'Arbeit geprüft',
+ 'conversations.tools.scoutContext.active': 'Kontext wird erkundet',
+ 'conversations.tools.scoutContext.done': 'Kontext erkundet',
+ 'conversations.tools.useTools.active': 'Tools werden verwendet',
+ 'conversations.tools.useTools.done': 'Tools verwendet',
+ 'conversations.tools.checkConnectedApp.active': 'Verbundene App wird geprüft',
+ 'conversations.tools.checkConnectedApp.done': 'Verbundene App geprüft',
+ 'conversations.tools.updateTodos.active': 'To-do-Liste wird aktualisiert',
+ 'conversations.tools.updateTodos.done': 'To-do-Liste aktualisiert',
+ 'conversations.tools.requestPlanReview.active': 'Planprüfung wird angefordert',
+ 'conversations.tools.requestPlanReview.done': 'Planprüfung angefordert',
+ 'conversations.tools.finishPlan.active': 'Plan wird abgeschlossen',
+ 'conversations.tools.finishPlan.done': 'Plan abgeschlossen',
+ 'conversations.tools.setGoal.active': 'Ziel wird festgelegt',
+ 'conversations.tools.setGoal.done': 'Ziel festgelegt',
+ 'conversations.tools.checkGoal.active': 'Ziel wird geprüft',
+ 'conversations.tools.checkGoal.done': 'Ziel geprüft',
+ 'conversations.tools.completeGoal.active': 'Ziel wird abgeschlossen',
+ 'conversations.tools.completeGoal.done': 'Ziel abgeschlossen',
+ 'conversations.tools.scheduleTask.active': 'Aufgabe wird geplant',
+ 'conversations.tools.scheduleTask.done': 'Aufgabe geplant',
+ 'conversations.tools.checkSchedules.active': 'Zeitpläne werden geprüft',
+ 'conversations.tools.checkSchedules.done': 'Zeitpläne geprüft',
+ 'conversations.tools.updateSchedule.active': 'Geplante Aufgabe wird aktualisiert',
+ 'conversations.tools.updateSchedule.done': 'Geplante Aufgabe aktualisiert',
+ 'conversations.tools.removeSchedule.active': 'Geplante Aufgabe wird entfernt',
+ 'conversations.tools.removeSchedule.done': 'Geplante Aufgabe entfernt',
+ 'conversations.tools.runScheduledTask.active': 'Geplante Aufgabe wird ausgeführt',
+ 'conversations.tools.runScheduledTask.done': 'Geplante Aufgabe ausgeführt',
+ 'conversations.tools.checkRunHistory.active': 'Ausführungsverlauf wird geprüft',
+ 'conversations.tools.checkRunHistory.done': 'Ausführungsverlauf geprüft',
+ 'conversations.tools.useApp.active': '{app} wird verwendet',
+ 'conversations.tools.useApp.done': '{app} verwendet',
+ 'conversations.tools.checkAvailableApps.active': 'Verfügbare Apps werden geprüft',
+ 'conversations.tools.checkAvailableApps.done': 'Verfügbare Apps geprüft',
+ 'conversations.tools.checkConnections.active': 'Deine Verbindungen werden geprüft',
+ 'conversations.tools.checkConnections.done': 'Deine Verbindungen geprüft',
+ 'conversations.tools.connectApp.active': 'App wird verbunden',
+ 'conversations.tools.connectApp.done': 'App verbunden',
+ 'conversations.tools.authorizeApp.active': 'App wird autorisiert',
+ 'conversations.tools.authorizeApp.done': 'App autorisiert',
+ 'conversations.tools.findAppActions.active': 'App-Aktionen werden gesucht',
+ 'conversations.tools.findAppActions.done': 'App-Aktionen gefunden',
+ 'conversations.tools.runAppAction.active': 'App-Aktion wird ausgeführt',
+ 'conversations.tools.runAppAction.done': 'App-Aktion ausgeführt',
+ 'conversations.tools.findTools.active': 'Tools werden gesucht',
+ 'conversations.tools.findTools.done': 'Tools gefunden',
+ 'conversations.tools.useTool.active': '{tool} wird verwendet',
+ 'conversations.tools.useTool.done': '{tool} verwendet',
+ 'conversations.tools.unsubscribe.active': 'Abmeldung läuft',
+ 'conversations.tools.unsubscribe.done': 'Abgemeldet',
+ 'conversations.tools.searchPlaces.active': 'Orte werden gesucht',
+ 'conversations.tools.searchPlaces.done': 'Orte gesucht',
+ 'conversations.tools.lookUpPlace.active': 'Ort wird nachgeschlagen',
+ 'conversations.tools.lookUpPlace.done': 'Ort nachgeschlagen',
+ 'conversations.tools.checkMarkets.active': 'Märkte werden geprüft',
+ 'conversations.tools.checkMarkets.done': 'Märkte geprüft',
+ 'conversations.tools.placeCall.active': 'Anruf wird getätigt',
+ 'conversations.tools.placeCall.done': 'Anruf getätigt',
+ 'conversations.tools.checkTaskSources.active': 'Aufgabenquellen werden geprüft',
+ 'conversations.tools.checkTaskSources.done': 'Aufgabenquellen geprüft',
+ 'conversations.tools.updateTaskSources.active': 'Aufgabenquellen werden aktualisiert',
+ 'conversations.tools.updateTaskSources.done': 'Aufgabenquellen aktualisiert',
+ 'conversations.tools.fetchTasks.active': 'Aufgaben werden abgerufen',
+ 'conversations.tools.fetchTasks.done': 'Aufgaben abgerufen',
+ 'conversations.tools.checkMcpServers.active': 'MCP-Server werden geprüft',
+ 'conversations.tools.checkMcpServers.done': 'MCP-Server geprüft',
+ 'conversations.tools.checkMcpTools.active': 'MCP-Tools werden geprüft',
+ 'conversations.tools.checkMcpTools.done': 'MCP-Tools geprüft',
+ 'conversations.tools.callMcpTool.active': '{tool} wird aufgerufen',
+ 'conversations.tools.callMcpTool.done': '{tool} aufgerufen',
+ 'conversations.tools.searchMcpServers.active': 'MCP-Server werden gesucht',
+ 'conversations.tools.searchMcpServers.done': 'MCP-Server gesucht',
+ 'conversations.tools.connectMcpServer.active': 'MCP-Server wird verbunden',
+ 'conversations.tools.connectMcpServer.done': 'MCP-Server verbunden',
+ 'conversations.tools.disconnectMcpServer.active': 'MCP-Server wird getrennt',
+ 'conversations.tools.disconnectMcpServer.done': 'MCP-Server getrennt',
+ 'conversations.tools.removeMcpServer.active': 'MCP-Server wird entfernt',
+ 'conversations.tools.removeMcpServer.done': 'MCP-Server entfernt',
+ 'conversations.tools.uploadFile.active': 'Datei wird hochgeladen',
+ 'conversations.tools.uploadFile.done': 'Datei hochgeladen',
+ 'conversations.tools.listStoredFiles.active': 'Gespeicherte Dateien werden aufgelistet',
+ 'conversations.tools.listStoredFiles.done': 'Gespeicherte Dateien aufgelistet',
+ 'conversations.tools.createShareLink.active': 'Freigabelink wird erstellt',
+ 'conversations.tools.createShareLink.done': 'Freigabelink erstellt',
+ 'conversations.tools.deleteFile.active': 'Datei wird gelöscht',
+ 'conversations.tools.deleteFile.done': 'Datei gelöscht',
+ 'conversations.tools.updateFileAccess.active': 'Dateizugriff wird aktualisiert',
+ 'conversations.tools.updateFileAccess.done': 'Dateizugriff aktualisiert',
+ 'conversations.tools.deploySite.active': 'Website wird bereitgestellt',
+ 'conversations.tools.deploySite.done': 'Website bereitgestellt',
+ 'conversations.tools.checkHosting.active': 'Hosting wird geprüft',
+ 'conversations.tools.checkHosting.done': 'Hosting geprüft',
+ 'conversations.tools.updateHosting.active': 'Hosting wird aktualisiert',
+ 'conversations.tools.updateHosting.done': 'Hosting aktualisiert',
+ 'conversations.tools.rollBackDeployment.active': 'Bereitstellung wird zurückgesetzt',
+ 'conversations.tools.rollBackDeployment.done': 'Bereitstellung zurückgesetzt',
+ 'conversations.tools.checkWallet.active': 'Wallet wird geprüft',
+ 'conversations.tools.checkWallet.done': 'Wallet geprüft',
+ 'conversations.tools.prepareTransfer.active': 'Überweisung wird vorbereitet',
+ 'conversations.tools.prepareTransfer.done': 'Überweisung vorbereitet',
+ 'conversations.tools.checkTransaction.active': 'Transaktion wird geprüft',
+ 'conversations.tools.checkTransaction.done': 'Transaktion geprüft',
+ 'conversations.tools.getSwapQuote.active': 'Swap-Angebot wird abgerufen',
+ 'conversations.tools.getSwapQuote.done': 'Swap-Angebot abgerufen',
+ 'conversations.tools.swapTokens.active': 'Tokens werden getauscht',
+ 'conversations.tools.swapTokens.done': 'Tokens getauscht',
+ 'conversations.tools.getBridgeQuote.active': 'Bridge-Angebot wird abgerufen',
+ 'conversations.tools.getBridgeQuote.done': 'Bridge-Angebot abgerufen',
+ 'conversations.tools.bridgeTokens.active': 'Tokens werden übertragen',
+ 'conversations.tools.bridgeTokens.done': 'Tokens übertragen',
+ 'conversations.tools.callDapp.active': 'App-Vertrag wird aufgerufen',
+ 'conversations.tools.callDapp.done': 'App-Vertrag aufgerufen',
+ 'conversations.tools.useSkill.active': 'Skill wird verwendet',
+ 'conversations.tools.useSkill.done': 'Skill verwendet',
+ 'conversations.tools.searchSkills.active': 'Skills werden gesucht',
+ 'conversations.tools.searchSkills.done': 'Skills gesucht',
+ 'conversations.tools.checkSkills.active': 'Skills werden geprüft',
+ 'conversations.tools.checkSkills.done': 'Skills geprüft',
+ 'conversations.tools.installSkill.active': 'Skill wird installiert',
+ 'conversations.tools.installSkill.done': 'Skill installiert',
+ 'conversations.tools.removeSkill.active': 'Skill wird entfernt',
+ 'conversations.tools.removeSkill.done': 'Skill entfernt',
+ 'conversations.tools.createSkill.active': 'Skill wird erstellt',
+ 'conversations.tools.createSkill.done': 'Skill erstellt',
+ 'conversations.tools.runWorkflow.active': 'Workflow wird ausgeführt',
+ 'conversations.tools.runWorkflow.done': 'Workflow ausgeführt',
+ 'conversations.tools.waitForWorkflow.active': 'Wartet auf Workflow',
+ 'conversations.tools.waitForWorkflow.done': 'Auf Workflow gewartet',
+ 'conversations.tools.designWorkflow.active': 'Workflow wird entworfen',
+ 'conversations.tools.designWorkflow.done': 'Workflow entworfen',
+ 'conversations.tools.saveWorkflow.active': 'Workflow wird gespeichert',
+ 'conversations.tools.saveWorkflow.done': 'Workflow gespeichert',
+ 'conversations.tools.validateWorkflow.active': 'Workflow wird validiert',
+ 'conversations.tools.validateWorkflow.done': 'Workflow validiert',
+ 'conversations.tools.testWorkflow.active': 'Workflow wird getestet',
+ 'conversations.tools.testWorkflow.done': 'Workflow getestet',
+ 'conversations.tools.checkWorkflows.active': 'Workflows werden geprüft',
+ 'conversations.tools.checkWorkflows.done': 'Workflows geprüft',
+ 'conversations.tools.cancelWorkflow.active': 'Workflow-Ausführung wird abgebrochen',
+ 'conversations.tools.cancelWorkflow.done': 'Workflow-Ausführung abgebrochen',
+ 'conversations.tools.suggestWorkflows.active': 'Workflows werden vorgeschlagen',
+ 'conversations.tools.suggestWorkflows.done': 'Workflows vorgeschlagen',
+ 'conversations.tools.checkSettings.active': 'Einstellungen werden geprüft',
+ 'conversations.tools.checkSettings.done': 'Einstellungen geprüft',
+ 'conversations.tools.checkSecurity.active': 'Sicherheit wird geprüft',
+ 'conversations.tools.checkSecurity.done': 'Sicherheit geprüft',
+ 'conversations.tools.runDiagnostics.active': 'Diagnose wird ausgeführt',
+ 'conversations.tools.runDiagnostics.done': 'Diagnose ausgeführt',
+ 'conversations.tools.checkUsageCosts.active': 'Nutzungskosten werden geprüft',
+ 'conversations.tools.checkUsageCosts.done': 'Nutzungskosten geprüft',
+ 'conversations.tools.manageService.active': 'Hintergrunddienst wird verwaltet',
+ 'conversations.tools.manageService.done': 'Hintergrunddienst verwaltet',
+ 'conversations.tools.readPersona.active': 'Persona wird gelesen',
+ 'conversations.tools.readPersona.done': 'Persona gelesen',
+ 'conversations.tools.updatePersona.active': 'Persona wird aktualisiert',
+ 'conversations.tools.updatePersona.done': 'Persona aktualisiert',
+ 'conversations.tools.setUpWorkspace.active': 'Arbeitsbereich wird eingerichtet',
+ 'conversations.tools.setUpWorkspace.done': 'Arbeitsbereich eingerichtet',
+ 'conversations.tools.checkArtifacts.active': 'Artefakte werden geprüft',
+ 'conversations.tools.checkArtifacts.done': 'Artefakte geprüft',
+ 'conversations.tools.deleteArtifact.active': 'Artefakt wird gelöscht',
+ 'conversations.tools.deleteArtifact.done': 'Artefakt gelöscht',
'conversations.subagent.noOutput': 'Keine Ausgabe zurückgegeben',
'conversations.subagent.close': 'Schließen',
'conversations.subagent.cancel': 'Aufgabe abbrechen',
diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts
index 13f58f18356..ee8b7893b2f 100644
--- a/app/src/lib/i18n/en.ts
+++ b/app/src/lib/i18n/en.ts
@@ -3740,6 +3740,359 @@ const en: TranslationMap = {
'conversations.subagent.noOutputYet': 'No output yet',
'conversations.subagent.input': 'Input',
'conversations.subagent.output': 'Output',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} step',
+ 'conversations.tools.steps.other': '{count} steps',
+ 'conversations.tools.working': 'Working',
+ 'conversations.tools.noOutput': 'No output',
+ 'conversations.tools.delegatedTo': 'Delegated to {agent}',
+ 'conversations.tools.openInBrowser': 'Open in browser',
+ 'conversations.tools.status.running': 'running',
+ 'conversations.tools.status.done': 'done',
+ 'conversations.tools.status.failed': 'failed',
+ 'conversations.tools.status.cancelled': 'cancelled',
+ 'conversations.tools.status.awaiting': 'awaiting input',
+ 'conversations.tools.search.searching': 'Searching',
+ 'conversations.tools.search.none': 'No results',
+ 'conversations.tools.search.found.one': 'Found {count} result',
+ 'conversations.tools.search.found.other': 'Found {count} results',
+ 'conversations.tools.search.via': 'via {provider}',
+ 'conversations.tools.readFile.active': 'Reading file',
+ 'conversations.tools.readFile.done': 'Read file',
+ 'conversations.tools.writeFile.active': 'Writing file',
+ 'conversations.tools.writeFile.done': 'Wrote file',
+ 'conversations.tools.editFile.active': 'Editing file',
+ 'conversations.tools.editFile.done': 'Edited file',
+ 'conversations.tools.applyEdits.active': 'Applying edits',
+ 'conversations.tools.applyEdits.done': 'Applied edits',
+ 'conversations.tools.searchCode.active': 'Searching code',
+ 'conversations.tools.searchCode.done': 'Searched code',
+ 'conversations.tools.findFiles.active': 'Finding files',
+ 'conversations.tools.findFiles.done': 'Found files',
+ 'conversations.tools.listFolder.active': 'Listing folder',
+ 'conversations.tools.listFolder.done': 'Listed folder',
+ 'conversations.tools.exportCsv.active': 'Exporting CSV',
+ 'conversations.tools.exportCsv.done': 'Exported CSV',
+ 'conversations.tools.updateMemoryNotes.active': 'Updating memory notes',
+ 'conversations.tools.updateMemoryNotes.done': 'Updated memory notes',
+ 'conversations.tools.runGit.active': 'Running git',
+ 'conversations.tools.runGit.done': 'Ran git',
+ 'conversations.tools.readChanges.active': 'Reading changes',
+ 'conversations.tools.readChanges.done': 'Read changes',
+ 'conversations.tools.runLinter.active': 'Running linter',
+ 'conversations.tools.runLinter.done': 'Ran linter',
+ 'conversations.tools.runTests.active': 'Running tests',
+ 'conversations.tools.runTests.done': 'Ran tests',
+ 'conversations.tools.analyzeCode.active': 'Analyzing code',
+ 'conversations.tools.analyzeCode.done': 'Analyzed code',
+ 'conversations.tools.insertRecord.active': 'Inserting record',
+ 'conversations.tools.insertRecord.done': 'Inserted record',
+ 'conversations.tools.runCommand.active': 'Running command',
+ 'conversations.tools.runCommand.done': 'Ran command',
+ 'conversations.tools.runCode.active': 'Running code',
+ 'conversations.tools.runCode.done': 'Ran code',
+ 'conversations.tools.runPackageManager.active': 'Running npm',
+ 'conversations.tools.runPackageManager.done': 'Ran npm',
+ 'conversations.tools.checkInstalledTools.active': 'Checking installed tools',
+ 'conversations.tools.checkInstalledTools.done': 'Checked installed tools',
+ 'conversations.tools.installTool.active': 'Installing tool',
+ 'conversations.tools.installTool.done': 'Installed tool',
+ 'conversations.tools.checkTime.active': 'Checking the time',
+ 'conversations.tools.checkTime.done': 'Checked the time',
+ 'conversations.tools.resolveDate.active': 'Working out the date',
+ 'conversations.tools.resolveDate.done': 'Worked out the date',
+ 'conversations.tools.retrieveOutput.active': 'Retrieving full output',
+ 'conversations.tools.retrieveOutput.done': 'Retrieved full output',
+ 'conversations.tools.reviewWorkspace.active': 'Reviewing workspace',
+ 'conversations.tools.reviewWorkspace.done': 'Reviewed workspace',
+ 'conversations.tools.configureProxy.active': 'Configuring proxy',
+ 'conversations.tools.configureProxy.done': 'Configured proxy',
+ 'conversations.tools.checkUpdates.active': 'Checking for updates',
+ 'conversations.tools.checkUpdates.done': 'Checked for updates',
+ 'conversations.tools.installUpdate.active': 'Installing update',
+ 'conversations.tools.installUpdate.done': 'Installed update',
+ 'conversations.tools.sendNotification.active': 'Sending notification',
+ 'conversations.tools.sendNotification.done': 'Sent notification',
+ 'conversations.tools.reviewToolUsage.active': 'Reviewing tool usage',
+ 'conversations.tools.reviewToolUsage.done': 'Reviewed tool usage',
+ 'conversations.tools.typeKeys.active': 'Typing',
+ 'conversations.tools.typeKeys.done': 'Typed',
+ 'conversations.tools.click.active': 'Clicking',
+ 'conversations.tools.click.done': 'Clicked',
+ 'conversations.tools.searchWeb.active': 'Searching the web',
+ 'conversations.tools.searchWeb.done': 'Searched the web',
+ 'conversations.tools.searchNews.active': 'Searching news',
+ 'conversations.tools.searchNews.done': 'Searched news',
+ 'conversations.tools.searchImages.active': 'Searching images',
+ 'conversations.tools.searchImages.done': 'Searched images',
+ 'conversations.tools.searchVideos.active': 'Searching videos',
+ 'conversations.tools.searchVideos.done': 'Searched videos',
+ 'conversations.tools.findSimilarPages.active': 'Finding similar pages',
+ 'conversations.tools.findSimilarPages.done': 'Found similar pages',
+ 'conversations.tools.readPages.active': 'Reading pages',
+ 'conversations.tools.readPages.done': 'Read pages',
+ 'conversations.tools.readWebpage.active': 'Reading webpage',
+ 'conversations.tools.readWebpage.done': 'Read webpage',
+ 'conversations.tools.research.active': 'Researching',
+ 'conversations.tools.research.done': 'Researched',
+ 'conversations.tools.enrichData.active': 'Enriching data',
+ 'conversations.tools.enrichData.done': 'Enriched data',
+ 'conversations.tools.buildDataset.active': 'Building dataset',
+ 'conversations.tools.buildDataset.done': 'Built dataset',
+ 'conversations.tools.askTheWeb.active': 'Asking the web',
+ 'conversations.tools.askTheWeb.done': 'Asked the web',
+ 'conversations.tools.browseForYou.active': 'Browsing for you',
+ 'conversations.tools.browseForYou.done': 'Browsed for you',
+ 'conversations.tools.callApi.active': 'Calling API',
+ 'conversations.tools.callApi.done': 'Called API',
+ 'conversations.tools.downloadFile.active': 'Downloading file',
+ 'conversations.tools.downloadFile.done': 'Downloaded file',
+ 'conversations.tools.makePaidRequest.active': 'Making paid request',
+ 'conversations.tools.makePaidRequest.done': 'Made paid request',
+ 'conversations.tools.searchDocs.active': 'Searching docs',
+ 'conversations.tools.searchDocs.done': 'Searched docs',
+ 'conversations.tools.readDocs.active': 'Reading docs',
+ 'conversations.tools.readDocs.done': 'Read docs',
+ 'conversations.tools.useBrowser.active': 'Using browser',
+ 'conversations.tools.useBrowser.done': 'Used browser',
+ 'conversations.tools.openPage.active': 'Opening page',
+ 'conversations.tools.openPage.done': 'Opened page',
+ 'conversations.tools.navigate.active': 'Navigating',
+ 'conversations.tools.navigate.done': 'Navigated',
+ 'conversations.tools.takeScreenshot.active': 'Taking screenshot',
+ 'conversations.tools.takeScreenshot.done': 'Took screenshot',
+ 'conversations.tools.scrollPage.active': 'Scrolling',
+ 'conversations.tools.scrollPage.done': 'Scrolled',
+ 'conversations.tools.readPage.active': 'Reading page',
+ 'conversations.tools.readPage.done': 'Read page',
+ 'conversations.tools.analyzeImage.active': 'Analyzing image',
+ 'conversations.tools.analyzeImage.done': 'Analyzed image',
+ 'conversations.tools.generateImage.active': 'Generating image',
+ 'conversations.tools.generateImage.done': 'Generated image',
+ 'conversations.tools.generateVideo.active': 'Generating video',
+ 'conversations.tools.generateVideo.done': 'Generated video',
+ 'conversations.tools.checkMediaModels.active': 'Checking media models',
+ 'conversations.tools.checkMediaModels.done': 'Checked media models',
+ 'conversations.tools.createDocument.active': 'Creating document',
+ 'conversations.tools.createDocument.done': 'Created document',
+ 'conversations.tools.createPresentation.active': 'Creating presentation',
+ 'conversations.tools.createPresentation.done': 'Created presentation',
+ 'conversations.tools.generatePodcast.active': 'Generating podcast',
+ 'conversations.tools.generatePodcast.done': 'Generated podcast',
+ 'conversations.tools.emailPodcast.active': 'Emailing podcast',
+ 'conversations.tools.emailPodcast.done': 'Emailed podcast',
+ 'conversations.tools.createAndEmailPodcast.active': 'Creating and emailing podcast',
+ 'conversations.tools.createAndEmailPodcast.done': 'Created and emailed podcast',
+ 'conversations.tools.recallMemories.active': 'Recalling memories',
+ 'conversations.tools.recallMemories.done': 'Recalled memories',
+ 'conversations.tools.saveToMemory.active': 'Saving to memory',
+ 'conversations.tools.saveToMemory.done': 'Saved to memory',
+ 'conversations.tools.forgetMemory.active': 'Forgetting memory',
+ 'conversations.tools.forgetMemory.done': 'Forgot memory',
+ 'conversations.tools.searchMemory.active': 'Searching memory',
+ 'conversations.tools.searchMemory.done': 'Searched memory',
+ 'conversations.tools.inspectMemory.active': 'Inspecting memory',
+ 'conversations.tools.inspectMemory.done': 'Inspected memory',
+ 'conversations.tools.exploreMemory.active': 'Exploring memory',
+ 'conversations.tools.exploreMemory.done': 'Explored memory',
+ 'conversations.tools.saveDocumentToMemory.active': 'Saving document to memory',
+ 'conversations.tools.saveDocumentToMemory.done': 'Saved document to memory',
+ 'conversations.tools.updateGoals.active': 'Updating goals',
+ 'conversations.tools.updateGoals.done': 'Updated goals',
+ 'conversations.tools.reviewGoals.active': 'Reviewing goals',
+ 'conversations.tools.reviewGoals.done': 'Reviewed goals',
+ 'conversations.tools.savePreference.active': 'Saving preference',
+ 'conversations.tools.savePreference.done': 'Saved preference',
+ 'conversations.tools.reviewLearnings.active': 'Reviewing what I learned',
+ 'conversations.tools.reviewLearnings.done': 'Reviewed what I learned',
+ 'conversations.tools.updateLearnings.active': 'Updating what I learned',
+ 'conversations.tools.updateLearnings.done': 'Updated what I learned',
+ 'conversations.tools.delegateTask.active': 'Delegating task',
+ 'conversations.tools.delegateTask.done': 'Delegated task',
+ 'conversations.tools.runAgentsInParallel.active': 'Running agents in parallel',
+ 'conversations.tools.runAgentsInParallel.done': 'Ran agents in parallel',
+ 'conversations.tools.messageAgent.active': 'Messaging agent',
+ 'conversations.tools.messageAgent.done': 'Messaged agent',
+ 'conversations.tools.waitForAgent.active': 'Waiting for agent',
+ 'conversations.tools.waitForAgent.done': 'Waited for agent',
+ 'conversations.tools.wait.active': 'Waiting',
+ 'conversations.tools.wait.done': 'Waited',
+ 'conversations.tools.closeAgent.active': 'Closing agent',
+ 'conversations.tools.closeAgent.done': 'Closed agent',
+ 'conversations.tools.checkAgents.active': 'Checking agents',
+ 'conversations.tools.checkAgents.done': 'Checked agents',
+ 'conversations.tools.askQuestion.active': 'Asking you a question',
+ 'conversations.tools.askQuestion.done': 'Asked you a question',
+ 'conversations.tools.prepareContext.active': 'Preparing context',
+ 'conversations.tools.prepareContext.done': 'Prepared context',
+ 'conversations.tools.extractDetails.active': 'Extracting details',
+ 'conversations.tools.extractDetails.done': 'Extracted details',
+ 'conversations.tools.planNextSteps.active': 'Planning next steps',
+ 'conversations.tools.planNextSteps.done': 'Planned next steps',
+ 'conversations.tools.reviewWork.active': 'Reviewing the work',
+ 'conversations.tools.reviewWork.done': 'Reviewed the work',
+ 'conversations.tools.scoutContext.active': 'Scouting context',
+ 'conversations.tools.scoutContext.done': 'Scouted context',
+ 'conversations.tools.useTools.active': 'Using tools',
+ 'conversations.tools.useTools.done': 'Used tools',
+ 'conversations.tools.checkConnectedApp.active': 'Checking your connected app',
+ 'conversations.tools.checkConnectedApp.done': 'Checked your connected app',
+ 'conversations.tools.updateTodos.active': 'Updating to-do list',
+ 'conversations.tools.updateTodos.done': 'Updated to-do list',
+ 'conversations.tools.requestPlanReview.active': 'Requesting plan review',
+ 'conversations.tools.requestPlanReview.done': 'Requested plan review',
+ 'conversations.tools.finishPlan.active': 'Finishing plan',
+ 'conversations.tools.finishPlan.done': 'Finished plan',
+ 'conversations.tools.setGoal.active': 'Setting goal',
+ 'conversations.tools.setGoal.done': 'Set goal',
+ 'conversations.tools.checkGoal.active': 'Checking goal',
+ 'conversations.tools.checkGoal.done': 'Checked goal',
+ 'conversations.tools.completeGoal.active': 'Completing goal',
+ 'conversations.tools.completeGoal.done': 'Completed goal',
+ 'conversations.tools.scheduleTask.active': 'Scheduling task',
+ 'conversations.tools.scheduleTask.done': 'Scheduled task',
+ 'conversations.tools.checkSchedules.active': 'Checking schedules',
+ 'conversations.tools.checkSchedules.done': 'Checked schedules',
+ 'conversations.tools.updateSchedule.active': 'Updating scheduled task',
+ 'conversations.tools.updateSchedule.done': 'Updated scheduled task',
+ 'conversations.tools.removeSchedule.active': 'Removing scheduled task',
+ 'conversations.tools.removeSchedule.done': 'Removed scheduled task',
+ 'conversations.tools.runScheduledTask.active': 'Running scheduled task',
+ 'conversations.tools.runScheduledTask.done': 'Ran scheduled task',
+ 'conversations.tools.checkRunHistory.active': 'Checking run history',
+ 'conversations.tools.checkRunHistory.done': 'Checked run history',
+ 'conversations.tools.useApp.active': 'Using {app}',
+ 'conversations.tools.useApp.done': 'Used {app}',
+ 'conversations.tools.checkAvailableApps.active': 'Checking available apps',
+ 'conversations.tools.checkAvailableApps.done': 'Checked available apps',
+ 'conversations.tools.checkConnections.active': 'Checking your connections',
+ 'conversations.tools.checkConnections.done': 'Checked your connections',
+ 'conversations.tools.connectApp.active': 'Connecting app',
+ 'conversations.tools.connectApp.done': 'Connected app',
+ 'conversations.tools.authorizeApp.active': 'Authorizing app',
+ 'conversations.tools.authorizeApp.done': 'Authorized app',
+ 'conversations.tools.findAppActions.active': 'Finding app actions',
+ 'conversations.tools.findAppActions.done': 'Found app actions',
+ 'conversations.tools.runAppAction.active': 'Running app action',
+ 'conversations.tools.runAppAction.done': 'Ran app action',
+ 'conversations.tools.findTools.active': 'Finding tools',
+ 'conversations.tools.findTools.done': 'Found tools',
+ 'conversations.tools.useTool.active': 'Using {tool}',
+ 'conversations.tools.useTool.done': 'Used {tool}',
+ 'conversations.tools.unsubscribe.active': 'Unsubscribing',
+ 'conversations.tools.unsubscribe.done': 'Unsubscribed',
+ 'conversations.tools.searchPlaces.active': 'Searching places',
+ 'conversations.tools.searchPlaces.done': 'Searched places',
+ 'conversations.tools.lookUpPlace.active': 'Looking up place',
+ 'conversations.tools.lookUpPlace.done': 'Looked up place',
+ 'conversations.tools.checkMarkets.active': 'Checking markets',
+ 'conversations.tools.checkMarkets.done': 'Checked markets',
+ 'conversations.tools.placeCall.active': 'Placing call',
+ 'conversations.tools.placeCall.done': 'Placed call',
+ 'conversations.tools.checkTaskSources.active': 'Checking task sources',
+ 'conversations.tools.checkTaskSources.done': 'Checked task sources',
+ 'conversations.tools.updateTaskSources.active': 'Updating task sources',
+ 'conversations.tools.updateTaskSources.done': 'Updated task sources',
+ 'conversations.tools.fetchTasks.active': 'Fetching tasks',
+ 'conversations.tools.fetchTasks.done': 'Fetched tasks',
+ 'conversations.tools.checkMcpServers.active': 'Checking MCP servers',
+ 'conversations.tools.checkMcpServers.done': 'Checked MCP servers',
+ 'conversations.tools.checkMcpTools.active': 'Checking MCP tools',
+ 'conversations.tools.checkMcpTools.done': 'Checked MCP tools',
+ 'conversations.tools.callMcpTool.active': 'Calling {tool}',
+ 'conversations.tools.callMcpTool.done': 'Called {tool}',
+ 'conversations.tools.searchMcpServers.active': 'Searching MCP servers',
+ 'conversations.tools.searchMcpServers.done': 'Searched MCP servers',
+ 'conversations.tools.connectMcpServer.active': 'Connecting MCP server',
+ 'conversations.tools.connectMcpServer.done': 'Connected MCP server',
+ 'conversations.tools.disconnectMcpServer.active': 'Disconnecting MCP server',
+ 'conversations.tools.disconnectMcpServer.done': 'Disconnected MCP server',
+ 'conversations.tools.removeMcpServer.active': 'Removing MCP server',
+ 'conversations.tools.removeMcpServer.done': 'Removed MCP server',
+ 'conversations.tools.uploadFile.active': 'Uploading file',
+ 'conversations.tools.uploadFile.done': 'Uploaded file',
+ 'conversations.tools.listStoredFiles.active': 'Listing stored files',
+ 'conversations.tools.listStoredFiles.done': 'Listed stored files',
+ 'conversations.tools.createShareLink.active': 'Creating share link',
+ 'conversations.tools.createShareLink.done': 'Created share link',
+ 'conversations.tools.deleteFile.active': 'Deleting file',
+ 'conversations.tools.deleteFile.done': 'Deleted file',
+ 'conversations.tools.updateFileAccess.active': 'Updating file access',
+ 'conversations.tools.updateFileAccess.done': 'Updated file access',
+ 'conversations.tools.deploySite.active': 'Deploying site',
+ 'conversations.tools.deploySite.done': 'Deployed site',
+ 'conversations.tools.checkHosting.active': 'Checking hosting',
+ 'conversations.tools.checkHosting.done': 'Checked hosting',
+ 'conversations.tools.updateHosting.active': 'Updating hosting',
+ 'conversations.tools.updateHosting.done': 'Updated hosting',
+ 'conversations.tools.rollBackDeployment.active': 'Rolling back deployment',
+ 'conversations.tools.rollBackDeployment.done': 'Rolled back deployment',
+ 'conversations.tools.checkWallet.active': 'Checking wallet',
+ 'conversations.tools.checkWallet.done': 'Checked wallet',
+ 'conversations.tools.prepareTransfer.active': 'Preparing transfer',
+ 'conversations.tools.prepareTransfer.done': 'Prepared transfer',
+ 'conversations.tools.checkTransaction.active': 'Checking transaction',
+ 'conversations.tools.checkTransaction.done': 'Checked transaction',
+ 'conversations.tools.getSwapQuote.active': 'Getting swap quote',
+ 'conversations.tools.getSwapQuote.done': 'Got swap quote',
+ 'conversations.tools.swapTokens.active': 'Swapping tokens',
+ 'conversations.tools.swapTokens.done': 'Swapped tokens',
+ 'conversations.tools.getBridgeQuote.active': 'Getting bridge quote',
+ 'conversations.tools.getBridgeQuote.done': 'Got bridge quote',
+ 'conversations.tools.bridgeTokens.active': 'Bridging tokens',
+ 'conversations.tools.bridgeTokens.done': 'Bridged tokens',
+ 'conversations.tools.callDapp.active': 'Calling app contract',
+ 'conversations.tools.callDapp.done': 'Called app contract',
+ 'conversations.tools.useSkill.active': 'Using skill',
+ 'conversations.tools.useSkill.done': 'Used skill',
+ 'conversations.tools.searchSkills.active': 'Searching skills',
+ 'conversations.tools.searchSkills.done': 'Searched skills',
+ 'conversations.tools.checkSkills.active': 'Checking skills',
+ 'conversations.tools.checkSkills.done': 'Checked skills',
+ 'conversations.tools.installSkill.active': 'Installing skill',
+ 'conversations.tools.installSkill.done': 'Installed skill',
+ 'conversations.tools.removeSkill.active': 'Removing skill',
+ 'conversations.tools.removeSkill.done': 'Removed skill',
+ 'conversations.tools.createSkill.active': 'Creating skill',
+ 'conversations.tools.createSkill.done': 'Created skill',
+ 'conversations.tools.runWorkflow.active': 'Running workflow',
+ 'conversations.tools.runWorkflow.done': 'Ran workflow',
+ 'conversations.tools.waitForWorkflow.active': 'Waiting for workflow',
+ 'conversations.tools.waitForWorkflow.done': 'Waited for workflow',
+ 'conversations.tools.designWorkflow.active': 'Designing workflow',
+ 'conversations.tools.designWorkflow.done': 'Designed workflow',
+ 'conversations.tools.saveWorkflow.active': 'Saving workflow',
+ 'conversations.tools.saveWorkflow.done': 'Saved workflow',
+ 'conversations.tools.validateWorkflow.active': 'Validating workflow',
+ 'conversations.tools.validateWorkflow.done': 'Validated workflow',
+ 'conversations.tools.testWorkflow.active': 'Testing workflow',
+ 'conversations.tools.testWorkflow.done': 'Tested workflow',
+ 'conversations.tools.checkWorkflows.active': 'Checking workflows',
+ 'conversations.tools.checkWorkflows.done': 'Checked workflows',
+ 'conversations.tools.cancelWorkflow.active': 'Cancelling workflow run',
+ 'conversations.tools.cancelWorkflow.done': 'Cancelled workflow run',
+ 'conversations.tools.suggestWorkflows.active': 'Suggesting workflows',
+ 'conversations.tools.suggestWorkflows.done': 'Suggested workflows',
+ 'conversations.tools.checkSettings.active': 'Checking settings',
+ 'conversations.tools.checkSettings.done': 'Checked settings',
+ 'conversations.tools.checkSecurity.active': 'Checking security',
+ 'conversations.tools.checkSecurity.done': 'Checked security',
+ 'conversations.tools.runDiagnostics.active': 'Running diagnostics',
+ 'conversations.tools.runDiagnostics.done': 'Ran diagnostics',
+ 'conversations.tools.checkUsageCosts.active': 'Checking usage costs',
+ 'conversations.tools.checkUsageCosts.done': 'Checked usage costs',
+ 'conversations.tools.manageService.active': 'Managing background service',
+ 'conversations.tools.manageService.done': 'Managed background service',
+ 'conversations.tools.readPersona.active': 'Reading persona',
+ 'conversations.tools.readPersona.done': 'Read persona',
+ 'conversations.tools.updatePersona.active': 'Updating persona',
+ 'conversations.tools.updatePersona.done': 'Updated persona',
+ 'conversations.tools.setUpWorkspace.active': 'Setting up workspace',
+ 'conversations.tools.setUpWorkspace.done': 'Set up workspace',
+ 'conversations.tools.checkArtifacts.active': 'Checking artifacts',
+ 'conversations.tools.checkArtifacts.done': 'Checked artifacts',
+ 'conversations.tools.deleteArtifact.active': 'Deleting artifact',
+ 'conversations.tools.deleteArtifact.done': 'Deleted artifact',
'conversations.subagent.noOutput': 'No output returned',
'conversations.subagent.close': 'Close',
'conversations.subagent.cancel': 'Cancel task',
diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts
index fcbf565d354..74b90d0fdda 100644
--- a/app/src/lib/i18n/es.ts
+++ b/app/src/lib/i18n/es.ts
@@ -3374,6 +3374,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'Aún no hay resultados',
'conversations.subagent.input': 'Entrada',
'conversations.subagent.output': 'Salida',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} paso',
+ 'conversations.tools.steps.other': '{count} pasos',
+ 'conversations.tools.working': 'Trabajando',
+ 'conversations.tools.noOutput': 'Sin salida',
+ 'conversations.tools.delegatedTo': 'Delegado a {agent}',
+ 'conversations.tools.openInBrowser': 'Abrir en el navegador',
+ 'conversations.tools.status.running': 'en curso',
+ 'conversations.tools.status.done': 'hecho',
+ 'conversations.tools.status.failed': 'fallido',
+ 'conversations.tools.status.cancelled': 'cancelado',
+ 'conversations.tools.status.awaiting': 'esperando respuesta',
+ 'conversations.tools.search.searching': 'Buscando',
+ 'conversations.tools.search.none': 'Sin resultados',
+ 'conversations.tools.search.found.one': '{count} resultado encontrado',
+ 'conversations.tools.search.found.other': '{count} resultados encontrados',
+ 'conversations.tools.search.via': 'vía {provider}',
+ 'conversations.tools.readFile.active': 'Leyendo archivo',
+ 'conversations.tools.readFile.done': 'Archivo leído',
+ 'conversations.tools.writeFile.active': 'Escribiendo archivo',
+ 'conversations.tools.writeFile.done': 'Archivo escrito',
+ 'conversations.tools.editFile.active': 'Editando archivo',
+ 'conversations.tools.editFile.done': 'Archivo editado',
+ 'conversations.tools.applyEdits.active': 'Aplicando cambios',
+ 'conversations.tools.applyEdits.done': 'Cambios aplicados',
+ 'conversations.tools.searchCode.active': 'Buscando en el código',
+ 'conversations.tools.searchCode.done': 'Código buscado',
+ 'conversations.tools.findFiles.active': 'Buscando archivos',
+ 'conversations.tools.findFiles.done': 'Archivos encontrados',
+ 'conversations.tools.listFolder.active': 'Listando carpeta',
+ 'conversations.tools.listFolder.done': 'Carpeta listada',
+ 'conversations.tools.exportCsv.active': 'Exportando CSV',
+ 'conversations.tools.exportCsv.done': 'CSV exportado',
+ 'conversations.tools.updateMemoryNotes.active': 'Actualizando notas de memoria',
+ 'conversations.tools.updateMemoryNotes.done': 'Notas de memoria actualizadas',
+ 'conversations.tools.runGit.active': 'Ejecutando git',
+ 'conversations.tools.runGit.done': 'git ejecutado',
+ 'conversations.tools.readChanges.active': 'Leyendo cambios',
+ 'conversations.tools.readChanges.done': 'Cambios leídos',
+ 'conversations.tools.runLinter.active': 'Ejecutando linter',
+ 'conversations.tools.runLinter.done': 'Linter ejecutado',
+ 'conversations.tools.runTests.active': 'Ejecutando pruebas',
+ 'conversations.tools.runTests.done': 'Pruebas ejecutadas',
+ 'conversations.tools.analyzeCode.active': 'Analizando código',
+ 'conversations.tools.analyzeCode.done': 'Código analizado',
+ 'conversations.tools.insertRecord.active': 'Insertando registro',
+ 'conversations.tools.insertRecord.done': 'Registro insertado',
+ 'conversations.tools.runCommand.active': 'Ejecutando comando',
+ 'conversations.tools.runCommand.done': 'Comando ejecutado',
+ 'conversations.tools.runCode.active': 'Ejecutando código',
+ 'conversations.tools.runCode.done': 'Código ejecutado',
+ 'conversations.tools.runPackageManager.active': 'Ejecutando npm',
+ 'conversations.tools.runPackageManager.done': 'npm ejecutado',
+ 'conversations.tools.checkInstalledTools.active': 'Comprobando herramientas instaladas',
+ 'conversations.tools.checkInstalledTools.done': 'Herramientas instaladas comprobadas',
+ 'conversations.tools.installTool.active': 'Instalando herramienta',
+ 'conversations.tools.installTool.done': 'Herramienta instalada',
+ 'conversations.tools.checkTime.active': 'Consultando la hora',
+ 'conversations.tools.checkTime.done': 'Hora consultada',
+ 'conversations.tools.resolveDate.active': 'Calculando la fecha',
+ 'conversations.tools.resolveDate.done': 'Fecha calculada',
+ 'conversations.tools.retrieveOutput.active': 'Recuperando la salida completa',
+ 'conversations.tools.retrieveOutput.done': 'Salida completa recuperada',
+ 'conversations.tools.reviewWorkspace.active': 'Revisando el espacio de trabajo',
+ 'conversations.tools.reviewWorkspace.done': 'Espacio de trabajo revisado',
+ 'conversations.tools.configureProxy.active': 'Configurando proxy',
+ 'conversations.tools.configureProxy.done': 'Proxy configurado',
+ 'conversations.tools.checkUpdates.active': 'Buscando actualizaciones',
+ 'conversations.tools.checkUpdates.done': 'Actualizaciones comprobadas',
+ 'conversations.tools.installUpdate.active': 'Instalando actualización',
+ 'conversations.tools.installUpdate.done': 'Actualización instalada',
+ 'conversations.tools.sendNotification.active': 'Enviando notificación',
+ 'conversations.tools.sendNotification.done': 'Notificación enviada',
+ 'conversations.tools.reviewToolUsage.active': 'Revisando el uso de herramientas',
+ 'conversations.tools.reviewToolUsage.done': 'Uso de herramientas revisado',
+ 'conversations.tools.typeKeys.active': 'Escribiendo',
+ 'conversations.tools.typeKeys.done': 'Texto escrito',
+ 'conversations.tools.click.active': 'Haciendo clic',
+ 'conversations.tools.click.done': 'Clic hecho',
+ 'conversations.tools.searchWeb.active': 'Buscando en la web',
+ 'conversations.tools.searchWeb.done': 'Búsqueda web hecha',
+ 'conversations.tools.searchNews.active': 'Buscando noticias',
+ 'conversations.tools.searchNews.done': 'Noticias buscadas',
+ 'conversations.tools.searchImages.active': 'Buscando imágenes',
+ 'conversations.tools.searchImages.done': 'Imágenes buscadas',
+ 'conversations.tools.searchVideos.active': 'Buscando vídeos',
+ 'conversations.tools.searchVideos.done': 'Vídeos buscados',
+ 'conversations.tools.findSimilarPages.active': 'Buscando páginas similares',
+ 'conversations.tools.findSimilarPages.done': 'Páginas similares encontradas',
+ 'conversations.tools.readPages.active': 'Leyendo páginas',
+ 'conversations.tools.readPages.done': 'Páginas leídas',
+ 'conversations.tools.readWebpage.active': 'Leyendo página web',
+ 'conversations.tools.readWebpage.done': 'Página web leída',
+ 'conversations.tools.research.active': 'Investigando',
+ 'conversations.tools.research.done': 'Investigación hecha',
+ 'conversations.tools.enrichData.active': 'Enriqueciendo datos',
+ 'conversations.tools.enrichData.done': 'Datos enriquecidos',
+ 'conversations.tools.buildDataset.active': 'Creando conjunto de datos',
+ 'conversations.tools.buildDataset.done': 'Conjunto de datos creado',
+ 'conversations.tools.askTheWeb.active': 'Consultando la web',
+ 'conversations.tools.askTheWeb.done': 'Web consultada',
+ 'conversations.tools.browseForYou.active': 'Navegando por ti',
+ 'conversations.tools.browseForYou.done': 'Navegación hecha por ti',
+ 'conversations.tools.callApi.active': 'Llamando a la API',
+ 'conversations.tools.callApi.done': 'API llamada',
+ 'conversations.tools.downloadFile.active': 'Descargando archivo',
+ 'conversations.tools.downloadFile.done': 'Archivo descargado',
+ 'conversations.tools.makePaidRequest.active': 'Haciendo solicitud de pago',
+ 'conversations.tools.makePaidRequest.done': 'Solicitud de pago hecha',
+ 'conversations.tools.searchDocs.active': 'Buscando en la documentación',
+ 'conversations.tools.searchDocs.done': 'Documentación consultada',
+ 'conversations.tools.readDocs.active': 'Leyendo la documentación',
+ 'conversations.tools.readDocs.done': 'Documentación leída',
+ 'conversations.tools.useBrowser.active': 'Usando el navegador',
+ 'conversations.tools.useBrowser.done': 'Navegador usado',
+ 'conversations.tools.openPage.active': 'Abriendo página',
+ 'conversations.tools.openPage.done': 'Página abierta',
+ 'conversations.tools.navigate.active': 'Navegando',
+ 'conversations.tools.navigate.done': 'Navegación hecha',
+ 'conversations.tools.takeScreenshot.active': 'Tomando captura de pantalla',
+ 'conversations.tools.takeScreenshot.done': 'Captura de pantalla tomada',
+ 'conversations.tools.scrollPage.active': 'Desplazando',
+ 'conversations.tools.scrollPage.done': 'Desplazamiento hecho',
+ 'conversations.tools.readPage.active': 'Leyendo página',
+ 'conversations.tools.readPage.done': 'Página leída',
+ 'conversations.tools.analyzeImage.active': 'Analizando imagen',
+ 'conversations.tools.analyzeImage.done': 'Imagen analizada',
+ 'conversations.tools.generateImage.active': 'Generando imagen',
+ 'conversations.tools.generateImage.done': 'Imagen generada',
+ 'conversations.tools.generateVideo.active': 'Generando vídeo',
+ 'conversations.tools.generateVideo.done': 'Vídeo generado',
+ 'conversations.tools.checkMediaModels.active': 'Comprobando modelos multimedia',
+ 'conversations.tools.checkMediaModels.done': 'Modelos multimedia comprobados',
+ 'conversations.tools.createDocument.active': 'Creando documento',
+ 'conversations.tools.createDocument.done': 'Documento creado',
+ 'conversations.tools.createPresentation.active': 'Creando presentación',
+ 'conversations.tools.createPresentation.done': 'Presentación creada',
+ 'conversations.tools.generatePodcast.active': 'Generando pódcast',
+ 'conversations.tools.generatePodcast.done': 'Pódcast generado',
+ 'conversations.tools.emailPodcast.active': 'Enviando pódcast por correo',
+ 'conversations.tools.emailPodcast.done': 'Pódcast enviado por correo',
+ 'conversations.tools.createAndEmailPodcast.active': 'Creando y enviando pódcast por correo',
+ 'conversations.tools.createAndEmailPodcast.done': 'Pódcast creado y enviado por correo',
+ 'conversations.tools.recallMemories.active': 'Recordando memorias',
+ 'conversations.tools.recallMemories.done': 'Memorias recordadas',
+ 'conversations.tools.saveToMemory.active': 'Guardando en la memoria',
+ 'conversations.tools.saveToMemory.done': 'Guardado en la memoria',
+ 'conversations.tools.forgetMemory.active': 'Olvidando recuerdo',
+ 'conversations.tools.forgetMemory.done': 'Recuerdo olvidado',
+ 'conversations.tools.searchMemory.active': 'Buscando en la memoria',
+ 'conversations.tools.searchMemory.done': 'Memoria consultada',
+ 'conversations.tools.inspectMemory.active': 'Inspeccionando la memoria',
+ 'conversations.tools.inspectMemory.done': 'Memoria inspeccionada',
+ 'conversations.tools.exploreMemory.active': 'Explorando la memoria',
+ 'conversations.tools.exploreMemory.done': 'Memoria explorada',
+ 'conversations.tools.saveDocumentToMemory.active': 'Guardando documento en la memoria',
+ 'conversations.tools.saveDocumentToMemory.done': 'Documento guardado en la memoria',
+ 'conversations.tools.updateGoals.active': 'Actualizando objetivos',
+ 'conversations.tools.updateGoals.done': 'Objetivos actualizados',
+ 'conversations.tools.reviewGoals.active': 'Revisando objetivos',
+ 'conversations.tools.reviewGoals.done': 'Objetivos revisados',
+ 'conversations.tools.savePreference.active': 'Guardando preferencia',
+ 'conversations.tools.savePreference.done': 'Preferencia guardada',
+ 'conversations.tools.reviewLearnings.active': 'Revisando lo aprendido',
+ 'conversations.tools.reviewLearnings.done': 'Aprendizajes revisados',
+ 'conversations.tools.updateLearnings.active': 'Actualizando lo aprendido',
+ 'conversations.tools.updateLearnings.done': 'Aprendizajes actualizados',
+ 'conversations.tools.delegateTask.active': 'Delegando tarea',
+ 'conversations.tools.delegateTask.done': 'Tarea delegada',
+ 'conversations.tools.runAgentsInParallel.active': 'Ejecutando agentes en paralelo',
+ 'conversations.tools.runAgentsInParallel.done': 'Agentes ejecutados en paralelo',
+ 'conversations.tools.messageAgent.active': 'Enviando mensaje al agente',
+ 'conversations.tools.messageAgent.done': 'Mensaje enviado al agente',
+ 'conversations.tools.waitForAgent.active': 'Esperando al agente',
+ 'conversations.tools.waitForAgent.done': 'Espera al agente terminada',
+ 'conversations.tools.wait.active': 'Esperando',
+ 'conversations.tools.wait.done': 'Espera terminada',
+ 'conversations.tools.closeAgent.active': 'Cerrando agente',
+ 'conversations.tools.closeAgent.done': 'Agente cerrado',
+ 'conversations.tools.checkAgents.active': 'Comprobando agentes',
+ 'conversations.tools.checkAgents.done': 'Agentes comprobados',
+ 'conversations.tools.askQuestion.active': 'Haciéndote una pregunta',
+ 'conversations.tools.askQuestion.done': 'Te hice una pregunta',
+ 'conversations.tools.prepareContext.active': 'Preparando contexto',
+ 'conversations.tools.prepareContext.done': 'Contexto preparado',
+ 'conversations.tools.extractDetails.active': 'Extrayendo detalles',
+ 'conversations.tools.extractDetails.done': 'Detalles extraídos',
+ 'conversations.tools.planNextSteps.active': 'Planificando próximos pasos',
+ 'conversations.tools.planNextSteps.done': 'Próximos pasos planificados',
+ 'conversations.tools.reviewWork.active': 'Revisando el trabajo',
+ 'conversations.tools.reviewWork.done': 'Trabajo revisado',
+ 'conversations.tools.scoutContext.active': 'Explorando el contexto',
+ 'conversations.tools.scoutContext.done': 'Contexto explorado',
+ 'conversations.tools.useTools.active': 'Usando herramientas',
+ 'conversations.tools.useTools.done': 'Herramientas usadas',
+ 'conversations.tools.checkConnectedApp.active': 'Comprobando tu app conectada',
+ 'conversations.tools.checkConnectedApp.done': 'App conectada comprobada',
+ 'conversations.tools.updateTodos.active': 'Actualizando lista de tareas',
+ 'conversations.tools.updateTodos.done': 'Lista de tareas actualizada',
+ 'conversations.tools.requestPlanReview.active': 'Solicitando revisión del plan',
+ 'conversations.tools.requestPlanReview.done': 'Revisión del plan solicitada',
+ 'conversations.tools.finishPlan.active': 'Terminando el plan',
+ 'conversations.tools.finishPlan.done': 'Plan terminado',
+ 'conversations.tools.setGoal.active': 'Estableciendo objetivo',
+ 'conversations.tools.setGoal.done': 'Objetivo establecido',
+ 'conversations.tools.checkGoal.active': 'Comprobando objetivo',
+ 'conversations.tools.checkGoal.done': 'Objetivo comprobado',
+ 'conversations.tools.completeGoal.active': 'Completando objetivo',
+ 'conversations.tools.completeGoal.done': 'Objetivo completado',
+ 'conversations.tools.scheduleTask.active': 'Programando tarea',
+ 'conversations.tools.scheduleTask.done': 'Tarea programada',
+ 'conversations.tools.checkSchedules.active': 'Comprobando programaciones',
+ 'conversations.tools.checkSchedules.done': 'Programaciones comprobadas',
+ 'conversations.tools.updateSchedule.active': 'Actualizando tarea programada',
+ 'conversations.tools.updateSchedule.done': 'Tarea programada actualizada',
+ 'conversations.tools.removeSchedule.active': 'Eliminando tarea programada',
+ 'conversations.tools.removeSchedule.done': 'Tarea programada eliminada',
+ 'conversations.tools.runScheduledTask.active': 'Ejecutando tarea programada',
+ 'conversations.tools.runScheduledTask.done': 'Tarea programada ejecutada',
+ 'conversations.tools.checkRunHistory.active': 'Comprobando historial de ejecuciones',
+ 'conversations.tools.checkRunHistory.done': 'Historial de ejecuciones comprobado',
+ 'conversations.tools.useApp.active': 'Usando {app}',
+ 'conversations.tools.useApp.done': '{app} usado',
+ 'conversations.tools.checkAvailableApps.active': 'Comprobando apps disponibles',
+ 'conversations.tools.checkAvailableApps.done': 'Apps disponibles comprobadas',
+ 'conversations.tools.checkConnections.active': 'Comprobando tus conexiones',
+ 'conversations.tools.checkConnections.done': 'Conexiones comprobadas',
+ 'conversations.tools.connectApp.active': 'Conectando app',
+ 'conversations.tools.connectApp.done': 'App conectada',
+ 'conversations.tools.authorizeApp.active': 'Autorizando app',
+ 'conversations.tools.authorizeApp.done': 'App autorizada',
+ 'conversations.tools.findAppActions.active': 'Buscando acciones de la app',
+ 'conversations.tools.findAppActions.done': 'Acciones de la app encontradas',
+ 'conversations.tools.runAppAction.active': 'Ejecutando acción de la app',
+ 'conversations.tools.runAppAction.done': 'Acción de la app ejecutada',
+ 'conversations.tools.findTools.active': 'Buscando herramientas',
+ 'conversations.tools.findTools.done': 'Herramientas encontradas',
+ 'conversations.tools.useTool.active': 'Usando {tool}',
+ 'conversations.tools.useTool.done': '{tool} usado',
+ 'conversations.tools.unsubscribe.active': 'Cancelando suscripción',
+ 'conversations.tools.unsubscribe.done': 'Suscripción cancelada',
+ 'conversations.tools.searchPlaces.active': 'Buscando lugares',
+ 'conversations.tools.searchPlaces.done': 'Lugares buscados',
+ 'conversations.tools.lookUpPlace.active': 'Consultando lugar',
+ 'conversations.tools.lookUpPlace.done': 'Lugar consultado',
+ 'conversations.tools.checkMarkets.active': 'Consultando mercados',
+ 'conversations.tools.checkMarkets.done': 'Mercados consultados',
+ 'conversations.tools.placeCall.active': 'Realizando llamada',
+ 'conversations.tools.placeCall.done': 'Llamada realizada',
+ 'conversations.tools.checkTaskSources.active': 'Comprobando fuentes de tareas',
+ 'conversations.tools.checkTaskSources.done': 'Fuentes de tareas comprobadas',
+ 'conversations.tools.updateTaskSources.active': 'Actualizando fuentes de tareas',
+ 'conversations.tools.updateTaskSources.done': 'Fuentes de tareas actualizadas',
+ 'conversations.tools.fetchTasks.active': 'Obteniendo tareas',
+ 'conversations.tools.fetchTasks.done': 'Tareas obtenidas',
+ 'conversations.tools.checkMcpServers.active': 'Comprobando servidores MCP',
+ 'conversations.tools.checkMcpServers.done': 'Servidores MCP comprobados',
+ 'conversations.tools.checkMcpTools.active': 'Comprobando herramientas MCP',
+ 'conversations.tools.checkMcpTools.done': 'Herramientas MCP comprobadas',
+ 'conversations.tools.callMcpTool.active': 'Llamando a {tool}',
+ 'conversations.tools.callMcpTool.done': '{tool} llamado',
+ 'conversations.tools.searchMcpServers.active': 'Buscando servidores MCP',
+ 'conversations.tools.searchMcpServers.done': 'Servidores MCP buscados',
+ 'conversations.tools.connectMcpServer.active': 'Conectando servidor MCP',
+ 'conversations.tools.connectMcpServer.done': 'Servidor MCP conectado',
+ 'conversations.tools.disconnectMcpServer.active': 'Desconectando servidor MCP',
+ 'conversations.tools.disconnectMcpServer.done': 'Servidor MCP desconectado',
+ 'conversations.tools.removeMcpServer.active': 'Eliminando servidor MCP',
+ 'conversations.tools.removeMcpServer.done': 'Servidor MCP eliminado',
+ 'conversations.tools.uploadFile.active': 'Subiendo archivo',
+ 'conversations.tools.uploadFile.done': 'Archivo subido',
+ 'conversations.tools.listStoredFiles.active': 'Listando archivos guardados',
+ 'conversations.tools.listStoredFiles.done': 'Archivos guardados listados',
+ 'conversations.tools.createShareLink.active': 'Creando enlace para compartir',
+ 'conversations.tools.createShareLink.done': 'Enlace para compartir creado',
+ 'conversations.tools.deleteFile.active': 'Eliminando archivo',
+ 'conversations.tools.deleteFile.done': 'Archivo eliminado',
+ 'conversations.tools.updateFileAccess.active': 'Actualizando acceso al archivo',
+ 'conversations.tools.updateFileAccess.done': 'Acceso al archivo actualizado',
+ 'conversations.tools.deploySite.active': 'Desplegando sitio',
+ 'conversations.tools.deploySite.done': 'Sitio desplegado',
+ 'conversations.tools.checkHosting.active': 'Comprobando alojamiento',
+ 'conversations.tools.checkHosting.done': 'Alojamiento comprobado',
+ 'conversations.tools.updateHosting.active': 'Actualizando alojamiento',
+ 'conversations.tools.updateHosting.done': 'Alojamiento actualizado',
+ 'conversations.tools.rollBackDeployment.active': 'Revirtiendo despliegue',
+ 'conversations.tools.rollBackDeployment.done': 'Despliegue revertido',
+ 'conversations.tools.checkWallet.active': 'Comprobando billetera',
+ 'conversations.tools.checkWallet.done': 'Billetera comprobada',
+ 'conversations.tools.prepareTransfer.active': 'Preparando transferencia',
+ 'conversations.tools.prepareTransfer.done': 'Transferencia preparada',
+ 'conversations.tools.checkTransaction.active': 'Comprobando transacción',
+ 'conversations.tools.checkTransaction.done': 'Transacción comprobada',
+ 'conversations.tools.getSwapQuote.active': 'Obteniendo cotización de intercambio',
+ 'conversations.tools.getSwapQuote.done': 'Cotización de intercambio obtenida',
+ 'conversations.tools.swapTokens.active': 'Intercambiando tokens',
+ 'conversations.tools.swapTokens.done': 'Tokens intercambiados',
+ 'conversations.tools.getBridgeQuote.active': 'Obteniendo cotización de puente',
+ 'conversations.tools.getBridgeQuote.done': 'Cotización de puente obtenida',
+ 'conversations.tools.bridgeTokens.active': 'Transfiriendo tokens por puente',
+ 'conversations.tools.bridgeTokens.done': 'Tokens transferidos por puente',
+ 'conversations.tools.callDapp.active': 'Llamando al contrato de la app',
+ 'conversations.tools.callDapp.done': 'Contrato de la app llamado',
+ 'conversations.tools.useSkill.active': 'Usando habilidad',
+ 'conversations.tools.useSkill.done': 'Habilidad usada',
+ 'conversations.tools.searchSkills.active': 'Buscando habilidades',
+ 'conversations.tools.searchSkills.done': 'Habilidades buscadas',
+ 'conversations.tools.checkSkills.active': 'Comprobando habilidades',
+ 'conversations.tools.checkSkills.done': 'Habilidades comprobadas',
+ 'conversations.tools.installSkill.active': 'Instalando habilidad',
+ 'conversations.tools.installSkill.done': 'Habilidad instalada',
+ 'conversations.tools.removeSkill.active': 'Eliminando habilidad',
+ 'conversations.tools.removeSkill.done': 'Habilidad eliminada',
+ 'conversations.tools.createSkill.active': 'Creando habilidad',
+ 'conversations.tools.createSkill.done': 'Habilidad creada',
+ 'conversations.tools.runWorkflow.active': 'Ejecutando flujo de trabajo',
+ 'conversations.tools.runWorkflow.done': 'Flujo de trabajo ejecutado',
+ 'conversations.tools.waitForWorkflow.active': 'Esperando el flujo de trabajo',
+ 'conversations.tools.waitForWorkflow.done': 'Espera del flujo de trabajo terminada',
+ 'conversations.tools.designWorkflow.active': 'Diseñando flujo de trabajo',
+ 'conversations.tools.designWorkflow.done': 'Flujo de trabajo diseñado',
+ 'conversations.tools.saveWorkflow.active': 'Guardando flujo de trabajo',
+ 'conversations.tools.saveWorkflow.done': 'Flujo de trabajo guardado',
+ 'conversations.tools.validateWorkflow.active': 'Validando flujo de trabajo',
+ 'conversations.tools.validateWorkflow.done': 'Flujo de trabajo validado',
+ 'conversations.tools.testWorkflow.active': 'Probando flujo de trabajo',
+ 'conversations.tools.testWorkflow.done': 'Flujo de trabajo probado',
+ 'conversations.tools.checkWorkflows.active': 'Comprobando flujos de trabajo',
+ 'conversations.tools.checkWorkflows.done': 'Flujos de trabajo comprobados',
+ 'conversations.tools.cancelWorkflow.active': 'Cancelando ejecución del flujo de trabajo',
+ 'conversations.tools.cancelWorkflow.done': 'Ejecución del flujo de trabajo cancelada',
+ 'conversations.tools.suggestWorkflows.active': 'Sugiriendo flujos de trabajo',
+ 'conversations.tools.suggestWorkflows.done': 'Flujos de trabajo sugeridos',
+ 'conversations.tools.checkSettings.active': 'Comprobando ajustes',
+ 'conversations.tools.checkSettings.done': 'Ajustes comprobados',
+ 'conversations.tools.checkSecurity.active': 'Comprobando seguridad',
+ 'conversations.tools.checkSecurity.done': 'Seguridad comprobada',
+ 'conversations.tools.runDiagnostics.active': 'Ejecutando diagnósticos',
+ 'conversations.tools.runDiagnostics.done': 'Diagnósticos ejecutados',
+ 'conversations.tools.checkUsageCosts.active': 'Comprobando costes de uso',
+ 'conversations.tools.checkUsageCosts.done': 'Costes de uso comprobados',
+ 'conversations.tools.manageService.active': 'Gestionando servicio en segundo plano',
+ 'conversations.tools.manageService.done': 'Servicio en segundo plano gestionado',
+ 'conversations.tools.readPersona.active': 'Leyendo personalidad',
+ 'conversations.tools.readPersona.done': 'Personalidad leída',
+ 'conversations.tools.updatePersona.active': 'Actualizando personalidad',
+ 'conversations.tools.updatePersona.done': 'Personalidad actualizada',
+ 'conversations.tools.setUpWorkspace.active': 'Configurando espacio de trabajo',
+ 'conversations.tools.setUpWorkspace.done': 'Espacio de trabajo configurado',
+ 'conversations.tools.checkArtifacts.active': 'Comprobando artefactos',
+ 'conversations.tools.checkArtifacts.done': 'Artefactos comprobados',
+ 'conversations.tools.deleteArtifact.active': 'Eliminando artefacto',
+ 'conversations.tools.deleteArtifact.done': 'Artefacto eliminado',
'conversations.subagent.noOutput': 'No se devolvió ninguna salida',
'conversations.subagent.close': 'Cerrar',
'conversations.subagent.cancel': 'Cancelar tarea',
diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts
index 9b9f4e9f97b..4c45a3aac01 100644
--- a/app/src/lib/i18n/fr.ts
+++ b/app/src/lib/i18n/fr.ts
@@ -3398,6 +3398,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'Aucun résultat pour l’instant',
'conversations.subagent.input': 'Entrée',
'conversations.subagent.output': 'Sortie',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} étape',
+ 'conversations.tools.steps.other': '{count} étapes',
+ 'conversations.tools.working': 'En cours',
+ 'conversations.tools.noOutput': 'Aucune sortie',
+ 'conversations.tools.delegatedTo': 'Délégué à {agent}',
+ 'conversations.tools.openInBrowser': 'Ouvrir dans le navigateur',
+ 'conversations.tools.status.running': 'en cours',
+ 'conversations.tools.status.done': 'terminé',
+ 'conversations.tools.status.failed': 'échec',
+ 'conversations.tools.status.cancelled': 'annulé',
+ 'conversations.tools.status.awaiting': 'en attente de réponse',
+ 'conversations.tools.search.searching': 'Recherche en cours',
+ 'conversations.tools.search.none': 'Aucun résultat',
+ 'conversations.tools.search.found.one': '{count} résultat trouvé',
+ 'conversations.tools.search.found.other': '{count} résultats trouvés',
+ 'conversations.tools.search.via': 'par {provider}',
+ 'conversations.tools.readFile.active': 'Lecture du fichier',
+ 'conversations.tools.readFile.done': 'Fichier lu',
+ 'conversations.tools.writeFile.active': 'Écriture du fichier',
+ 'conversations.tools.writeFile.done': 'Fichier écrit',
+ 'conversations.tools.editFile.active': 'Modification du fichier',
+ 'conversations.tools.editFile.done': 'Fichier modifié',
+ 'conversations.tools.applyEdits.active': 'Application des modifications',
+ 'conversations.tools.applyEdits.done': 'Modifications appliquées',
+ 'conversations.tools.searchCode.active': 'Recherche dans le code',
+ 'conversations.tools.searchCode.done': 'Code parcouru',
+ 'conversations.tools.findFiles.active': 'Recherche de fichiers',
+ 'conversations.tools.findFiles.done': 'Fichiers trouvés',
+ 'conversations.tools.listFolder.active': 'Affichage du dossier',
+ 'conversations.tools.listFolder.done': 'Dossier affiché',
+ 'conversations.tools.exportCsv.active': 'Export du CSV',
+ 'conversations.tools.exportCsv.done': 'CSV exporté',
+ 'conversations.tools.updateMemoryNotes.active': 'Mise à jour des notes de mémoire',
+ 'conversations.tools.updateMemoryNotes.done': 'Notes de mémoire mises à jour',
+ 'conversations.tools.runGit.active': 'Exécution de git',
+ 'conversations.tools.runGit.done': 'git exécuté',
+ 'conversations.tools.readChanges.active': 'Lecture des modifications',
+ 'conversations.tools.readChanges.done': 'Modifications lues',
+ 'conversations.tools.runLinter.active': 'Exécution du linter',
+ 'conversations.tools.runLinter.done': 'Linter exécuté',
+ 'conversations.tools.runTests.active': 'Exécution des tests',
+ 'conversations.tools.runTests.done': 'Tests exécutés',
+ 'conversations.tools.analyzeCode.active': 'Analyse du code',
+ 'conversations.tools.analyzeCode.done': 'Code analysé',
+ 'conversations.tools.insertRecord.active': "Insertion de l'enregistrement",
+ 'conversations.tools.insertRecord.done': 'Enregistrement inséré',
+ 'conversations.tools.runCommand.active': 'Exécution de la commande',
+ 'conversations.tools.runCommand.done': 'Commande exécutée',
+ 'conversations.tools.runCode.active': 'Exécution du code',
+ 'conversations.tools.runCode.done': 'Code exécuté',
+ 'conversations.tools.runPackageManager.active': 'Exécution de npm',
+ 'conversations.tools.runPackageManager.done': 'npm exécuté',
+ 'conversations.tools.checkInstalledTools.active': 'Vérification des outils installés',
+ 'conversations.tools.checkInstalledTools.done': 'Outils installés vérifiés',
+ 'conversations.tools.installTool.active': "Installation de l'outil",
+ 'conversations.tools.installTool.done': 'Outil installé',
+ 'conversations.tools.checkTime.active': "Vérification de l'heure",
+ 'conversations.tools.checkTime.done': 'Heure vérifiée',
+ 'conversations.tools.resolveDate.active': 'Calcul de la date',
+ 'conversations.tools.resolveDate.done': 'Date calculée',
+ 'conversations.tools.retrieveOutput.active': 'Récupération de la sortie complète',
+ 'conversations.tools.retrieveOutput.done': 'Sortie complète récupérée',
+ 'conversations.tools.reviewWorkspace.active': "Examen de l'espace de travail",
+ 'conversations.tools.reviewWorkspace.done': 'Espace de travail examiné',
+ 'conversations.tools.configureProxy.active': 'Configuration du proxy',
+ 'conversations.tools.configureProxy.done': 'Proxy configuré',
+ 'conversations.tools.checkUpdates.active': 'Recherche de mises à jour',
+ 'conversations.tools.checkUpdates.done': 'Mises à jour vérifiées',
+ 'conversations.tools.installUpdate.active': 'Installation de la mise à jour',
+ 'conversations.tools.installUpdate.done': 'Mise à jour installée',
+ 'conversations.tools.sendNotification.active': 'Envoi de la notification',
+ 'conversations.tools.sendNotification.done': 'Notification envoyée',
+ 'conversations.tools.reviewToolUsage.active': "Examen de l'utilisation des outils",
+ 'conversations.tools.reviewToolUsage.done': 'Utilisation des outils examinée',
+ 'conversations.tools.typeKeys.active': 'Saisie en cours',
+ 'conversations.tools.typeKeys.done': 'Texte saisi',
+ 'conversations.tools.click.active': 'Clic en cours',
+ 'conversations.tools.click.done': 'Clic effectué',
+ 'conversations.tools.searchWeb.active': 'Recherche sur le web',
+ 'conversations.tools.searchWeb.done': 'Recherche web effectuée',
+ 'conversations.tools.searchNews.active': "Recherche d'actualités",
+ 'conversations.tools.searchNews.done': 'Actualités recherchées',
+ 'conversations.tools.searchImages.active': "Recherche d'images",
+ 'conversations.tools.searchImages.done': 'Images recherchées',
+ 'conversations.tools.searchVideos.active': 'Recherche de vidéos',
+ 'conversations.tools.searchVideos.done': 'Vidéos recherchées',
+ 'conversations.tools.findSimilarPages.active': 'Recherche de pages similaires',
+ 'conversations.tools.findSimilarPages.done': 'Pages similaires trouvées',
+ 'conversations.tools.readPages.active': 'Lecture des pages',
+ 'conversations.tools.readPages.done': 'Pages lues',
+ 'conversations.tools.readWebpage.active': 'Lecture de la page web',
+ 'conversations.tools.readWebpage.done': 'Page web lue',
+ 'conversations.tools.research.active': 'Recherche approfondie',
+ 'conversations.tools.research.done': 'Recherche approfondie terminée',
+ 'conversations.tools.enrichData.active': 'Enrichissement des données',
+ 'conversations.tools.enrichData.done': 'Données enrichies',
+ 'conversations.tools.buildDataset.active': 'Création du jeu de données',
+ 'conversations.tools.buildDataset.done': 'Jeu de données créé',
+ 'conversations.tools.askTheWeb.active': 'Interrogation du web',
+ 'conversations.tools.askTheWeb.done': 'Web interrogé',
+ 'conversations.tools.browseForYou.active': 'Navigation pour vous',
+ 'conversations.tools.browseForYou.done': 'Navigation effectuée pour vous',
+ 'conversations.tools.callApi.active': "Appel de l'API",
+ 'conversations.tools.callApi.done': 'API appelée',
+ 'conversations.tools.downloadFile.active': 'Téléchargement du fichier',
+ 'conversations.tools.downloadFile.done': 'Fichier téléchargé',
+ 'conversations.tools.makePaidRequest.active': "Envoi d'une requête payante",
+ 'conversations.tools.makePaidRequest.done': 'Requête payante envoyée',
+ 'conversations.tools.searchDocs.active': 'Recherche dans la documentation',
+ 'conversations.tools.searchDocs.done': 'Documentation parcourue',
+ 'conversations.tools.readDocs.active': 'Lecture de la documentation',
+ 'conversations.tools.readDocs.done': 'Documentation lue',
+ 'conversations.tools.useBrowser.active': 'Utilisation du navigateur',
+ 'conversations.tools.useBrowser.done': 'Navigateur utilisé',
+ 'conversations.tools.openPage.active': 'Ouverture de la page',
+ 'conversations.tools.openPage.done': 'Page ouverte',
+ 'conversations.tools.navigate.active': 'Navigation en cours',
+ 'conversations.tools.navigate.done': 'Navigation effectuée',
+ 'conversations.tools.takeScreenshot.active': "Capture d'écran en cours",
+ 'conversations.tools.takeScreenshot.done': "Capture d'écran effectuée",
+ 'conversations.tools.scrollPage.active': 'Défilement en cours',
+ 'conversations.tools.scrollPage.done': 'Défilement effectué',
+ 'conversations.tools.readPage.active': 'Lecture de la page',
+ 'conversations.tools.readPage.done': 'Page lue',
+ 'conversations.tools.analyzeImage.active': "Analyse de l'image",
+ 'conversations.tools.analyzeImage.done': 'Image analysée',
+ 'conversations.tools.generateImage.active': "Génération de l'image",
+ 'conversations.tools.generateImage.done': 'Image générée',
+ 'conversations.tools.generateVideo.active': 'Génération de la vidéo',
+ 'conversations.tools.generateVideo.done': 'Vidéo générée',
+ 'conversations.tools.checkMediaModels.active': 'Vérification des modèles multimédias',
+ 'conversations.tools.checkMediaModels.done': 'Modèles multimédias vérifiés',
+ 'conversations.tools.createDocument.active': 'Création du document',
+ 'conversations.tools.createDocument.done': 'Document créé',
+ 'conversations.tools.createPresentation.active': 'Création de la présentation',
+ 'conversations.tools.createPresentation.done': 'Présentation créée',
+ 'conversations.tools.generatePodcast.active': 'Génération du podcast',
+ 'conversations.tools.generatePodcast.done': 'Podcast généré',
+ 'conversations.tools.emailPodcast.active': 'Envoi du podcast par e-mail',
+ 'conversations.tools.emailPodcast.done': 'Podcast envoyé par e-mail',
+ 'conversations.tools.createAndEmailPodcast.active': 'Création et envoi du podcast par e-mail',
+ 'conversations.tools.createAndEmailPodcast.done': 'Podcast créé et envoyé par e-mail',
+ 'conversations.tools.recallMemories.active': 'Rappel des souvenirs',
+ 'conversations.tools.recallMemories.done': 'Souvenirs rappelés',
+ 'conversations.tools.saveToMemory.active': 'Enregistrement en mémoire',
+ 'conversations.tools.saveToMemory.done': 'Enregistré en mémoire',
+ 'conversations.tools.forgetMemory.active': 'Oubli du souvenir',
+ 'conversations.tools.forgetMemory.done': 'Souvenir oublié',
+ 'conversations.tools.searchMemory.active': 'Recherche dans la mémoire',
+ 'conversations.tools.searchMemory.done': 'Mémoire parcourue',
+ 'conversations.tools.inspectMemory.active': 'Inspection de la mémoire',
+ 'conversations.tools.inspectMemory.done': 'Mémoire inspectée',
+ 'conversations.tools.exploreMemory.active': 'Exploration de la mémoire',
+ 'conversations.tools.exploreMemory.done': 'Mémoire explorée',
+ 'conversations.tools.saveDocumentToMemory.active': 'Enregistrement du document en mémoire',
+ 'conversations.tools.saveDocumentToMemory.done': 'Document enregistré en mémoire',
+ 'conversations.tools.updateGoals.active': 'Mise à jour des objectifs',
+ 'conversations.tools.updateGoals.done': 'Objectifs mis à jour',
+ 'conversations.tools.reviewGoals.active': 'Examen des objectifs',
+ 'conversations.tools.reviewGoals.done': 'Objectifs examinés',
+ 'conversations.tools.savePreference.active': 'Enregistrement de la préférence',
+ 'conversations.tools.savePreference.done': 'Préférence enregistrée',
+ 'conversations.tools.reviewLearnings.active': 'Examen de mes apprentissages',
+ 'conversations.tools.reviewLearnings.done': 'Apprentissages examinés',
+ 'conversations.tools.updateLearnings.active': 'Mise à jour de mes apprentissages',
+ 'conversations.tools.updateLearnings.done': 'Apprentissages mis à jour',
+ 'conversations.tools.delegateTask.active': 'Délégation de la tâche',
+ 'conversations.tools.delegateTask.done': 'Tâche déléguée',
+ 'conversations.tools.runAgentsInParallel.active': "Exécution d'agents en parallèle",
+ 'conversations.tools.runAgentsInParallel.done': 'Agents exécutés en parallèle',
+ 'conversations.tools.messageAgent.active': "Envoi d'un message à l'agent",
+ 'conversations.tools.messageAgent.done': "Message envoyé à l'agent",
+ 'conversations.tools.waitForAgent.active': "Attente de l'agent",
+ 'conversations.tools.waitForAgent.done': "Attente de l'agent terminée",
+ 'conversations.tools.wait.active': 'Attente en cours',
+ 'conversations.tools.wait.done': 'Attente terminée',
+ 'conversations.tools.closeAgent.active': "Fermeture de l'agent",
+ 'conversations.tools.closeAgent.done': 'Agent fermé',
+ 'conversations.tools.checkAgents.active': 'Vérification des agents',
+ 'conversations.tools.checkAgents.done': 'Agents vérifiés',
+ 'conversations.tools.askQuestion.active': 'Question en cours pour vous',
+ 'conversations.tools.askQuestion.done': 'Question posée',
+ 'conversations.tools.prepareContext.active': 'Préparation du contexte',
+ 'conversations.tools.prepareContext.done': 'Contexte préparé',
+ 'conversations.tools.extractDetails.active': 'Extraction des détails',
+ 'conversations.tools.extractDetails.done': 'Détails extraits',
+ 'conversations.tools.planNextSteps.active': 'Planification des prochaines étapes',
+ 'conversations.tools.planNextSteps.done': 'Prochaines étapes planifiées',
+ 'conversations.tools.reviewWork.active': 'Relecture du travail',
+ 'conversations.tools.reviewWork.done': 'Travail relu',
+ 'conversations.tools.scoutContext.active': 'Repérage du contexte',
+ 'conversations.tools.scoutContext.done': 'Contexte repéré',
+ 'conversations.tools.useTools.active': 'Utilisation des outils',
+ 'conversations.tools.useTools.done': 'Outils utilisés',
+ 'conversations.tools.checkConnectedApp.active': 'Vérification de votre app connectée',
+ 'conversations.tools.checkConnectedApp.done': 'App connectée vérifiée',
+ 'conversations.tools.updateTodos.active': 'Mise à jour de la liste de tâches',
+ 'conversations.tools.updateTodos.done': 'Liste de tâches mise à jour',
+ 'conversations.tools.requestPlanReview.active': 'Demande de relecture du plan',
+ 'conversations.tools.requestPlanReview.done': 'Relecture du plan demandée',
+ 'conversations.tools.finishPlan.active': 'Finalisation du plan',
+ 'conversations.tools.finishPlan.done': 'Plan finalisé',
+ 'conversations.tools.setGoal.active': "Définition de l'objectif",
+ 'conversations.tools.setGoal.done': 'Objectif défini',
+ 'conversations.tools.checkGoal.active': "Vérification de l'objectif",
+ 'conversations.tools.checkGoal.done': 'Objectif vérifié',
+ 'conversations.tools.completeGoal.active': "Réalisation de l'objectif",
+ 'conversations.tools.completeGoal.done': 'Objectif atteint',
+ 'conversations.tools.scheduleTask.active': 'Planification de la tâche',
+ 'conversations.tools.scheduleTask.done': 'Tâche planifiée',
+ 'conversations.tools.checkSchedules.active': 'Vérification des planifications',
+ 'conversations.tools.checkSchedules.done': 'Planifications vérifiées',
+ 'conversations.tools.updateSchedule.active': 'Mise à jour de la tâche planifiée',
+ 'conversations.tools.updateSchedule.done': 'Tâche planifiée mise à jour',
+ 'conversations.tools.removeSchedule.active': 'Suppression de la tâche planifiée',
+ 'conversations.tools.removeSchedule.done': 'Tâche planifiée supprimée',
+ 'conversations.tools.runScheduledTask.active': 'Exécution de la tâche planifiée',
+ 'conversations.tools.runScheduledTask.done': 'Tâche planifiée exécutée',
+ 'conversations.tools.checkRunHistory.active': "Vérification de l'historique d'exécution",
+ 'conversations.tools.checkRunHistory.done': "Historique d'exécution vérifié",
+ 'conversations.tools.useApp.active': 'Utilisation de {app}',
+ 'conversations.tools.useApp.done': '{app} utilisé',
+ 'conversations.tools.checkAvailableApps.active': 'Vérification des apps disponibles',
+ 'conversations.tools.checkAvailableApps.done': 'Apps disponibles vérifiées',
+ 'conversations.tools.checkConnections.active': 'Vérification de vos connexions',
+ 'conversations.tools.checkConnections.done': 'Connexions vérifiées',
+ 'conversations.tools.connectApp.active': "Connexion de l'app",
+ 'conversations.tools.connectApp.done': 'App connectée',
+ 'conversations.tools.authorizeApp.active': "Autorisation de l'app",
+ 'conversations.tools.authorizeApp.done': 'App autorisée',
+ 'conversations.tools.findAppActions.active': "Recherche d'actions de l'app",
+ 'conversations.tools.findAppActions.done': "Actions de l'app trouvées",
+ 'conversations.tools.runAppAction.active': "Exécution de l'action de l'app",
+ 'conversations.tools.runAppAction.done': "Action de l'app exécutée",
+ 'conversations.tools.findTools.active': "Recherche d'outils",
+ 'conversations.tools.findTools.done': 'Outils trouvés',
+ 'conversations.tools.useTool.active': 'Utilisation de {tool}',
+ 'conversations.tools.useTool.done': '{tool} utilisé',
+ 'conversations.tools.unsubscribe.active': 'Désabonnement en cours',
+ 'conversations.tools.unsubscribe.done': 'Désabonnement effectué',
+ 'conversations.tools.searchPlaces.active': 'Recherche de lieux',
+ 'conversations.tools.searchPlaces.done': 'Lieux recherchés',
+ 'conversations.tools.lookUpPlace.active': 'Recherche du lieu',
+ 'conversations.tools.lookUpPlace.done': 'Lieu trouvé',
+ 'conversations.tools.checkMarkets.active': 'Consultation des marchés',
+ 'conversations.tools.checkMarkets.done': 'Marchés consultés',
+ 'conversations.tools.placeCall.active': 'Appel en cours',
+ 'conversations.tools.placeCall.done': 'Appel passé',
+ 'conversations.tools.checkTaskSources.active': 'Vérification des sources de tâches',
+ 'conversations.tools.checkTaskSources.done': 'Sources de tâches vérifiées',
+ 'conversations.tools.updateTaskSources.active': 'Mise à jour des sources de tâches',
+ 'conversations.tools.updateTaskSources.done': 'Sources de tâches mises à jour',
+ 'conversations.tools.fetchTasks.active': 'Récupération des tâches',
+ 'conversations.tools.fetchTasks.done': 'Tâches récupérées',
+ 'conversations.tools.checkMcpServers.active': 'Vérification des serveurs MCP',
+ 'conversations.tools.checkMcpServers.done': 'Serveurs MCP vérifiés',
+ 'conversations.tools.checkMcpTools.active': 'Vérification des outils MCP',
+ 'conversations.tools.checkMcpTools.done': 'Outils MCP vérifiés',
+ 'conversations.tools.callMcpTool.active': 'Appel de {tool}',
+ 'conversations.tools.callMcpTool.done': '{tool} appelé',
+ 'conversations.tools.searchMcpServers.active': 'Recherche de serveurs MCP',
+ 'conversations.tools.searchMcpServers.done': 'Serveurs MCP recherchés',
+ 'conversations.tools.connectMcpServer.active': 'Connexion du serveur MCP',
+ 'conversations.tools.connectMcpServer.done': 'Serveur MCP connecté',
+ 'conversations.tools.disconnectMcpServer.active': 'Déconnexion du serveur MCP',
+ 'conversations.tools.disconnectMcpServer.done': 'Serveur MCP déconnecté',
+ 'conversations.tools.removeMcpServer.active': 'Suppression du serveur MCP',
+ 'conversations.tools.removeMcpServer.done': 'Serveur MCP supprimé',
+ 'conversations.tools.uploadFile.active': 'Envoi du fichier',
+ 'conversations.tools.uploadFile.done': 'Fichier envoyé',
+ 'conversations.tools.listStoredFiles.active': 'Liste des fichiers stockés',
+ 'conversations.tools.listStoredFiles.done': 'Fichiers stockés listés',
+ 'conversations.tools.createShareLink.active': 'Création du lien de partage',
+ 'conversations.tools.createShareLink.done': 'Lien de partage créé',
+ 'conversations.tools.deleteFile.active': 'Suppression du fichier',
+ 'conversations.tools.deleteFile.done': 'Fichier supprimé',
+ 'conversations.tools.updateFileAccess.active': "Mise à jour de l'accès au fichier",
+ 'conversations.tools.updateFileAccess.done': 'Accès au fichier mis à jour',
+ 'conversations.tools.deploySite.active': 'Déploiement du site',
+ 'conversations.tools.deploySite.done': 'Site déployé',
+ 'conversations.tools.checkHosting.active': "Vérification de l'hébergement",
+ 'conversations.tools.checkHosting.done': 'Hébergement vérifié',
+ 'conversations.tools.updateHosting.active': "Mise à jour de l'hébergement",
+ 'conversations.tools.updateHosting.done': 'Hébergement mis à jour',
+ 'conversations.tools.rollBackDeployment.active': 'Annulation du déploiement',
+ 'conversations.tools.rollBackDeployment.done': 'Déploiement annulé',
+ 'conversations.tools.checkWallet.active': 'Vérification du portefeuille',
+ 'conversations.tools.checkWallet.done': 'Portefeuille vérifié',
+ 'conversations.tools.prepareTransfer.active': 'Préparation du transfert',
+ 'conversations.tools.prepareTransfer.done': 'Transfert préparé',
+ 'conversations.tools.checkTransaction.active': 'Vérification de la transaction',
+ 'conversations.tools.checkTransaction.done': 'Transaction vérifiée',
+ 'conversations.tools.getSwapQuote.active': "Obtention du devis d'échange",
+ 'conversations.tools.getSwapQuote.done': "Devis d'échange obtenu",
+ 'conversations.tools.swapTokens.active': 'Échange de jetons',
+ 'conversations.tools.swapTokens.done': 'Jetons échangés',
+ 'conversations.tools.getBridgeQuote.active': 'Obtention du devis de pont',
+ 'conversations.tools.getBridgeQuote.done': 'Devis de pont obtenu',
+ 'conversations.tools.bridgeTokens.active': 'Transfert de jetons par pont',
+ 'conversations.tools.bridgeTokens.done': 'Jetons transférés par pont',
+ 'conversations.tools.callDapp.active': "Appel du contrat de l'app",
+ 'conversations.tools.callDapp.done': "Contrat de l'app appelé",
+ 'conversations.tools.useSkill.active': 'Utilisation de la compétence',
+ 'conversations.tools.useSkill.done': 'Compétence utilisée',
+ 'conversations.tools.searchSkills.active': 'Recherche de compétences',
+ 'conversations.tools.searchSkills.done': 'Compétences recherchées',
+ 'conversations.tools.checkSkills.active': 'Vérification des compétences',
+ 'conversations.tools.checkSkills.done': 'Compétences vérifiées',
+ 'conversations.tools.installSkill.active': 'Installation de la compétence',
+ 'conversations.tools.installSkill.done': 'Compétence installée',
+ 'conversations.tools.removeSkill.active': 'Suppression de la compétence',
+ 'conversations.tools.removeSkill.done': 'Compétence supprimée',
+ 'conversations.tools.createSkill.active': 'Création de la compétence',
+ 'conversations.tools.createSkill.done': 'Compétence créée',
+ 'conversations.tools.runWorkflow.active': 'Exécution du workflow',
+ 'conversations.tools.runWorkflow.done': 'Workflow exécuté',
+ 'conversations.tools.waitForWorkflow.active': 'Attente du workflow',
+ 'conversations.tools.waitForWorkflow.done': 'Attente du workflow terminée',
+ 'conversations.tools.designWorkflow.active': 'Conception du workflow',
+ 'conversations.tools.designWorkflow.done': 'Workflow conçu',
+ 'conversations.tools.saveWorkflow.active': 'Enregistrement du workflow',
+ 'conversations.tools.saveWorkflow.done': 'Workflow enregistré',
+ 'conversations.tools.validateWorkflow.active': 'Validation du workflow',
+ 'conversations.tools.validateWorkflow.done': 'Workflow validé',
+ 'conversations.tools.testWorkflow.active': 'Test du workflow',
+ 'conversations.tools.testWorkflow.done': 'Workflow testé',
+ 'conversations.tools.checkWorkflows.active': 'Vérification des workflows',
+ 'conversations.tools.checkWorkflows.done': 'Workflows vérifiés',
+ 'conversations.tools.cancelWorkflow.active': "Annulation de l'exécution du workflow",
+ 'conversations.tools.cancelWorkflow.done': 'Exécution du workflow annulée',
+ 'conversations.tools.suggestWorkflows.active': 'Suggestion de workflows',
+ 'conversations.tools.suggestWorkflows.done': 'Workflows suggérés',
+ 'conversations.tools.checkSettings.active': 'Vérification des paramètres',
+ 'conversations.tools.checkSettings.done': 'Paramètres vérifiés',
+ 'conversations.tools.checkSecurity.active': 'Vérification de la sécurité',
+ 'conversations.tools.checkSecurity.done': 'Sécurité vérifiée',
+ 'conversations.tools.runDiagnostics.active': 'Exécution des diagnostics',
+ 'conversations.tools.runDiagnostics.done': 'Diagnostics exécutés',
+ 'conversations.tools.checkUsageCosts.active': "Vérification des coûts d'utilisation",
+ 'conversations.tools.checkUsageCosts.done': "Coûts d'utilisation vérifiés",
+ 'conversations.tools.manageService.active': 'Gestion du service en arrière-plan',
+ 'conversations.tools.manageService.done': 'Service en arrière-plan géré',
+ 'conversations.tools.readPersona.active': 'Lecture du persona',
+ 'conversations.tools.readPersona.done': 'Persona lu',
+ 'conversations.tools.updatePersona.active': 'Mise à jour du persona',
+ 'conversations.tools.updatePersona.done': 'Persona mis à jour',
+ 'conversations.tools.setUpWorkspace.active': "Configuration de l'espace de travail",
+ 'conversations.tools.setUpWorkspace.done': 'Espace de travail configuré',
+ 'conversations.tools.checkArtifacts.active': 'Vérification des artefacts',
+ 'conversations.tools.checkArtifacts.done': 'Artefacts vérifiés',
+ 'conversations.tools.deleteArtifact.active': "Suppression de l'artefact",
+ 'conversations.tools.deleteArtifact.done': 'Artefact supprimé',
'conversations.subagent.noOutput': 'Aucune sortie renvoyée',
'conversations.subagent.close': 'Fermer',
'conversations.subagent.cancel': 'Annuler la tâche',
diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts
index 695dee834bf..89e04762628 100644
--- a/app/src/lib/i18n/hi.ts
+++ b/app/src/lib/i18n/hi.ts
@@ -3317,6 +3317,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'अभी तक कोई आउटपुट नहीं',
'conversations.subagent.input': 'इनपुट',
'conversations.subagent.output': 'आउटपुट',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} चरण',
+ 'conversations.tools.steps.other': '{count} चरण',
+ 'conversations.tools.working': 'काम जारी है',
+ 'conversations.tools.noOutput': 'कोई आउटपुट नहीं',
+ 'conversations.tools.delegatedTo': '{agent} को सौंपा गया',
+ 'conversations.tools.openInBrowser': 'ब्राउज़र में खोलें',
+ 'conversations.tools.status.running': 'चल रहा है',
+ 'conversations.tools.status.done': 'पूरा',
+ 'conversations.tools.status.failed': 'विफल',
+ 'conversations.tools.status.cancelled': 'रद्द',
+ 'conversations.tools.status.awaiting': 'इनपुट की प्रतीक्षा',
+ 'conversations.tools.search.searching': 'खोज रहा है',
+ 'conversations.tools.search.none': 'कोई परिणाम नहीं',
+ 'conversations.tools.search.found.one': '{count} परिणाम मिला',
+ 'conversations.tools.search.found.other': '{count} परिणाम मिले',
+ 'conversations.tools.search.via': '{provider} के ज़रिए',
+ 'conversations.tools.readFile.active': 'फ़ाइल पढ़ रहा है',
+ 'conversations.tools.readFile.done': 'फ़ाइल पढ़ी',
+ 'conversations.tools.writeFile.active': 'फ़ाइल लिख रहा है',
+ 'conversations.tools.writeFile.done': 'फ़ाइल लिखी',
+ 'conversations.tools.editFile.active': 'फ़ाइल संपादित कर रहा है',
+ 'conversations.tools.editFile.done': 'फ़ाइल संपादित की',
+ 'conversations.tools.applyEdits.active': 'बदलाव लागू कर रहा है',
+ 'conversations.tools.applyEdits.done': 'बदलाव लागू किए',
+ 'conversations.tools.searchCode.active': 'कोड खोज रहा है',
+ 'conversations.tools.searchCode.done': 'कोड खोजा',
+ 'conversations.tools.findFiles.active': 'फ़ाइलें ढूँढ रहा है',
+ 'conversations.tools.findFiles.done': 'फ़ाइलें ढूँढीं',
+ 'conversations.tools.listFolder.active': 'फ़ोल्डर की सूची बना रहा है',
+ 'conversations.tools.listFolder.done': 'फ़ोल्डर की सूची बनाई',
+ 'conversations.tools.exportCsv.active': 'CSV निर्यात कर रहा है',
+ 'conversations.tools.exportCsv.done': 'CSV निर्यात किया',
+ 'conversations.tools.updateMemoryNotes.active': 'मेमोरी नोट्स अपडेट कर रहा है',
+ 'conversations.tools.updateMemoryNotes.done': 'मेमोरी नोट्स अपडेट किए',
+ 'conversations.tools.runGit.active': 'git चला रहा है',
+ 'conversations.tools.runGit.done': 'git चलाया',
+ 'conversations.tools.readChanges.active': 'बदलाव पढ़ रहा है',
+ 'conversations.tools.readChanges.done': 'बदलाव पढ़े',
+ 'conversations.tools.runLinter.active': 'लिंटर चला रहा है',
+ 'conversations.tools.runLinter.done': 'लिंटर चलाया',
+ 'conversations.tools.runTests.active': 'टेस्ट चला रहा है',
+ 'conversations.tools.runTests.done': 'टेस्ट चलाए',
+ 'conversations.tools.analyzeCode.active': 'कोड का विश्लेषण कर रहा है',
+ 'conversations.tools.analyzeCode.done': 'कोड का विश्लेषण किया',
+ 'conversations.tools.insertRecord.active': 'रिकॉर्ड जोड़ रहा है',
+ 'conversations.tools.insertRecord.done': 'रिकॉर्ड जोड़ा',
+ 'conversations.tools.runCommand.active': 'कमांड चला रहा है',
+ 'conversations.tools.runCommand.done': 'कमांड चलाई',
+ 'conversations.tools.runCode.active': 'कोड चला रहा है',
+ 'conversations.tools.runCode.done': 'कोड चलाया',
+ 'conversations.tools.runPackageManager.active': 'npm चला रहा है',
+ 'conversations.tools.runPackageManager.done': 'npm चलाया',
+ 'conversations.tools.checkInstalledTools.active': 'इंस्टॉल किए गए टूल जाँच रहा है',
+ 'conversations.tools.checkInstalledTools.done': 'इंस्टॉल किए गए टूल जाँचे',
+ 'conversations.tools.installTool.active': 'टूल इंस्टॉल कर रहा है',
+ 'conversations.tools.installTool.done': 'टूल इंस्टॉल किया',
+ 'conversations.tools.checkTime.active': 'समय देख रहा है',
+ 'conversations.tools.checkTime.done': 'समय देखा',
+ 'conversations.tools.resolveDate.active': 'तारीख़ निकाल रहा है',
+ 'conversations.tools.resolveDate.done': 'तारीख़ निकाली',
+ 'conversations.tools.retrieveOutput.active': 'पूरा आउटपुट ला रहा है',
+ 'conversations.tools.retrieveOutput.done': 'पूरा आउटपुट लाया',
+ 'conversations.tools.reviewWorkspace.active': 'वर्कस्पेस की समीक्षा कर रहा है',
+ 'conversations.tools.reviewWorkspace.done': 'वर्कस्पेस की समीक्षा की',
+ 'conversations.tools.configureProxy.active': 'प्रॉक्सी कॉन्फ़िगर कर रहा है',
+ 'conversations.tools.configureProxy.done': 'प्रॉक्सी कॉन्फ़िगर की',
+ 'conversations.tools.checkUpdates.active': 'अपडेट जाँच रहा है',
+ 'conversations.tools.checkUpdates.done': 'अपडेट जाँचे',
+ 'conversations.tools.installUpdate.active': 'अपडेट इंस्टॉल कर रहा है',
+ 'conversations.tools.installUpdate.done': 'अपडेट इंस्टॉल किया',
+ 'conversations.tools.sendNotification.active': 'सूचना भेज रहा है',
+ 'conversations.tools.sendNotification.done': 'सूचना भेजी',
+ 'conversations.tools.reviewToolUsage.active': 'टूल उपयोग की समीक्षा कर रहा है',
+ 'conversations.tools.reviewToolUsage.done': 'टूल उपयोग की समीक्षा की',
+ 'conversations.tools.typeKeys.active': 'टाइप कर रहा है',
+ 'conversations.tools.typeKeys.done': 'टाइप किया',
+ 'conversations.tools.click.active': 'क्लिक कर रहा है',
+ 'conversations.tools.click.done': 'क्लिक किया',
+ 'conversations.tools.searchWeb.active': 'वेब पर खोज रहा है',
+ 'conversations.tools.searchWeb.done': 'वेब पर खोजा',
+ 'conversations.tools.searchNews.active': 'समाचार खोज रहा है',
+ 'conversations.tools.searchNews.done': 'समाचार खोजे',
+ 'conversations.tools.searchImages.active': 'चित्र खोज रहा है',
+ 'conversations.tools.searchImages.done': 'चित्र खोजे',
+ 'conversations.tools.searchVideos.active': 'वीडियो खोज रहा है',
+ 'conversations.tools.searchVideos.done': 'वीडियो खोजे',
+ 'conversations.tools.findSimilarPages.active': 'मिलते-जुलते पेज ढूँढ रहा है',
+ 'conversations.tools.findSimilarPages.done': 'मिलते-जुलते पेज ढूँढे',
+ 'conversations.tools.readPages.active': 'पेज पढ़ रहा है',
+ 'conversations.tools.readPages.done': 'पेज पढ़े',
+ 'conversations.tools.readWebpage.active': 'वेबपेज पढ़ रहा है',
+ 'conversations.tools.readWebpage.done': 'वेबपेज पढ़ा',
+ 'conversations.tools.research.active': 'शोध कर रहा है',
+ 'conversations.tools.research.done': 'शोध किया',
+ 'conversations.tools.enrichData.active': 'डेटा समृद्ध कर रहा है',
+ 'conversations.tools.enrichData.done': 'डेटा समृद्ध किया',
+ 'conversations.tools.buildDataset.active': 'डेटासेट बना रहा है',
+ 'conversations.tools.buildDataset.done': 'डेटासेट बनाया',
+ 'conversations.tools.askTheWeb.active': 'वेब से पूछ रहा है',
+ 'conversations.tools.askTheWeb.done': 'वेब से पूछा',
+ 'conversations.tools.browseForYou.active': 'आपके लिए ब्राउज़ कर रहा है',
+ 'conversations.tools.browseForYou.done': 'आपके लिए ब्राउज़ किया',
+ 'conversations.tools.callApi.active': 'API कॉल कर रहा है',
+ 'conversations.tools.callApi.done': 'API कॉल किया',
+ 'conversations.tools.downloadFile.active': 'फ़ाइल डाउनलोड कर रहा है',
+ 'conversations.tools.downloadFile.done': 'फ़ाइल डाउनलोड की',
+ 'conversations.tools.makePaidRequest.active': 'सशुल्क अनुरोध भेज रहा है',
+ 'conversations.tools.makePaidRequest.done': 'सशुल्क अनुरोध भेजा',
+ 'conversations.tools.searchDocs.active': 'दस्तावेज़ खोज रहा है',
+ 'conversations.tools.searchDocs.done': 'दस्तावेज़ खोजे',
+ 'conversations.tools.readDocs.active': 'दस्तावेज़ पढ़ रहा है',
+ 'conversations.tools.readDocs.done': 'दस्तावेज़ पढ़े',
+ 'conversations.tools.useBrowser.active': 'ब्राउज़र का उपयोग कर रहा है',
+ 'conversations.tools.useBrowser.done': 'ब्राउज़र का उपयोग किया',
+ 'conversations.tools.openPage.active': 'पेज खोल रहा है',
+ 'conversations.tools.openPage.done': 'पेज खोला',
+ 'conversations.tools.navigate.active': 'नेविगेट कर रहा है',
+ 'conversations.tools.navigate.done': 'नेविगेट किया',
+ 'conversations.tools.takeScreenshot.active': 'स्क्रीनशॉट ले रहा है',
+ 'conversations.tools.takeScreenshot.done': 'स्क्रीनशॉट लिया',
+ 'conversations.tools.scrollPage.active': 'स्क्रॉल कर रहा है',
+ 'conversations.tools.scrollPage.done': 'स्क्रॉल किया',
+ 'conversations.tools.readPage.active': 'पेज पढ़ रहा है',
+ 'conversations.tools.readPage.done': 'पेज पढ़ा',
+ 'conversations.tools.analyzeImage.active': 'चित्र का विश्लेषण कर रहा है',
+ 'conversations.tools.analyzeImage.done': 'चित्र का विश्लेषण किया',
+ 'conversations.tools.generateImage.active': 'चित्र बना रहा है',
+ 'conversations.tools.generateImage.done': 'चित्र बनाया',
+ 'conversations.tools.generateVideo.active': 'वीडियो बना रहा है',
+ 'conversations.tools.generateVideo.done': 'वीडियो बनाया',
+ 'conversations.tools.checkMediaModels.active': 'मीडिया मॉडल जाँच रहा है',
+ 'conversations.tools.checkMediaModels.done': 'मीडिया मॉडल जाँचे',
+ 'conversations.tools.createDocument.active': 'दस्तावेज़ बना रहा है',
+ 'conversations.tools.createDocument.done': 'दस्तावेज़ बनाया',
+ 'conversations.tools.createPresentation.active': 'प्रेज़ेंटेशन बना रहा है',
+ 'conversations.tools.createPresentation.done': 'प्रेज़ेंटेशन बनाया',
+ 'conversations.tools.generatePodcast.active': 'पॉडकास्ट बना रहा है',
+ 'conversations.tools.generatePodcast.done': 'पॉडकास्ट बनाया',
+ 'conversations.tools.emailPodcast.active': 'पॉडकास्ट ईमेल कर रहा है',
+ 'conversations.tools.emailPodcast.done': 'पॉडकास्ट ईमेल किया',
+ 'conversations.tools.createAndEmailPodcast.active': 'पॉडकास्ट बनाकर ईमेल कर रहा है',
+ 'conversations.tools.createAndEmailPodcast.done': 'पॉडकास्ट बनाकर ईमेल किया',
+ 'conversations.tools.recallMemories.active': 'यादें खोज रहा है',
+ 'conversations.tools.recallMemories.done': 'यादें खोजीं',
+ 'conversations.tools.saveToMemory.active': 'मेमोरी में सहेज रहा है',
+ 'conversations.tools.saveToMemory.done': 'मेमोरी में सहेजा',
+ 'conversations.tools.forgetMemory.active': 'मेमोरी भुला रहा है',
+ 'conversations.tools.forgetMemory.done': 'मेमोरी भुलाई',
+ 'conversations.tools.searchMemory.active': 'मेमोरी में खोज रहा है',
+ 'conversations.tools.searchMemory.done': 'मेमोरी में खोजा',
+ 'conversations.tools.inspectMemory.active': 'मेमोरी की जाँच कर रहा है',
+ 'conversations.tools.inspectMemory.done': 'मेमोरी की जाँच की',
+ 'conversations.tools.exploreMemory.active': 'मेमोरी देख रहा है',
+ 'conversations.tools.exploreMemory.done': 'मेमोरी देखी',
+ 'conversations.tools.saveDocumentToMemory.active': 'दस्तावेज़ मेमोरी में सहेज रहा है',
+ 'conversations.tools.saveDocumentToMemory.done': 'दस्तावेज़ मेमोरी में सहेजा',
+ 'conversations.tools.updateGoals.active': 'लक्ष्य अपडेट कर रहा है',
+ 'conversations.tools.updateGoals.done': 'लक्ष्य अपडेट किए',
+ 'conversations.tools.reviewGoals.active': 'लक्ष्यों की समीक्षा कर रहा है',
+ 'conversations.tools.reviewGoals.done': 'लक्ष्यों की समीक्षा की',
+ 'conversations.tools.savePreference.active': 'पसंद सहेज रहा है',
+ 'conversations.tools.savePreference.done': 'पसंद सहेजी',
+ 'conversations.tools.reviewLearnings.active': 'सीखी बातों की समीक्षा कर रहा है',
+ 'conversations.tools.reviewLearnings.done': 'सीखी बातों की समीक्षा की',
+ 'conversations.tools.updateLearnings.active': 'सीखी बातें अपडेट कर रहा है',
+ 'conversations.tools.updateLearnings.done': 'सीखी बातें अपडेट कीं',
+ 'conversations.tools.delegateTask.active': 'कार्य सौंप रहा है',
+ 'conversations.tools.delegateTask.done': 'कार्य सौंपा',
+ 'conversations.tools.runAgentsInParallel.active': 'एजेंट समानांतर चला रहा है',
+ 'conversations.tools.runAgentsInParallel.done': 'एजेंट समानांतर चलाए',
+ 'conversations.tools.messageAgent.active': 'एजेंट को संदेश भेज रहा है',
+ 'conversations.tools.messageAgent.done': 'एजेंट को संदेश भेजा',
+ 'conversations.tools.waitForAgent.active': 'एजेंट की प्रतीक्षा कर रहा है',
+ 'conversations.tools.waitForAgent.done': 'एजेंट की प्रतीक्षा की',
+ 'conversations.tools.wait.active': 'प्रतीक्षा कर रहा है',
+ 'conversations.tools.wait.done': 'प्रतीक्षा की',
+ 'conversations.tools.closeAgent.active': 'एजेंट बंद कर रहा है',
+ 'conversations.tools.closeAgent.done': 'एजेंट बंद किया',
+ 'conversations.tools.checkAgents.active': 'एजेंट जाँच रहा है',
+ 'conversations.tools.checkAgents.done': 'एजेंट जाँचे',
+ 'conversations.tools.askQuestion.active': 'आपसे सवाल पूछ रहा है',
+ 'conversations.tools.askQuestion.done': 'आपसे सवाल पूछा',
+ 'conversations.tools.prepareContext.active': 'संदर्भ तैयार कर रहा है',
+ 'conversations.tools.prepareContext.done': 'संदर्भ तैयार किया',
+ 'conversations.tools.extractDetails.active': 'विवरण निकाल रहा है',
+ 'conversations.tools.extractDetails.done': 'विवरण निकाले',
+ 'conversations.tools.planNextSteps.active': 'अगले कदमों की योजना बना रहा है',
+ 'conversations.tools.planNextSteps.done': 'अगले कदमों की योजना बनाई',
+ 'conversations.tools.reviewWork.active': 'काम की समीक्षा कर रहा है',
+ 'conversations.tools.reviewWork.done': 'काम की समीक्षा की',
+ 'conversations.tools.scoutContext.active': 'संदर्भ टटोल रहा है',
+ 'conversations.tools.scoutContext.done': 'संदर्भ टटोला',
+ 'conversations.tools.useTools.active': 'टूल का उपयोग कर रहा है',
+ 'conversations.tools.useTools.done': 'टूल का उपयोग किया',
+ 'conversations.tools.checkConnectedApp.active': 'आपका कनेक्टेड ऐप जाँच रहा है',
+ 'conversations.tools.checkConnectedApp.done': 'आपका कनेक्टेड ऐप जाँचा',
+ 'conversations.tools.updateTodos.active': 'कार्य सूची अपडेट कर रहा है',
+ 'conversations.tools.updateTodos.done': 'कार्य सूची अपडेट की',
+ 'conversations.tools.requestPlanReview.active': 'योजना की समीक्षा का अनुरोध कर रहा है',
+ 'conversations.tools.requestPlanReview.done': 'योजना की समीक्षा का अनुरोध किया',
+ 'conversations.tools.finishPlan.active': 'योजना पूरी कर रहा है',
+ 'conversations.tools.finishPlan.done': 'योजना पूरी की',
+ 'conversations.tools.setGoal.active': 'लक्ष्य तय कर रहा है',
+ 'conversations.tools.setGoal.done': 'लक्ष्य तय किया',
+ 'conversations.tools.checkGoal.active': 'लक्ष्य जाँच रहा है',
+ 'conversations.tools.checkGoal.done': 'लक्ष्य जाँचा',
+ 'conversations.tools.completeGoal.active': 'लक्ष्य पूरा कर रहा है',
+ 'conversations.tools.completeGoal.done': 'लक्ष्य पूरा किया',
+ 'conversations.tools.scheduleTask.active': 'कार्य शेड्यूल कर रहा है',
+ 'conversations.tools.scheduleTask.done': 'कार्य शेड्यूल किया',
+ 'conversations.tools.checkSchedules.active': 'शेड्यूल जाँच रहा है',
+ 'conversations.tools.checkSchedules.done': 'शेड्यूल जाँचे',
+ 'conversations.tools.updateSchedule.active': 'शेड्यूल किया गया कार्य अपडेट कर रहा है',
+ 'conversations.tools.updateSchedule.done': 'शेड्यूल किया गया कार्य अपडेट किया',
+ 'conversations.tools.removeSchedule.active': 'शेड्यूल किया गया कार्य हटा रहा है',
+ 'conversations.tools.removeSchedule.done': 'शेड्यूल किया गया कार्य हटाया',
+ 'conversations.tools.runScheduledTask.active': 'शेड्यूल किया गया कार्य चला रहा है',
+ 'conversations.tools.runScheduledTask.done': 'शेड्यूल किया गया कार्य चलाया',
+ 'conversations.tools.checkRunHistory.active': 'रन इतिहास जाँच रहा है',
+ 'conversations.tools.checkRunHistory.done': 'रन इतिहास जाँचा',
+ 'conversations.tools.useApp.active': '{app} का उपयोग कर रहा है',
+ 'conversations.tools.useApp.done': '{app} का उपयोग किया',
+ 'conversations.tools.checkAvailableApps.active': 'उपलब्ध ऐप जाँच रहा है',
+ 'conversations.tools.checkAvailableApps.done': 'उपलब्ध ऐप जाँचे',
+ 'conversations.tools.checkConnections.active': 'आपके कनेक्शन जाँच रहा है',
+ 'conversations.tools.checkConnections.done': 'आपके कनेक्शन जाँचे',
+ 'conversations.tools.connectApp.active': 'ऐप कनेक्ट कर रहा है',
+ 'conversations.tools.connectApp.done': 'ऐप कनेक्ट किया',
+ 'conversations.tools.authorizeApp.active': 'ऐप को अधिकृत कर रहा है',
+ 'conversations.tools.authorizeApp.done': 'ऐप को अधिकृत किया',
+ 'conversations.tools.findAppActions.active': 'ऐप क्रियाएँ ढूँढ रहा है',
+ 'conversations.tools.findAppActions.done': 'ऐप क्रियाएँ ढूँढीं',
+ 'conversations.tools.runAppAction.active': 'ऐप क्रिया चला रहा है',
+ 'conversations.tools.runAppAction.done': 'ऐप क्रिया चलाई',
+ 'conversations.tools.findTools.active': 'टूल ढूँढ रहा है',
+ 'conversations.tools.findTools.done': 'टूल ढूँढे',
+ 'conversations.tools.useTool.active': '{tool} का उपयोग कर रहा है',
+ 'conversations.tools.useTool.done': '{tool} का उपयोग किया',
+ 'conversations.tools.unsubscribe.active': 'सदस्यता रद्द कर रहा है',
+ 'conversations.tools.unsubscribe.done': 'सदस्यता रद्द की',
+ 'conversations.tools.searchPlaces.active': 'स्थान खोज रहा है',
+ 'conversations.tools.searchPlaces.done': 'स्थान खोजे',
+ 'conversations.tools.lookUpPlace.active': 'स्थान की जानकारी ले रहा है',
+ 'conversations.tools.lookUpPlace.done': 'स्थान की जानकारी ली',
+ 'conversations.tools.checkMarkets.active': 'बाज़ार देख रहा है',
+ 'conversations.tools.checkMarkets.done': 'बाज़ार देखे',
+ 'conversations.tools.placeCall.active': 'कॉल कर रहा है',
+ 'conversations.tools.placeCall.done': 'कॉल किया',
+ 'conversations.tools.checkTaskSources.active': 'कार्य स्रोत जाँच रहा है',
+ 'conversations.tools.checkTaskSources.done': 'कार्य स्रोत जाँचे',
+ 'conversations.tools.updateTaskSources.active': 'कार्य स्रोत अपडेट कर रहा है',
+ 'conversations.tools.updateTaskSources.done': 'कार्य स्रोत अपडेट किए',
+ 'conversations.tools.fetchTasks.active': 'कार्य ला रहा है',
+ 'conversations.tools.fetchTasks.done': 'कार्य लाए',
+ 'conversations.tools.checkMcpServers.active': 'MCP सर्वर जाँच रहा है',
+ 'conversations.tools.checkMcpServers.done': 'MCP सर्वर जाँचे',
+ 'conversations.tools.checkMcpTools.active': 'MCP टूल जाँच रहा है',
+ 'conversations.tools.checkMcpTools.done': 'MCP टूल जाँचे',
+ 'conversations.tools.callMcpTool.active': '{tool} कॉल कर रहा है',
+ 'conversations.tools.callMcpTool.done': '{tool} कॉल किया',
+ 'conversations.tools.searchMcpServers.active': 'MCP सर्वर खोज रहा है',
+ 'conversations.tools.searchMcpServers.done': 'MCP सर्वर खोजे',
+ 'conversations.tools.connectMcpServer.active': 'MCP सर्वर कनेक्ट कर रहा है',
+ 'conversations.tools.connectMcpServer.done': 'MCP सर्वर कनेक्ट किया',
+ 'conversations.tools.disconnectMcpServer.active': 'MCP सर्वर डिस्कनेक्ट कर रहा है',
+ 'conversations.tools.disconnectMcpServer.done': 'MCP सर्वर डिस्कनेक्ट किया',
+ 'conversations.tools.removeMcpServer.active': 'MCP सर्वर हटा रहा है',
+ 'conversations.tools.removeMcpServer.done': 'MCP सर्वर हटाया',
+ 'conversations.tools.uploadFile.active': 'फ़ाइल अपलोड कर रहा है',
+ 'conversations.tools.uploadFile.done': 'फ़ाइल अपलोड की',
+ 'conversations.tools.listStoredFiles.active': 'सहेजी गई फ़ाइलों की सूची बना रहा है',
+ 'conversations.tools.listStoredFiles.done': 'सहेजी गई फ़ाइलों की सूची बनाई',
+ 'conversations.tools.createShareLink.active': 'शेयर लिंक बना रहा है',
+ 'conversations.tools.createShareLink.done': 'शेयर लिंक बनाया',
+ 'conversations.tools.deleteFile.active': 'फ़ाइल हटा रहा है',
+ 'conversations.tools.deleteFile.done': 'फ़ाइल हटाई',
+ 'conversations.tools.updateFileAccess.active': 'फ़ाइल एक्सेस अपडेट कर रहा है',
+ 'conversations.tools.updateFileAccess.done': 'फ़ाइल एक्सेस अपडेट किया',
+ 'conversations.tools.deploySite.active': 'साइट डिप्लॉय कर रहा है',
+ 'conversations.tools.deploySite.done': 'साइट डिप्लॉय की',
+ 'conversations.tools.checkHosting.active': 'होस्टिंग जाँच रहा है',
+ 'conversations.tools.checkHosting.done': 'होस्टिंग जाँची',
+ 'conversations.tools.updateHosting.active': 'होस्टिंग अपडेट कर रहा है',
+ 'conversations.tools.updateHosting.done': 'होस्टिंग अपडेट की',
+ 'conversations.tools.rollBackDeployment.active': 'डिप्लॉयमेंट वापस ले रहा है',
+ 'conversations.tools.rollBackDeployment.done': 'डिप्लॉयमेंट वापस लिया',
+ 'conversations.tools.checkWallet.active': 'वॉलेट जाँच रहा है',
+ 'conversations.tools.checkWallet.done': 'वॉलेट जाँचा',
+ 'conversations.tools.prepareTransfer.active': 'ट्रांसफ़र तैयार कर रहा है',
+ 'conversations.tools.prepareTransfer.done': 'ट्रांसफ़र तैयार किया',
+ 'conversations.tools.checkTransaction.active': 'लेन-देन जाँच रहा है',
+ 'conversations.tools.checkTransaction.done': 'लेन-देन जाँचा',
+ 'conversations.tools.getSwapQuote.active': 'स्वैप कोट ले रहा है',
+ 'conversations.tools.getSwapQuote.done': 'स्वैप कोट लिया',
+ 'conversations.tools.swapTokens.active': 'टोकन स्वैप कर रहा है',
+ 'conversations.tools.swapTokens.done': 'टोकन स्वैप किए',
+ 'conversations.tools.getBridgeQuote.active': 'ब्रिज कोट ले रहा है',
+ 'conversations.tools.getBridgeQuote.done': 'ब्रिज कोट लिया',
+ 'conversations.tools.bridgeTokens.active': 'टोकन ब्रिज कर रहा है',
+ 'conversations.tools.bridgeTokens.done': 'टोकन ब्रिज किए',
+ 'conversations.tools.callDapp.active': 'ऐप कॉन्ट्रैक्ट कॉल कर रहा है',
+ 'conversations.tools.callDapp.done': 'ऐप कॉन्ट्रैक्ट कॉल किया',
+ 'conversations.tools.useSkill.active': 'स्किल का उपयोग कर रहा है',
+ 'conversations.tools.useSkill.done': 'स्किल का उपयोग किया',
+ 'conversations.tools.searchSkills.active': 'स्किल खोज रहा है',
+ 'conversations.tools.searchSkills.done': 'स्किल खोजीं',
+ 'conversations.tools.checkSkills.active': 'स्किल जाँच रहा है',
+ 'conversations.tools.checkSkills.done': 'स्किल जाँचीं',
+ 'conversations.tools.installSkill.active': 'स्किल इंस्टॉल कर रहा है',
+ 'conversations.tools.installSkill.done': 'स्किल इंस्टॉल की',
+ 'conversations.tools.removeSkill.active': 'स्किल हटा रहा है',
+ 'conversations.tools.removeSkill.done': 'स्किल हटाई',
+ 'conversations.tools.createSkill.active': 'स्किल बना रहा है',
+ 'conversations.tools.createSkill.done': 'स्किल बनाई',
+ 'conversations.tools.runWorkflow.active': 'वर्कफ़्लो चला रहा है',
+ 'conversations.tools.runWorkflow.done': 'वर्कफ़्लो चलाया',
+ 'conversations.tools.waitForWorkflow.active': 'वर्कफ़्लो की प्रतीक्षा कर रहा है',
+ 'conversations.tools.waitForWorkflow.done': 'वर्कफ़्लो की प्रतीक्षा की',
+ 'conversations.tools.designWorkflow.active': 'वर्कफ़्लो डिज़ाइन कर रहा है',
+ 'conversations.tools.designWorkflow.done': 'वर्कफ़्लो डिज़ाइन किया',
+ 'conversations.tools.saveWorkflow.active': 'वर्कफ़्लो सहेज रहा है',
+ 'conversations.tools.saveWorkflow.done': 'वर्कफ़्लो सहेजा',
+ 'conversations.tools.validateWorkflow.active': 'वर्कफ़्लो सत्यापित कर रहा है',
+ 'conversations.tools.validateWorkflow.done': 'वर्कफ़्लो सत्यापित किया',
+ 'conversations.tools.testWorkflow.active': 'वर्कफ़्लो टेस्ट कर रहा है',
+ 'conversations.tools.testWorkflow.done': 'वर्कफ़्लो टेस्ट किया',
+ 'conversations.tools.checkWorkflows.active': 'वर्कफ़्लो जाँच रहा है',
+ 'conversations.tools.checkWorkflows.done': 'वर्कफ़्लो जाँचे',
+ 'conversations.tools.cancelWorkflow.active': 'वर्कफ़्लो रन रद्द कर रहा है',
+ 'conversations.tools.cancelWorkflow.done': 'वर्कफ़्लो रन रद्द किया',
+ 'conversations.tools.suggestWorkflows.active': 'वर्कफ़्लो सुझा रहा है',
+ 'conversations.tools.suggestWorkflows.done': 'वर्कफ़्लो सुझाए',
+ 'conversations.tools.checkSettings.active': 'सेटिंग्स जाँच रहा है',
+ 'conversations.tools.checkSettings.done': 'सेटिंग्स जाँचीं',
+ 'conversations.tools.checkSecurity.active': 'सुरक्षा जाँच रहा है',
+ 'conversations.tools.checkSecurity.done': 'सुरक्षा जाँची',
+ 'conversations.tools.runDiagnostics.active': 'डायग्नोस्टिक्स चला रहा है',
+ 'conversations.tools.runDiagnostics.done': 'डायग्नोस्टिक्स चलाए',
+ 'conversations.tools.checkUsageCosts.active': 'उपयोग लागत जाँच रहा है',
+ 'conversations.tools.checkUsageCosts.done': 'उपयोग लागत जाँची',
+ 'conversations.tools.manageService.active': 'बैकग्राउंड सेवा प्रबंधित कर रहा है',
+ 'conversations.tools.manageService.done': 'बैकग्राउंड सेवा प्रबंधित की',
+ 'conversations.tools.readPersona.active': 'पर्सोना पढ़ रहा है',
+ 'conversations.tools.readPersona.done': 'पर्सोना पढ़ा',
+ 'conversations.tools.updatePersona.active': 'पर्सोना अपडेट कर रहा है',
+ 'conversations.tools.updatePersona.done': 'पर्सोना अपडेट किया',
+ 'conversations.tools.setUpWorkspace.active': 'वर्कस्पेस सेट अप कर रहा है',
+ 'conversations.tools.setUpWorkspace.done': 'वर्कस्पेस सेट अप किया',
+ 'conversations.tools.checkArtifacts.active': 'आर्टिफैक्ट जाँच रहा है',
+ 'conversations.tools.checkArtifacts.done': 'आर्टिफैक्ट जाँचे',
+ 'conversations.tools.deleteArtifact.active': 'आर्टिफैक्ट हटा रहा है',
+ 'conversations.tools.deleteArtifact.done': 'आर्टिफैक्ट हटाया',
'conversations.subagent.noOutput': 'कोई आउटपुट नहीं मिला',
'conversations.subagent.close': 'बंद करें',
'conversations.subagent.cancel': 'कार्य रद्द करें',
diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts
index f660d28fce5..9f44b5b3279 100644
--- a/app/src/lib/i18n/id.ts
+++ b/app/src/lib/i18n/id.ts
@@ -3332,6 +3332,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'Belum ada keluaran',
'conversations.subagent.input': 'Masukan',
'conversations.subagent.output': 'Keluaran',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} langkah',
+ 'conversations.tools.steps.other': '{count} langkah',
+ 'conversations.tools.working': 'Sedang bekerja',
+ 'conversations.tools.noOutput': 'Tidak ada keluaran',
+ 'conversations.tools.delegatedTo': 'Didelegasikan ke {agent}',
+ 'conversations.tools.openInBrowser': 'Buka di peramban',
+ 'conversations.tools.status.running': 'berjalan',
+ 'conversations.tools.status.done': 'selesai',
+ 'conversations.tools.status.failed': 'gagal',
+ 'conversations.tools.status.cancelled': 'dibatalkan',
+ 'conversations.tools.status.awaiting': 'menunggu masukan',
+ 'conversations.tools.search.searching': 'Mencari',
+ 'conversations.tools.search.none': 'Tidak ada hasil',
+ 'conversations.tools.search.found.one': '{count} hasil ditemukan',
+ 'conversations.tools.search.found.other': '{count} hasil ditemukan',
+ 'conversations.tools.search.via': 'melalui {provider}',
+ 'conversations.tools.readFile.active': 'Membaca file',
+ 'conversations.tools.readFile.done': 'File dibaca',
+ 'conversations.tools.writeFile.active': 'Menulis file',
+ 'conversations.tools.writeFile.done': 'File ditulis',
+ 'conversations.tools.editFile.active': 'Mengedit file',
+ 'conversations.tools.editFile.done': 'File diedit',
+ 'conversations.tools.applyEdits.active': 'Menerapkan perubahan',
+ 'conversations.tools.applyEdits.done': 'Perubahan diterapkan',
+ 'conversations.tools.searchCode.active': 'Mencari kode',
+ 'conversations.tools.searchCode.done': 'Kode dicari',
+ 'conversations.tools.findFiles.active': 'Mencari file',
+ 'conversations.tools.findFiles.done': 'File ditemukan',
+ 'conversations.tools.listFolder.active': 'Menampilkan isi folder',
+ 'conversations.tools.listFolder.done': 'Isi folder ditampilkan',
+ 'conversations.tools.exportCsv.active': 'Mengekspor CSV',
+ 'conversations.tools.exportCsv.done': 'CSV diekspor',
+ 'conversations.tools.updateMemoryNotes.active': 'Memperbarui catatan memori',
+ 'conversations.tools.updateMemoryNotes.done': 'Catatan memori diperbarui',
+ 'conversations.tools.runGit.active': 'Menjalankan git',
+ 'conversations.tools.runGit.done': 'git dijalankan',
+ 'conversations.tools.readChanges.active': 'Membaca perubahan',
+ 'conversations.tools.readChanges.done': 'Perubahan dibaca',
+ 'conversations.tools.runLinter.active': 'Menjalankan linter',
+ 'conversations.tools.runLinter.done': 'Linter dijalankan',
+ 'conversations.tools.runTests.active': 'Menjalankan pengujian',
+ 'conversations.tools.runTests.done': 'Pengujian dijalankan',
+ 'conversations.tools.analyzeCode.active': 'Menganalisis kode',
+ 'conversations.tools.analyzeCode.done': 'Kode dianalisis',
+ 'conversations.tools.insertRecord.active': 'Menambahkan catatan',
+ 'conversations.tools.insertRecord.done': 'Catatan ditambahkan',
+ 'conversations.tools.runCommand.active': 'Menjalankan perintah',
+ 'conversations.tools.runCommand.done': 'Perintah dijalankan',
+ 'conversations.tools.runCode.active': 'Menjalankan kode',
+ 'conversations.tools.runCode.done': 'Kode dijalankan',
+ 'conversations.tools.runPackageManager.active': 'Menjalankan npm',
+ 'conversations.tools.runPackageManager.done': 'npm dijalankan',
+ 'conversations.tools.checkInstalledTools.active': 'Memeriksa alat terpasang',
+ 'conversations.tools.checkInstalledTools.done': 'Alat terpasang diperiksa',
+ 'conversations.tools.installTool.active': 'Memasang alat',
+ 'conversations.tools.installTool.done': 'Alat dipasang',
+ 'conversations.tools.checkTime.active': 'Memeriksa waktu',
+ 'conversations.tools.checkTime.done': 'Waktu diperiksa',
+ 'conversations.tools.resolveDate.active': 'Menentukan tanggal',
+ 'conversations.tools.resolveDate.done': 'Tanggal ditentukan',
+ 'conversations.tools.retrieveOutput.active': 'Mengambil keluaran lengkap',
+ 'conversations.tools.retrieveOutput.done': 'Keluaran lengkap diambil',
+ 'conversations.tools.reviewWorkspace.active': 'Meninjau ruang kerja',
+ 'conversations.tools.reviewWorkspace.done': 'Ruang kerja ditinjau',
+ 'conversations.tools.configureProxy.active': 'Mengonfigurasi proxy',
+ 'conversations.tools.configureProxy.done': 'Proxy dikonfigurasi',
+ 'conversations.tools.checkUpdates.active': 'Memeriksa pembaruan',
+ 'conversations.tools.checkUpdates.done': 'Pembaruan diperiksa',
+ 'conversations.tools.installUpdate.active': 'Memasang pembaruan',
+ 'conversations.tools.installUpdate.done': 'Pembaruan dipasang',
+ 'conversations.tools.sendNotification.active': 'Mengirim notifikasi',
+ 'conversations.tools.sendNotification.done': 'Notifikasi dikirim',
+ 'conversations.tools.reviewToolUsage.active': 'Meninjau penggunaan alat',
+ 'conversations.tools.reviewToolUsage.done': 'Penggunaan alat ditinjau',
+ 'conversations.tools.typeKeys.active': 'Mengetik',
+ 'conversations.tools.typeKeys.done': 'Selesai mengetik',
+ 'conversations.tools.click.active': 'Mengeklik',
+ 'conversations.tools.click.done': 'Diklik',
+ 'conversations.tools.searchWeb.active': 'Mencari di web',
+ 'conversations.tools.searchWeb.done': 'Pencarian web selesai',
+ 'conversations.tools.searchNews.active': 'Mencari berita',
+ 'conversations.tools.searchNews.done': 'Berita dicari',
+ 'conversations.tools.searchImages.active': 'Mencari gambar',
+ 'conversations.tools.searchImages.done': 'Gambar dicari',
+ 'conversations.tools.searchVideos.active': 'Mencari video',
+ 'conversations.tools.searchVideos.done': 'Video dicari',
+ 'conversations.tools.findSimilarPages.active': 'Mencari halaman serupa',
+ 'conversations.tools.findSimilarPages.done': 'Halaman serupa ditemukan',
+ 'conversations.tools.readPages.active': 'Membaca halaman',
+ 'conversations.tools.readPages.done': 'Halaman dibaca',
+ 'conversations.tools.readWebpage.active': 'Membaca halaman web',
+ 'conversations.tools.readWebpage.done': 'Halaman web dibaca',
+ 'conversations.tools.research.active': 'Meriset',
+ 'conversations.tools.research.done': 'Riset selesai',
+ 'conversations.tools.enrichData.active': 'Memperkaya data',
+ 'conversations.tools.enrichData.done': 'Data diperkaya',
+ 'conversations.tools.buildDataset.active': 'Membangun dataset',
+ 'conversations.tools.buildDataset.done': 'Dataset dibangun',
+ 'conversations.tools.askTheWeb.active': 'Bertanya ke web',
+ 'conversations.tools.askTheWeb.done': 'Sudah bertanya ke web',
+ 'conversations.tools.browseForYou.active': 'Menjelajah untuk Anda',
+ 'conversations.tools.browseForYou.done': 'Selesai menjelajah untuk Anda',
+ 'conversations.tools.callApi.active': 'Memanggil API',
+ 'conversations.tools.callApi.done': 'API dipanggil',
+ 'conversations.tools.downloadFile.active': 'Mengunduh file',
+ 'conversations.tools.downloadFile.done': 'File diunduh',
+ 'conversations.tools.makePaidRequest.active': 'Mengirim permintaan berbayar',
+ 'conversations.tools.makePaidRequest.done': 'Permintaan berbayar dikirim',
+ 'conversations.tools.searchDocs.active': 'Mencari dokumentasi',
+ 'conversations.tools.searchDocs.done': 'Dokumentasi dicari',
+ 'conversations.tools.readDocs.active': 'Membaca dokumentasi',
+ 'conversations.tools.readDocs.done': 'Dokumentasi dibaca',
+ 'conversations.tools.useBrowser.active': 'Menggunakan peramban',
+ 'conversations.tools.useBrowser.done': 'Peramban digunakan',
+ 'conversations.tools.openPage.active': 'Membuka halaman',
+ 'conversations.tools.openPage.done': 'Halaman dibuka',
+ 'conversations.tools.navigate.active': 'Bernavigasi',
+ 'conversations.tools.navigate.done': 'Navigasi selesai',
+ 'conversations.tools.takeScreenshot.active': 'Mengambil tangkapan layar',
+ 'conversations.tools.takeScreenshot.done': 'Tangkapan layar diambil',
+ 'conversations.tools.scrollPage.active': 'Menggulir',
+ 'conversations.tools.scrollPage.done': 'Selesai menggulir',
+ 'conversations.tools.readPage.active': 'Membaca halaman',
+ 'conversations.tools.readPage.done': 'Halaman dibaca',
+ 'conversations.tools.analyzeImage.active': 'Menganalisis gambar',
+ 'conversations.tools.analyzeImage.done': 'Gambar dianalisis',
+ 'conversations.tools.generateImage.active': 'Membuat gambar',
+ 'conversations.tools.generateImage.done': 'Gambar dibuat',
+ 'conversations.tools.generateVideo.active': 'Membuat video',
+ 'conversations.tools.generateVideo.done': 'Video dibuat',
+ 'conversations.tools.checkMediaModels.active': 'Memeriksa model media',
+ 'conversations.tools.checkMediaModels.done': 'Model media diperiksa',
+ 'conversations.tools.createDocument.active': 'Membuat dokumen',
+ 'conversations.tools.createDocument.done': 'Dokumen dibuat',
+ 'conversations.tools.createPresentation.active': 'Membuat presentasi',
+ 'conversations.tools.createPresentation.done': 'Presentasi dibuat',
+ 'conversations.tools.generatePodcast.active': 'Membuat podcast',
+ 'conversations.tools.generatePodcast.done': 'Podcast dibuat',
+ 'conversations.tools.emailPodcast.active': 'Mengirim podcast lewat email',
+ 'conversations.tools.emailPodcast.done': 'Podcast dikirim lewat email',
+ 'conversations.tools.createAndEmailPodcast.active': 'Membuat dan mengirim podcast lewat email',
+ 'conversations.tools.createAndEmailPodcast.done': 'Podcast dibuat dan dikirim lewat email',
+ 'conversations.tools.recallMemories.active': 'Mengingat kembali memori',
+ 'conversations.tools.recallMemories.done': 'Memori diingat kembali',
+ 'conversations.tools.saveToMemory.active': 'Menyimpan ke memori',
+ 'conversations.tools.saveToMemory.done': 'Disimpan ke memori',
+ 'conversations.tools.forgetMemory.active': 'Melupakan memori',
+ 'conversations.tools.forgetMemory.done': 'Memori dilupakan',
+ 'conversations.tools.searchMemory.active': 'Mencari di memori',
+ 'conversations.tools.searchMemory.done': 'Memori dicari',
+ 'conversations.tools.inspectMemory.active': 'Memeriksa memori',
+ 'conversations.tools.inspectMemory.done': 'Memori diperiksa',
+ 'conversations.tools.exploreMemory.active': 'Menjelajahi memori',
+ 'conversations.tools.exploreMemory.done': 'Memori dijelajahi',
+ 'conversations.tools.saveDocumentToMemory.active': 'Menyimpan dokumen ke memori',
+ 'conversations.tools.saveDocumentToMemory.done': 'Dokumen disimpan ke memori',
+ 'conversations.tools.updateGoals.active': 'Memperbarui tujuan',
+ 'conversations.tools.updateGoals.done': 'Tujuan diperbarui',
+ 'conversations.tools.reviewGoals.active': 'Meninjau tujuan',
+ 'conversations.tools.reviewGoals.done': 'Tujuan ditinjau',
+ 'conversations.tools.savePreference.active': 'Menyimpan preferensi',
+ 'conversations.tools.savePreference.done': 'Preferensi disimpan',
+ 'conversations.tools.reviewLearnings.active': 'Meninjau hal yang saya pelajari',
+ 'conversations.tools.reviewLearnings.done': 'Hal yang saya pelajari ditinjau',
+ 'conversations.tools.updateLearnings.active': 'Memperbarui hal yang saya pelajari',
+ 'conversations.tools.updateLearnings.done': 'Hal yang saya pelajari diperbarui',
+ 'conversations.tools.delegateTask.active': 'Mendelegasikan tugas',
+ 'conversations.tools.delegateTask.done': 'Tugas didelegasikan',
+ 'conversations.tools.runAgentsInParallel.active': 'Menjalankan agen secara paralel',
+ 'conversations.tools.runAgentsInParallel.done': 'Agen dijalankan secara paralel',
+ 'conversations.tools.messageAgent.active': 'Mengirim pesan ke agen',
+ 'conversations.tools.messageAgent.done': 'Pesan dikirim ke agen',
+ 'conversations.tools.waitForAgent.active': 'Menunggu agen',
+ 'conversations.tools.waitForAgent.done': 'Selesai menunggu agen',
+ 'conversations.tools.wait.active': 'Menunggu',
+ 'conversations.tools.wait.done': 'Selesai menunggu',
+ 'conversations.tools.closeAgent.active': 'Menutup agen',
+ 'conversations.tools.closeAgent.done': 'Agen ditutup',
+ 'conversations.tools.checkAgents.active': 'Memeriksa agen',
+ 'conversations.tools.checkAgents.done': 'Agen diperiksa',
+ 'conversations.tools.askQuestion.active': 'Mengajukan pertanyaan kepada Anda',
+ 'conversations.tools.askQuestion.done': 'Pertanyaan diajukan kepada Anda',
+ 'conversations.tools.prepareContext.active': 'Menyiapkan konteks',
+ 'conversations.tools.prepareContext.done': 'Konteks disiapkan',
+ 'conversations.tools.extractDetails.active': 'Mengekstrak detail',
+ 'conversations.tools.extractDetails.done': 'Detail diekstrak',
+ 'conversations.tools.planNextSteps.active': 'Merencanakan langkah berikutnya',
+ 'conversations.tools.planNextSteps.done': 'Langkah berikutnya direncanakan',
+ 'conversations.tools.reviewWork.active': 'Meninjau pekerjaan',
+ 'conversations.tools.reviewWork.done': 'Pekerjaan ditinjau',
+ 'conversations.tools.scoutContext.active': 'Menelusuri konteks',
+ 'conversations.tools.scoutContext.done': 'Konteks ditelusuri',
+ 'conversations.tools.useTools.active': 'Menggunakan alat',
+ 'conversations.tools.useTools.done': 'Alat digunakan',
+ 'conversations.tools.checkConnectedApp.active': 'Memeriksa aplikasi terhubung Anda',
+ 'conversations.tools.checkConnectedApp.done': 'Aplikasi terhubung Anda diperiksa',
+ 'conversations.tools.updateTodos.active': 'Memperbarui daftar tugas',
+ 'conversations.tools.updateTodos.done': 'Daftar tugas diperbarui',
+ 'conversations.tools.requestPlanReview.active': 'Meminta tinjauan rencana',
+ 'conversations.tools.requestPlanReview.done': 'Tinjauan rencana diminta',
+ 'conversations.tools.finishPlan.active': 'Menyelesaikan rencana',
+ 'conversations.tools.finishPlan.done': 'Rencana diselesaikan',
+ 'conversations.tools.setGoal.active': 'Menetapkan tujuan',
+ 'conversations.tools.setGoal.done': 'Tujuan ditetapkan',
+ 'conversations.tools.checkGoal.active': 'Memeriksa tujuan',
+ 'conversations.tools.checkGoal.done': 'Tujuan diperiksa',
+ 'conversations.tools.completeGoal.active': 'Menuntaskan tujuan',
+ 'conversations.tools.completeGoal.done': 'Tujuan dituntaskan',
+ 'conversations.tools.scheduleTask.active': 'Menjadwalkan tugas',
+ 'conversations.tools.scheduleTask.done': 'Tugas dijadwalkan',
+ 'conversations.tools.checkSchedules.active': 'Memeriksa jadwal',
+ 'conversations.tools.checkSchedules.done': 'Jadwal diperiksa',
+ 'conversations.tools.updateSchedule.active': 'Memperbarui tugas terjadwal',
+ 'conversations.tools.updateSchedule.done': 'Tugas terjadwal diperbarui',
+ 'conversations.tools.removeSchedule.active': 'Menghapus tugas terjadwal',
+ 'conversations.tools.removeSchedule.done': 'Tugas terjadwal dihapus',
+ 'conversations.tools.runScheduledTask.active': 'Menjalankan tugas terjadwal',
+ 'conversations.tools.runScheduledTask.done': 'Tugas terjadwal dijalankan',
+ 'conversations.tools.checkRunHistory.active': 'Memeriksa riwayat eksekusi',
+ 'conversations.tools.checkRunHistory.done': 'Riwayat eksekusi diperiksa',
+ 'conversations.tools.useApp.active': 'Menggunakan {app}',
+ 'conversations.tools.useApp.done': '{app} digunakan',
+ 'conversations.tools.checkAvailableApps.active': 'Memeriksa aplikasi yang tersedia',
+ 'conversations.tools.checkAvailableApps.done': 'Aplikasi yang tersedia diperiksa',
+ 'conversations.tools.checkConnections.active': 'Memeriksa koneksi Anda',
+ 'conversations.tools.checkConnections.done': 'Koneksi Anda diperiksa',
+ 'conversations.tools.connectApp.active': 'Menghubungkan aplikasi',
+ 'conversations.tools.connectApp.done': 'Aplikasi terhubung',
+ 'conversations.tools.authorizeApp.active': 'Mengotorisasi aplikasi',
+ 'conversations.tools.authorizeApp.done': 'Aplikasi diotorisasi',
+ 'conversations.tools.findAppActions.active': 'Mencari tindakan aplikasi',
+ 'conversations.tools.findAppActions.done': 'Tindakan aplikasi ditemukan',
+ 'conversations.tools.runAppAction.active': 'Menjalankan tindakan aplikasi',
+ 'conversations.tools.runAppAction.done': 'Tindakan aplikasi dijalankan',
+ 'conversations.tools.findTools.active': 'Mencari alat',
+ 'conversations.tools.findTools.done': 'Alat ditemukan',
+ 'conversations.tools.useTool.active': 'Menggunakan {tool}',
+ 'conversations.tools.useTool.done': '{tool} digunakan',
+ 'conversations.tools.unsubscribe.active': 'Berhenti berlangganan',
+ 'conversations.tools.unsubscribe.done': 'Langganan dihentikan',
+ 'conversations.tools.searchPlaces.active': 'Mencari tempat',
+ 'conversations.tools.searchPlaces.done': 'Tempat dicari',
+ 'conversations.tools.lookUpPlace.active': 'Mencari info tempat',
+ 'conversations.tools.lookUpPlace.done': 'Info tempat ditemukan',
+ 'conversations.tools.checkMarkets.active': 'Memeriksa pasar',
+ 'conversations.tools.checkMarkets.done': 'Pasar diperiksa',
+ 'conversations.tools.placeCall.active': 'Melakukan panggilan',
+ 'conversations.tools.placeCall.done': 'Panggilan dilakukan',
+ 'conversations.tools.checkTaskSources.active': 'Memeriksa sumber tugas',
+ 'conversations.tools.checkTaskSources.done': 'Sumber tugas diperiksa',
+ 'conversations.tools.updateTaskSources.active': 'Memperbarui sumber tugas',
+ 'conversations.tools.updateTaskSources.done': 'Sumber tugas diperbarui',
+ 'conversations.tools.fetchTasks.active': 'Mengambil tugas',
+ 'conversations.tools.fetchTasks.done': 'Tugas diambil',
+ 'conversations.tools.checkMcpServers.active': 'Memeriksa server MCP',
+ 'conversations.tools.checkMcpServers.done': 'Server MCP diperiksa',
+ 'conversations.tools.checkMcpTools.active': 'Memeriksa alat MCP',
+ 'conversations.tools.checkMcpTools.done': 'Alat MCP diperiksa',
+ 'conversations.tools.callMcpTool.active': 'Memanggil {tool}',
+ 'conversations.tools.callMcpTool.done': '{tool} dipanggil',
+ 'conversations.tools.searchMcpServers.active': 'Mencari server MCP',
+ 'conversations.tools.searchMcpServers.done': 'Server MCP dicari',
+ 'conversations.tools.connectMcpServer.active': 'Menghubungkan server MCP',
+ 'conversations.tools.connectMcpServer.done': 'Server MCP terhubung',
+ 'conversations.tools.disconnectMcpServer.active': 'Memutuskan server MCP',
+ 'conversations.tools.disconnectMcpServer.done': 'Server MCP diputuskan',
+ 'conversations.tools.removeMcpServer.active': 'Menghapus server MCP',
+ 'conversations.tools.removeMcpServer.done': 'Server MCP dihapus',
+ 'conversations.tools.uploadFile.active': 'Mengunggah file',
+ 'conversations.tools.uploadFile.done': 'File diunggah',
+ 'conversations.tools.listStoredFiles.active': 'Menampilkan file tersimpan',
+ 'conversations.tools.listStoredFiles.done': 'File tersimpan ditampilkan',
+ 'conversations.tools.createShareLink.active': 'Membuat tautan berbagi',
+ 'conversations.tools.createShareLink.done': 'Tautan berbagi dibuat',
+ 'conversations.tools.deleteFile.active': 'Menghapus file',
+ 'conversations.tools.deleteFile.done': 'File dihapus',
+ 'conversations.tools.updateFileAccess.active': 'Memperbarui akses file',
+ 'conversations.tools.updateFileAccess.done': 'Akses file diperbarui',
+ 'conversations.tools.deploySite.active': 'Menerapkan situs',
+ 'conversations.tools.deploySite.done': 'Situs diterapkan',
+ 'conversations.tools.checkHosting.active': 'Memeriksa hosting',
+ 'conversations.tools.checkHosting.done': 'Hosting diperiksa',
+ 'conversations.tools.updateHosting.active': 'Memperbarui hosting',
+ 'conversations.tools.updateHosting.done': 'Hosting diperbarui',
+ 'conversations.tools.rollBackDeployment.active': 'Membatalkan penerapan',
+ 'conversations.tools.rollBackDeployment.done': 'Penerapan dibatalkan',
+ 'conversations.tools.checkWallet.active': 'Memeriksa dompet',
+ 'conversations.tools.checkWallet.done': 'Dompet diperiksa',
+ 'conversations.tools.prepareTransfer.active': 'Menyiapkan transfer',
+ 'conversations.tools.prepareTransfer.done': 'Transfer disiapkan',
+ 'conversations.tools.checkTransaction.active': 'Memeriksa transaksi',
+ 'conversations.tools.checkTransaction.done': 'Transaksi diperiksa',
+ 'conversations.tools.getSwapQuote.active': 'Mengambil kuotasi swap',
+ 'conversations.tools.getSwapQuote.done': 'Kuotasi swap diambil',
+ 'conversations.tools.swapTokens.active': 'Menukar token',
+ 'conversations.tools.swapTokens.done': 'Token ditukar',
+ 'conversations.tools.getBridgeQuote.active': 'Mengambil kuotasi bridge',
+ 'conversations.tools.getBridgeQuote.done': 'Kuotasi bridge diambil',
+ 'conversations.tools.bridgeTokens.active': 'Memindahkan token lewat bridge',
+ 'conversations.tools.bridgeTokens.done': 'Token dipindahkan lewat bridge',
+ 'conversations.tools.callDapp.active': 'Memanggil kontrak aplikasi',
+ 'conversations.tools.callDapp.done': 'Kontrak aplikasi dipanggil',
+ 'conversations.tools.useSkill.active': 'Menggunakan keahlian',
+ 'conversations.tools.useSkill.done': 'Keahlian digunakan',
+ 'conversations.tools.searchSkills.active': 'Mencari keahlian',
+ 'conversations.tools.searchSkills.done': 'Keahlian dicari',
+ 'conversations.tools.checkSkills.active': 'Memeriksa keahlian',
+ 'conversations.tools.checkSkills.done': 'Keahlian diperiksa',
+ 'conversations.tools.installSkill.active': 'Memasang keahlian',
+ 'conversations.tools.installSkill.done': 'Keahlian dipasang',
+ 'conversations.tools.removeSkill.active': 'Menghapus keahlian',
+ 'conversations.tools.removeSkill.done': 'Keahlian dihapus',
+ 'conversations.tools.createSkill.active': 'Membuat keahlian',
+ 'conversations.tools.createSkill.done': 'Keahlian dibuat',
+ 'conversations.tools.runWorkflow.active': 'Menjalankan alur kerja',
+ 'conversations.tools.runWorkflow.done': 'Alur kerja dijalankan',
+ 'conversations.tools.waitForWorkflow.active': 'Menunggu alur kerja',
+ 'conversations.tools.waitForWorkflow.done': 'Selesai menunggu alur kerja',
+ 'conversations.tools.designWorkflow.active': 'Merancang alur kerja',
+ 'conversations.tools.designWorkflow.done': 'Alur kerja dirancang',
+ 'conversations.tools.saveWorkflow.active': 'Menyimpan alur kerja',
+ 'conversations.tools.saveWorkflow.done': 'Alur kerja disimpan',
+ 'conversations.tools.validateWorkflow.active': 'Memvalidasi alur kerja',
+ 'conversations.tools.validateWorkflow.done': 'Alur kerja divalidasi',
+ 'conversations.tools.testWorkflow.active': 'Menguji alur kerja',
+ 'conversations.tools.testWorkflow.done': 'Alur kerja diuji',
+ 'conversations.tools.checkWorkflows.active': 'Memeriksa alur kerja',
+ 'conversations.tools.checkWorkflows.done': 'Alur kerja diperiksa',
+ 'conversations.tools.cancelWorkflow.active': 'Membatalkan eksekusi alur kerja',
+ 'conversations.tools.cancelWorkflow.done': 'Eksekusi alur kerja dibatalkan',
+ 'conversations.tools.suggestWorkflows.active': 'Menyarankan alur kerja',
+ 'conversations.tools.suggestWorkflows.done': 'Alur kerja disarankan',
+ 'conversations.tools.checkSettings.active': 'Memeriksa pengaturan',
+ 'conversations.tools.checkSettings.done': 'Pengaturan diperiksa',
+ 'conversations.tools.checkSecurity.active': 'Memeriksa keamanan',
+ 'conversations.tools.checkSecurity.done': 'Keamanan diperiksa',
+ 'conversations.tools.runDiagnostics.active': 'Menjalankan diagnostik',
+ 'conversations.tools.runDiagnostics.done': 'Diagnostik dijalankan',
+ 'conversations.tools.checkUsageCosts.active': 'Memeriksa biaya penggunaan',
+ 'conversations.tools.checkUsageCosts.done': 'Biaya penggunaan diperiksa',
+ 'conversations.tools.manageService.active': 'Mengelola layanan latar belakang',
+ 'conversations.tools.manageService.done': 'Layanan latar belakang dikelola',
+ 'conversations.tools.readPersona.active': 'Membaca persona',
+ 'conversations.tools.readPersona.done': 'Persona dibaca',
+ 'conversations.tools.updatePersona.active': 'Memperbarui persona',
+ 'conversations.tools.updatePersona.done': 'Persona diperbarui',
+ 'conversations.tools.setUpWorkspace.active': 'Menyiapkan ruang kerja',
+ 'conversations.tools.setUpWorkspace.done': 'Ruang kerja disiapkan',
+ 'conversations.tools.checkArtifacts.active': 'Memeriksa artefak',
+ 'conversations.tools.checkArtifacts.done': 'Artefak diperiksa',
+ 'conversations.tools.deleteArtifact.active': 'Menghapus artefak',
+ 'conversations.tools.deleteArtifact.done': 'Artefak dihapus',
'conversations.subagent.noOutput': 'Tidak ada keluaran',
'conversations.subagent.close': 'Tutup',
'conversations.subagent.cancel': 'Batalkan tugas',
diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts
index 30f5b5c36ca..3ed6e95c660 100644
--- a/app/src/lib/i18n/it.ts
+++ b/app/src/lib/i18n/it.ts
@@ -3373,6 +3373,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'Ancora nessun output',
'conversations.subagent.input': 'Input',
'conversations.subagent.output': 'Output',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} passaggio',
+ 'conversations.tools.steps.other': '{count} passaggi',
+ 'conversations.tools.working': 'In corso',
+ 'conversations.tools.noOutput': 'Nessun output',
+ 'conversations.tools.delegatedTo': 'Delegato a {agent}',
+ 'conversations.tools.openInBrowser': 'Apri nel browser',
+ 'conversations.tools.status.running': 'in corso',
+ 'conversations.tools.status.done': 'completato',
+ 'conversations.tools.status.failed': 'non riuscito',
+ 'conversations.tools.status.cancelled': 'annullato',
+ 'conversations.tools.status.awaiting': 'in attesa di risposta',
+ 'conversations.tools.search.searching': 'Ricerca in corso',
+ 'conversations.tools.search.none': 'Nessun risultato',
+ 'conversations.tools.search.found.one': '{count} risultato trovato',
+ 'conversations.tools.search.found.other': '{count} risultati trovati',
+ 'conversations.tools.search.via': 'tramite {provider}',
+ 'conversations.tools.readFile.active': 'Lettura del file',
+ 'conversations.tools.readFile.done': 'File letto',
+ 'conversations.tools.writeFile.active': 'Scrittura del file',
+ 'conversations.tools.writeFile.done': 'File scritto',
+ 'conversations.tools.editFile.active': 'Modifica del file',
+ 'conversations.tools.editFile.done': 'File modificato',
+ 'conversations.tools.applyEdits.active': 'Applicazione delle modifiche',
+ 'conversations.tools.applyEdits.done': 'Modifiche applicate',
+ 'conversations.tools.searchCode.active': 'Ricerca nel codice',
+ 'conversations.tools.searchCode.done': 'Codice esaminato',
+ 'conversations.tools.findFiles.active': 'Ricerca dei file',
+ 'conversations.tools.findFiles.done': 'File trovati',
+ 'conversations.tools.listFolder.active': 'Elenco della cartella',
+ 'conversations.tools.listFolder.done': 'Cartella elencata',
+ 'conversations.tools.exportCsv.active': 'Esportazione CSV',
+ 'conversations.tools.exportCsv.done': 'CSV esportato',
+ 'conversations.tools.updateMemoryNotes.active': 'Aggiornamento delle note di memoria',
+ 'conversations.tools.updateMemoryNotes.done': 'Note di memoria aggiornate',
+ 'conversations.tools.runGit.active': 'Esecuzione di git',
+ 'conversations.tools.runGit.done': 'git eseguito',
+ 'conversations.tools.readChanges.active': 'Lettura delle modifiche',
+ 'conversations.tools.readChanges.done': 'Modifiche lette',
+ 'conversations.tools.runLinter.active': 'Esecuzione del linter',
+ 'conversations.tools.runLinter.done': 'Linter eseguito',
+ 'conversations.tools.runTests.active': 'Esecuzione dei test',
+ 'conversations.tools.runTests.done': 'Test eseguiti',
+ 'conversations.tools.analyzeCode.active': 'Analisi del codice',
+ 'conversations.tools.analyzeCode.done': 'Codice analizzato',
+ 'conversations.tools.insertRecord.active': 'Inserimento del record',
+ 'conversations.tools.insertRecord.done': 'Record inserito',
+ 'conversations.tools.runCommand.active': 'Esecuzione del comando',
+ 'conversations.tools.runCommand.done': 'Comando eseguito',
+ 'conversations.tools.runCode.active': 'Esecuzione del codice',
+ 'conversations.tools.runCode.done': 'Codice eseguito',
+ 'conversations.tools.runPackageManager.active': 'Esecuzione di npm',
+ 'conversations.tools.runPackageManager.done': 'npm eseguito',
+ 'conversations.tools.checkInstalledTools.active': 'Verifica degli strumenti installati',
+ 'conversations.tools.checkInstalledTools.done': 'Strumenti installati verificati',
+ 'conversations.tools.installTool.active': 'Installazione dello strumento',
+ 'conversations.tools.installTool.done': 'Strumento installato',
+ 'conversations.tools.checkTime.active': "Controllo dell'ora",
+ 'conversations.tools.checkTime.done': 'Ora controllata',
+ 'conversations.tools.resolveDate.active': 'Calcolo della data',
+ 'conversations.tools.resolveDate.done': 'Data calcolata',
+ 'conversations.tools.retrieveOutput.active': "Recupero dell'output completo",
+ 'conversations.tools.retrieveOutput.done': 'Output completo recuperato',
+ 'conversations.tools.reviewWorkspace.active': "Esame dell'area di lavoro",
+ 'conversations.tools.reviewWorkspace.done': 'Area di lavoro esaminata',
+ 'conversations.tools.configureProxy.active': 'Configurazione del proxy',
+ 'conversations.tools.configureProxy.done': 'Proxy configurato',
+ 'conversations.tools.checkUpdates.active': 'Ricerca di aggiornamenti',
+ 'conversations.tools.checkUpdates.done': 'Aggiornamenti verificati',
+ 'conversations.tools.installUpdate.active': "Installazione dell'aggiornamento",
+ 'conversations.tools.installUpdate.done': 'Aggiornamento installato',
+ 'conversations.tools.sendNotification.active': 'Invio della notifica',
+ 'conversations.tools.sendNotification.done': 'Notifica inviata',
+ 'conversations.tools.reviewToolUsage.active': "Esame dell'uso degli strumenti",
+ 'conversations.tools.reviewToolUsage.done': 'Uso degli strumenti esaminato',
+ 'conversations.tools.typeKeys.active': 'Digitazione in corso',
+ 'conversations.tools.typeKeys.done': 'Testo digitato',
+ 'conversations.tools.click.active': 'Clic in corso',
+ 'conversations.tools.click.done': 'Clic eseguito',
+ 'conversations.tools.searchWeb.active': 'Ricerca sul web',
+ 'conversations.tools.searchWeb.done': 'Ricerca web completata',
+ 'conversations.tools.searchNews.active': 'Ricerca di notizie',
+ 'conversations.tools.searchNews.done': 'Notizie cercate',
+ 'conversations.tools.searchImages.active': 'Ricerca di immagini',
+ 'conversations.tools.searchImages.done': 'Immagini cercate',
+ 'conversations.tools.searchVideos.active': 'Ricerca di video',
+ 'conversations.tools.searchVideos.done': 'Video cercati',
+ 'conversations.tools.findSimilarPages.active': 'Ricerca di pagine simili',
+ 'conversations.tools.findSimilarPages.done': 'Pagine simili trovate',
+ 'conversations.tools.readPages.active': 'Lettura delle pagine',
+ 'conversations.tools.readPages.done': 'Pagine lette',
+ 'conversations.tools.readWebpage.active': 'Lettura della pagina web',
+ 'conversations.tools.readWebpage.done': 'Pagina web letta',
+ 'conversations.tools.research.active': 'Approfondimento in corso',
+ 'conversations.tools.research.done': 'Approfondimento completato',
+ 'conversations.tools.enrichData.active': 'Arricchimento dei dati',
+ 'conversations.tools.enrichData.done': 'Dati arricchiti',
+ 'conversations.tools.buildDataset.active': 'Creazione del set di dati',
+ 'conversations.tools.buildDataset.done': 'Set di dati creato',
+ 'conversations.tools.askTheWeb.active': 'Interrogazione del web',
+ 'conversations.tools.askTheWeb.done': 'Web interrogato',
+ 'conversations.tools.browseForYou.active': 'Navigazione per te',
+ 'conversations.tools.browseForYou.done': 'Navigazione completata per te',
+ 'conversations.tools.callApi.active': "Chiamata all'API",
+ 'conversations.tools.callApi.done': 'API chiamata',
+ 'conversations.tools.downloadFile.active': 'Download del file',
+ 'conversations.tools.downloadFile.done': 'File scaricato',
+ 'conversations.tools.makePaidRequest.active': 'Invio di una richiesta a pagamento',
+ 'conversations.tools.makePaidRequest.done': 'Richiesta a pagamento inviata',
+ 'conversations.tools.searchDocs.active': 'Ricerca nella documentazione',
+ 'conversations.tools.searchDocs.done': 'Documentazione consultata',
+ 'conversations.tools.readDocs.active': 'Lettura della documentazione',
+ 'conversations.tools.readDocs.done': 'Documentazione letta',
+ 'conversations.tools.useBrowser.active': 'Uso del browser',
+ 'conversations.tools.useBrowser.done': 'Browser utilizzato',
+ 'conversations.tools.openPage.active': 'Apertura della pagina',
+ 'conversations.tools.openPage.done': 'Pagina aperta',
+ 'conversations.tools.navigate.active': 'Navigazione in corso',
+ 'conversations.tools.navigate.done': 'Navigazione completata',
+ 'conversations.tools.takeScreenshot.active': 'Acquisizione dello screenshot',
+ 'conversations.tools.takeScreenshot.done': 'Screenshot acquisito',
+ 'conversations.tools.scrollPage.active': 'Scorrimento in corso',
+ 'conversations.tools.scrollPage.done': 'Scorrimento completato',
+ 'conversations.tools.readPage.active': 'Lettura della pagina',
+ 'conversations.tools.readPage.done': 'Pagina letta',
+ 'conversations.tools.analyzeImage.active': "Analisi dell'immagine",
+ 'conversations.tools.analyzeImage.done': 'Immagine analizzata',
+ 'conversations.tools.generateImage.active': "Generazione dell'immagine",
+ 'conversations.tools.generateImage.done': 'Immagine generata',
+ 'conversations.tools.generateVideo.active': 'Generazione del video',
+ 'conversations.tools.generateVideo.done': 'Video generato',
+ 'conversations.tools.checkMediaModels.active': 'Verifica dei modelli multimediali',
+ 'conversations.tools.checkMediaModels.done': 'Modelli multimediali verificati',
+ 'conversations.tools.createDocument.active': 'Creazione del documento',
+ 'conversations.tools.createDocument.done': 'Documento creato',
+ 'conversations.tools.createPresentation.active': 'Creazione della presentazione',
+ 'conversations.tools.createPresentation.done': 'Presentazione creata',
+ 'conversations.tools.generatePodcast.active': 'Generazione del podcast',
+ 'conversations.tools.generatePodcast.done': 'Podcast generato',
+ 'conversations.tools.emailPodcast.active': 'Invio del podcast via email',
+ 'conversations.tools.emailPodcast.done': 'Podcast inviato via email',
+ 'conversations.tools.createAndEmailPodcast.active': 'Creazione e invio del podcast via email',
+ 'conversations.tools.createAndEmailPodcast.done': 'Podcast creato e inviato via email',
+ 'conversations.tools.recallMemories.active': 'Recupero dei ricordi',
+ 'conversations.tools.recallMemories.done': 'Ricordi recuperati',
+ 'conversations.tools.saveToMemory.active': 'Salvataggio in memoria',
+ 'conversations.tools.saveToMemory.done': 'Salvato in memoria',
+ 'conversations.tools.forgetMemory.active': 'Rimozione del ricordo',
+ 'conversations.tools.forgetMemory.done': 'Ricordo rimosso',
+ 'conversations.tools.searchMemory.active': 'Ricerca nella memoria',
+ 'conversations.tools.searchMemory.done': 'Memoria consultata',
+ 'conversations.tools.inspectMemory.active': 'Ispezione della memoria',
+ 'conversations.tools.inspectMemory.done': 'Memoria ispezionata',
+ 'conversations.tools.exploreMemory.active': 'Esplorazione della memoria',
+ 'conversations.tools.exploreMemory.done': 'Memoria esplorata',
+ 'conversations.tools.saveDocumentToMemory.active': 'Salvataggio del documento in memoria',
+ 'conversations.tools.saveDocumentToMemory.done': 'Documento salvato in memoria',
+ 'conversations.tools.updateGoals.active': 'Aggiornamento degli obiettivi',
+ 'conversations.tools.updateGoals.done': 'Obiettivi aggiornati',
+ 'conversations.tools.reviewGoals.active': 'Esame degli obiettivi',
+ 'conversations.tools.reviewGoals.done': 'Obiettivi esaminati',
+ 'conversations.tools.savePreference.active': 'Salvataggio della preferenza',
+ 'conversations.tools.savePreference.done': 'Preferenza salvata',
+ 'conversations.tools.reviewLearnings.active': 'Esame di ciò che ho imparato',
+ 'conversations.tools.reviewLearnings.done': 'Apprendimenti esaminati',
+ 'conversations.tools.updateLearnings.active': 'Aggiornamento di ciò che ho imparato',
+ 'conversations.tools.updateLearnings.done': 'Apprendimenti aggiornati',
+ 'conversations.tools.delegateTask.active': "Delega dell'attività",
+ 'conversations.tools.delegateTask.done': 'Attività delegata',
+ 'conversations.tools.runAgentsInParallel.active': 'Esecuzione di agenti in parallelo',
+ 'conversations.tools.runAgentsInParallel.done': 'Agenti eseguiti in parallelo',
+ 'conversations.tools.messageAgent.active': "Invio di un messaggio all'agente",
+ 'conversations.tools.messageAgent.done': "Messaggio inviato all'agente",
+ 'conversations.tools.waitForAgent.active': "Attesa dell'agente",
+ 'conversations.tools.waitForAgent.done': "Attesa dell'agente terminata",
+ 'conversations.tools.wait.active': 'Attesa in corso',
+ 'conversations.tools.wait.done': 'Attesa terminata',
+ 'conversations.tools.closeAgent.active': "Chiusura dell'agente",
+ 'conversations.tools.closeAgent.done': 'Agente chiuso',
+ 'conversations.tools.checkAgents.active': 'Verifica degli agenti',
+ 'conversations.tools.checkAgents.done': 'Agenti verificati',
+ 'conversations.tools.askQuestion.active': 'Ti sto facendo una domanda',
+ 'conversations.tools.askQuestion.done': 'Domanda posta',
+ 'conversations.tools.prepareContext.active': 'Preparazione del contesto',
+ 'conversations.tools.prepareContext.done': 'Contesto preparato',
+ 'conversations.tools.extractDetails.active': 'Estrazione dei dettagli',
+ 'conversations.tools.extractDetails.done': 'Dettagli estratti',
+ 'conversations.tools.planNextSteps.active': 'Pianificazione dei prossimi passi',
+ 'conversations.tools.planNextSteps.done': 'Prossimi passi pianificati',
+ 'conversations.tools.reviewWork.active': 'Revisione del lavoro',
+ 'conversations.tools.reviewWork.done': 'Lavoro revisionato',
+ 'conversations.tools.scoutContext.active': 'Esplorazione del contesto',
+ 'conversations.tools.scoutContext.done': 'Contesto esplorato',
+ 'conversations.tools.useTools.active': 'Uso degli strumenti',
+ 'conversations.tools.useTools.done': 'Strumenti utilizzati',
+ 'conversations.tools.checkConnectedApp.active': 'Verifica della tua app collegata',
+ 'conversations.tools.checkConnectedApp.done': 'App collegata verificata',
+ 'conversations.tools.updateTodos.active': 'Aggiornamento della lista di cose da fare',
+ 'conversations.tools.updateTodos.done': 'Lista di cose da fare aggiornata',
+ 'conversations.tools.requestPlanReview.active': 'Richiesta di revisione del piano',
+ 'conversations.tools.requestPlanReview.done': 'Revisione del piano richiesta',
+ 'conversations.tools.finishPlan.active': 'Completamento del piano',
+ 'conversations.tools.finishPlan.done': 'Piano completato',
+ 'conversations.tools.setGoal.active': "Impostazione dell'obiettivo",
+ 'conversations.tools.setGoal.done': 'Obiettivo impostato',
+ 'conversations.tools.checkGoal.active': "Verifica dell'obiettivo",
+ 'conversations.tools.checkGoal.done': 'Obiettivo verificato',
+ 'conversations.tools.completeGoal.active': "Completamento dell'obiettivo",
+ 'conversations.tools.completeGoal.done': 'Obiettivo completato',
+ 'conversations.tools.scheduleTask.active': "Pianificazione dell'attività",
+ 'conversations.tools.scheduleTask.done': 'Attività pianificata',
+ 'conversations.tools.checkSchedules.active': 'Verifica delle pianificazioni',
+ 'conversations.tools.checkSchedules.done': 'Pianificazioni verificate',
+ 'conversations.tools.updateSchedule.active': "Aggiornamento dell'attività pianificata",
+ 'conversations.tools.updateSchedule.done': 'Attività pianificata aggiornata',
+ 'conversations.tools.removeSchedule.active': "Rimozione dell'attività pianificata",
+ 'conversations.tools.removeSchedule.done': 'Attività pianificata rimossa',
+ 'conversations.tools.runScheduledTask.active': "Esecuzione dell'attività pianificata",
+ 'conversations.tools.runScheduledTask.done': 'Attività pianificata eseguita',
+ 'conversations.tools.checkRunHistory.active': 'Verifica della cronologia delle esecuzioni',
+ 'conversations.tools.checkRunHistory.done': 'Cronologia delle esecuzioni verificata',
+ 'conversations.tools.useApp.active': 'Uso di {app}',
+ 'conversations.tools.useApp.done': '{app} utilizzato',
+ 'conversations.tools.checkAvailableApps.active': 'Verifica delle app disponibili',
+ 'conversations.tools.checkAvailableApps.done': 'App disponibili verificate',
+ 'conversations.tools.checkConnections.active': 'Verifica dei tuoi collegamenti',
+ 'conversations.tools.checkConnections.done': 'Collegamenti verificati',
+ 'conversations.tools.connectApp.active': "Collegamento dell'app",
+ 'conversations.tools.connectApp.done': 'App collegata',
+ 'conversations.tools.authorizeApp.active': "Autorizzazione dell'app",
+ 'conversations.tools.authorizeApp.done': 'App autorizzata',
+ 'conversations.tools.findAppActions.active': "Ricerca delle azioni dell'app",
+ 'conversations.tools.findAppActions.done': "Azioni dell'app trovate",
+ 'conversations.tools.runAppAction.active': "Esecuzione dell'azione dell'app",
+ 'conversations.tools.runAppAction.done': "Azione dell'app eseguita",
+ 'conversations.tools.findTools.active': 'Ricerca degli strumenti',
+ 'conversations.tools.findTools.done': 'Strumenti trovati',
+ 'conversations.tools.useTool.active': 'Uso di {tool}',
+ 'conversations.tools.useTool.done': '{tool} utilizzato',
+ 'conversations.tools.unsubscribe.active': "Annullamento dell'iscrizione",
+ 'conversations.tools.unsubscribe.done': 'Iscrizione annullata',
+ 'conversations.tools.searchPlaces.active': 'Ricerca di luoghi',
+ 'conversations.tools.searchPlaces.done': 'Luoghi cercati',
+ 'conversations.tools.lookUpPlace.active': 'Ricerca del luogo',
+ 'conversations.tools.lookUpPlace.done': 'Luogo trovato',
+ 'conversations.tools.checkMarkets.active': 'Controllo dei mercati',
+ 'conversations.tools.checkMarkets.done': 'Mercati controllati',
+ 'conversations.tools.placeCall.active': 'Chiamata in corso',
+ 'conversations.tools.placeCall.done': 'Chiamata effettuata',
+ 'conversations.tools.checkTaskSources.active': 'Verifica delle fonti delle attività',
+ 'conversations.tools.checkTaskSources.done': 'Fonti delle attività verificate',
+ 'conversations.tools.updateTaskSources.active': 'Aggiornamento delle fonti delle attività',
+ 'conversations.tools.updateTaskSources.done': 'Fonti delle attività aggiornate',
+ 'conversations.tools.fetchTasks.active': 'Recupero delle attività',
+ 'conversations.tools.fetchTasks.done': 'Attività recuperate',
+ 'conversations.tools.checkMcpServers.active': 'Verifica dei server MCP',
+ 'conversations.tools.checkMcpServers.done': 'Server MCP verificati',
+ 'conversations.tools.checkMcpTools.active': 'Verifica degli strumenti MCP',
+ 'conversations.tools.checkMcpTools.done': 'Strumenti MCP verificati',
+ 'conversations.tools.callMcpTool.active': 'Chiamata a {tool}',
+ 'conversations.tools.callMcpTool.done': '{tool} chiamato',
+ 'conversations.tools.searchMcpServers.active': 'Ricerca di server MCP',
+ 'conversations.tools.searchMcpServers.done': 'Server MCP cercati',
+ 'conversations.tools.connectMcpServer.active': 'Collegamento del server MCP',
+ 'conversations.tools.connectMcpServer.done': 'Server MCP collegato',
+ 'conversations.tools.disconnectMcpServer.active': 'Scollegamento del server MCP',
+ 'conversations.tools.disconnectMcpServer.done': 'Server MCP scollegato',
+ 'conversations.tools.removeMcpServer.active': 'Rimozione del server MCP',
+ 'conversations.tools.removeMcpServer.done': 'Server MCP rimosso',
+ 'conversations.tools.uploadFile.active': 'Caricamento del file',
+ 'conversations.tools.uploadFile.done': 'File caricato',
+ 'conversations.tools.listStoredFiles.active': 'Elenco dei file archiviati',
+ 'conversations.tools.listStoredFiles.done': 'File archiviati elencati',
+ 'conversations.tools.createShareLink.active': 'Creazione del link di condivisione',
+ 'conversations.tools.createShareLink.done': 'Link di condivisione creato',
+ 'conversations.tools.deleteFile.active': 'Eliminazione del file',
+ 'conversations.tools.deleteFile.done': 'File eliminato',
+ 'conversations.tools.updateFileAccess.active': "Aggiornamento dell'accesso al file",
+ 'conversations.tools.updateFileAccess.done': 'Accesso al file aggiornato',
+ 'conversations.tools.deploySite.active': 'Distribuzione del sito',
+ 'conversations.tools.deploySite.done': 'Sito distribuito',
+ 'conversations.tools.checkHosting.active': "Verifica dell'hosting",
+ 'conversations.tools.checkHosting.done': 'Hosting verificato',
+ 'conversations.tools.updateHosting.active': "Aggiornamento dell'hosting",
+ 'conversations.tools.updateHosting.done': 'Hosting aggiornato',
+ 'conversations.tools.rollBackDeployment.active': 'Ripristino della distribuzione',
+ 'conversations.tools.rollBackDeployment.done': 'Distribuzione ripristinata',
+ 'conversations.tools.checkWallet.active': 'Verifica del portafoglio',
+ 'conversations.tools.checkWallet.done': 'Portafoglio verificato',
+ 'conversations.tools.prepareTransfer.active': 'Preparazione del trasferimento',
+ 'conversations.tools.prepareTransfer.done': 'Trasferimento preparato',
+ 'conversations.tools.checkTransaction.active': 'Verifica della transazione',
+ 'conversations.tools.checkTransaction.done': 'Transazione verificata',
+ 'conversations.tools.getSwapQuote.active': 'Richiesta del preventivo di scambio',
+ 'conversations.tools.getSwapQuote.done': 'Preventivo di scambio ottenuto',
+ 'conversations.tools.swapTokens.active': 'Scambio di token',
+ 'conversations.tools.swapTokens.done': 'Token scambiati',
+ 'conversations.tools.getBridgeQuote.active': 'Richiesta del preventivo di bridge',
+ 'conversations.tools.getBridgeQuote.done': 'Preventivo di bridge ottenuto',
+ 'conversations.tools.bridgeTokens.active': 'Trasferimento di token tramite bridge',
+ 'conversations.tools.bridgeTokens.done': 'Token trasferiti tramite bridge',
+ 'conversations.tools.callDapp.active': "Chiamata al contratto dell'app",
+ 'conversations.tools.callDapp.done': "Contratto dell'app chiamato",
+ 'conversations.tools.useSkill.active': 'Uso della skill',
+ 'conversations.tools.useSkill.done': 'Skill utilizzata',
+ 'conversations.tools.searchSkills.active': 'Ricerca di skill',
+ 'conversations.tools.searchSkills.done': 'Skill cercate',
+ 'conversations.tools.checkSkills.active': 'Verifica delle skill',
+ 'conversations.tools.checkSkills.done': 'Skill verificate',
+ 'conversations.tools.installSkill.active': 'Installazione della skill',
+ 'conversations.tools.installSkill.done': 'Skill installata',
+ 'conversations.tools.removeSkill.active': 'Rimozione della skill',
+ 'conversations.tools.removeSkill.done': 'Skill rimossa',
+ 'conversations.tools.createSkill.active': 'Creazione della skill',
+ 'conversations.tools.createSkill.done': 'Skill creata',
+ 'conversations.tools.runWorkflow.active': 'Esecuzione del flusso di lavoro',
+ 'conversations.tools.runWorkflow.done': 'Flusso di lavoro eseguito',
+ 'conversations.tools.waitForWorkflow.active': 'Attesa del flusso di lavoro',
+ 'conversations.tools.waitForWorkflow.done': 'Attesa del flusso di lavoro terminata',
+ 'conversations.tools.designWorkflow.active': 'Progettazione del flusso di lavoro',
+ 'conversations.tools.designWorkflow.done': 'Flusso di lavoro progettato',
+ 'conversations.tools.saveWorkflow.active': 'Salvataggio del flusso di lavoro',
+ 'conversations.tools.saveWorkflow.done': 'Flusso di lavoro salvato',
+ 'conversations.tools.validateWorkflow.active': 'Convalida del flusso di lavoro',
+ 'conversations.tools.validateWorkflow.done': 'Flusso di lavoro convalidato',
+ 'conversations.tools.testWorkflow.active': 'Test del flusso di lavoro',
+ 'conversations.tools.testWorkflow.done': 'Flusso di lavoro testato',
+ 'conversations.tools.checkWorkflows.active': 'Verifica dei flussi di lavoro',
+ 'conversations.tools.checkWorkflows.done': 'Flussi di lavoro verificati',
+ 'conversations.tools.cancelWorkflow.active': "Annullamento dell'esecuzione del flusso di lavoro",
+ 'conversations.tools.cancelWorkflow.done': 'Esecuzione del flusso di lavoro annullata',
+ 'conversations.tools.suggestWorkflows.active': 'Suggerimento di flussi di lavoro',
+ 'conversations.tools.suggestWorkflows.done': 'Flussi di lavoro suggeriti',
+ 'conversations.tools.checkSettings.active': 'Verifica delle impostazioni',
+ 'conversations.tools.checkSettings.done': 'Impostazioni verificate',
+ 'conversations.tools.checkSecurity.active': 'Verifica della sicurezza',
+ 'conversations.tools.checkSecurity.done': 'Sicurezza verificata',
+ 'conversations.tools.runDiagnostics.active': 'Esecuzione della diagnostica',
+ 'conversations.tools.runDiagnostics.done': 'Diagnostica eseguita',
+ 'conversations.tools.checkUsageCosts.active': 'Verifica dei costi di utilizzo',
+ 'conversations.tools.checkUsageCosts.done': 'Costi di utilizzo verificati',
+ 'conversations.tools.manageService.active': 'Gestione del servizio in background',
+ 'conversations.tools.manageService.done': 'Servizio in background gestito',
+ 'conversations.tools.readPersona.active': 'Lettura della persona',
+ 'conversations.tools.readPersona.done': 'Persona letta',
+ 'conversations.tools.updatePersona.active': 'Aggiornamento della persona',
+ 'conversations.tools.updatePersona.done': 'Persona aggiornata',
+ 'conversations.tools.setUpWorkspace.active': "Configurazione dell'area di lavoro",
+ 'conversations.tools.setUpWorkspace.done': 'Area di lavoro configurata',
+ 'conversations.tools.checkArtifacts.active': 'Verifica degli artefatti',
+ 'conversations.tools.checkArtifacts.done': 'Artefatti verificati',
+ 'conversations.tools.deleteArtifact.active': "Eliminazione dell'artefatto",
+ 'conversations.tools.deleteArtifact.done': 'Artefatto eliminato',
'conversations.subagent.noOutput': 'Nessun output restituito',
'conversations.subagent.close': 'Chiudi',
'conversations.subagent.cancel': 'Annulla attività',
diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts
index e8da4ed5255..0681c9670c6 100644
--- a/app/src/lib/i18n/ko.ts
+++ b/app/src/lib/i18n/ko.ts
@@ -3282,6 +3282,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': '아직 출력이 없습니다',
'conversations.subagent.input': '입력',
'conversations.subagent.output': '출력',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count}단계',
+ 'conversations.tools.steps.other': '{count}단계',
+ 'conversations.tools.working': '작업 중',
+ 'conversations.tools.noOutput': '출력 없음',
+ 'conversations.tools.delegatedTo': '{agent}에게 위임함',
+ 'conversations.tools.openInBrowser': '브라우저에서 열기',
+ 'conversations.tools.status.running': '실행 중',
+ 'conversations.tools.status.done': '완료',
+ 'conversations.tools.status.failed': '실패',
+ 'conversations.tools.status.cancelled': '취소됨',
+ 'conversations.tools.status.awaiting': '입력 대기 중',
+ 'conversations.tools.search.searching': '검색 중',
+ 'conversations.tools.search.none': '결과 없음',
+ 'conversations.tools.search.found.one': '결과 {count}개 찾음',
+ 'conversations.tools.search.found.other': '결과 {count}개 찾음',
+ 'conversations.tools.search.via': '{provider} 사용',
+ 'conversations.tools.readFile.active': '파일 읽는 중',
+ 'conversations.tools.readFile.done': '파일 읽음',
+ 'conversations.tools.writeFile.active': '파일 쓰는 중',
+ 'conversations.tools.writeFile.done': '파일 작성함',
+ 'conversations.tools.editFile.active': '파일 편집 중',
+ 'conversations.tools.editFile.done': '파일 편집함',
+ 'conversations.tools.applyEdits.active': '변경 사항 적용 중',
+ 'conversations.tools.applyEdits.done': '변경 사항 적용함',
+ 'conversations.tools.searchCode.active': '코드 검색 중',
+ 'conversations.tools.searchCode.done': '코드 검색함',
+ 'conversations.tools.findFiles.active': '파일 찾는 중',
+ 'conversations.tools.findFiles.done': '파일 찾음',
+ 'conversations.tools.listFolder.active': '폴더 목록 확인 중',
+ 'conversations.tools.listFolder.done': '폴더 목록 확인함',
+ 'conversations.tools.exportCsv.active': 'CSV 내보내는 중',
+ 'conversations.tools.exportCsv.done': 'CSV 내보냄',
+ 'conversations.tools.updateMemoryNotes.active': '메모리 노트 업데이트 중',
+ 'conversations.tools.updateMemoryNotes.done': '메모리 노트 업데이트함',
+ 'conversations.tools.runGit.active': 'git 실행 중',
+ 'conversations.tools.runGit.done': 'git 실행함',
+ 'conversations.tools.readChanges.active': '변경 사항 읽는 중',
+ 'conversations.tools.readChanges.done': '변경 사항 읽음',
+ 'conversations.tools.runLinter.active': '린터 실행 중',
+ 'conversations.tools.runLinter.done': '린터 실행함',
+ 'conversations.tools.runTests.active': '테스트 실행 중',
+ 'conversations.tools.runTests.done': '테스트 실행함',
+ 'conversations.tools.analyzeCode.active': '코드 분석 중',
+ 'conversations.tools.analyzeCode.done': '코드 분석함',
+ 'conversations.tools.insertRecord.active': '레코드 추가 중',
+ 'conversations.tools.insertRecord.done': '레코드 추가함',
+ 'conversations.tools.runCommand.active': '명령 실행 중',
+ 'conversations.tools.runCommand.done': '명령 실행함',
+ 'conversations.tools.runCode.active': '코드 실행 중',
+ 'conversations.tools.runCode.done': '코드 실행함',
+ 'conversations.tools.runPackageManager.active': 'npm 실행 중',
+ 'conversations.tools.runPackageManager.done': 'npm 실행함',
+ 'conversations.tools.checkInstalledTools.active': '설치된 도구 확인 중',
+ 'conversations.tools.checkInstalledTools.done': '설치된 도구 확인함',
+ 'conversations.tools.installTool.active': '도구 설치 중',
+ 'conversations.tools.installTool.done': '도구 설치함',
+ 'conversations.tools.checkTime.active': '시간 확인 중',
+ 'conversations.tools.checkTime.done': '시간 확인함',
+ 'conversations.tools.resolveDate.active': '날짜 계산 중',
+ 'conversations.tools.resolveDate.done': '날짜 계산함',
+ 'conversations.tools.retrieveOutput.active': '전체 출력 가져오는 중',
+ 'conversations.tools.retrieveOutput.done': '전체 출력 가져옴',
+ 'conversations.tools.reviewWorkspace.active': '작업 공간 검토 중',
+ 'conversations.tools.reviewWorkspace.done': '작업 공간 검토함',
+ 'conversations.tools.configureProxy.active': '프록시 구성 중',
+ 'conversations.tools.configureProxy.done': '프록시 구성함',
+ 'conversations.tools.checkUpdates.active': '업데이트 확인 중',
+ 'conversations.tools.checkUpdates.done': '업데이트 확인함',
+ 'conversations.tools.installUpdate.active': '업데이트 설치 중',
+ 'conversations.tools.installUpdate.done': '업데이트 설치함',
+ 'conversations.tools.sendNotification.active': '알림 보내는 중',
+ 'conversations.tools.sendNotification.done': '알림 보냄',
+ 'conversations.tools.reviewToolUsage.active': '도구 사용 내역 검토 중',
+ 'conversations.tools.reviewToolUsage.done': '도구 사용 내역 검토함',
+ 'conversations.tools.typeKeys.active': '입력 중',
+ 'conversations.tools.typeKeys.done': '입력함',
+ 'conversations.tools.click.active': '클릭 중',
+ 'conversations.tools.click.done': '클릭함',
+ 'conversations.tools.searchWeb.active': '웹 검색 중',
+ 'conversations.tools.searchWeb.done': '웹 검색함',
+ 'conversations.tools.searchNews.active': '뉴스 검색 중',
+ 'conversations.tools.searchNews.done': '뉴스 검색함',
+ 'conversations.tools.searchImages.active': '이미지 검색 중',
+ 'conversations.tools.searchImages.done': '이미지 검색함',
+ 'conversations.tools.searchVideos.active': '동영상 검색 중',
+ 'conversations.tools.searchVideos.done': '동영상 검색함',
+ 'conversations.tools.findSimilarPages.active': '유사한 페이지 찾는 중',
+ 'conversations.tools.findSimilarPages.done': '유사한 페이지 찾음',
+ 'conversations.tools.readPages.active': '페이지 읽는 중',
+ 'conversations.tools.readPages.done': '페이지 읽음',
+ 'conversations.tools.readWebpage.active': '웹페이지 읽는 중',
+ 'conversations.tools.readWebpage.done': '웹페이지 읽음',
+ 'conversations.tools.research.active': '조사 중',
+ 'conversations.tools.research.done': '조사함',
+ 'conversations.tools.enrichData.active': '데이터 보강 중',
+ 'conversations.tools.enrichData.done': '데이터 보강함',
+ 'conversations.tools.buildDataset.active': '데이터셋 구축 중',
+ 'conversations.tools.buildDataset.done': '데이터셋 구축함',
+ 'conversations.tools.askTheWeb.active': '웹에 질문하는 중',
+ 'conversations.tools.askTheWeb.done': '웹에 질문함',
+ 'conversations.tools.browseForYou.active': '대신 탐색하는 중',
+ 'conversations.tools.browseForYou.done': '대신 탐색함',
+ 'conversations.tools.callApi.active': 'API 호출 중',
+ 'conversations.tools.callApi.done': 'API 호출함',
+ 'conversations.tools.downloadFile.active': '파일 다운로드 중',
+ 'conversations.tools.downloadFile.done': '파일 다운로드함',
+ 'conversations.tools.makePaidRequest.active': '유료 요청 보내는 중',
+ 'conversations.tools.makePaidRequest.done': '유료 요청 보냄',
+ 'conversations.tools.searchDocs.active': '문서 검색 중',
+ 'conversations.tools.searchDocs.done': '문서 검색함',
+ 'conversations.tools.readDocs.active': '문서 읽는 중',
+ 'conversations.tools.readDocs.done': '문서 읽음',
+ 'conversations.tools.useBrowser.active': '브라우저 사용 중',
+ 'conversations.tools.useBrowser.done': '브라우저 사용함',
+ 'conversations.tools.openPage.active': '페이지 여는 중',
+ 'conversations.tools.openPage.done': '페이지 열었음',
+ 'conversations.tools.navigate.active': '이동 중',
+ 'conversations.tools.navigate.done': '이동함',
+ 'conversations.tools.takeScreenshot.active': '스크린샷 찍는 중',
+ 'conversations.tools.takeScreenshot.done': '스크린샷 찍음',
+ 'conversations.tools.scrollPage.active': '스크롤 중',
+ 'conversations.tools.scrollPage.done': '스크롤함',
+ 'conversations.tools.readPage.active': '페이지 읽는 중',
+ 'conversations.tools.readPage.done': '페이지 읽음',
+ 'conversations.tools.analyzeImage.active': '이미지 분석 중',
+ 'conversations.tools.analyzeImage.done': '이미지 분석함',
+ 'conversations.tools.generateImage.active': '이미지 생성 중',
+ 'conversations.tools.generateImage.done': '이미지 생성함',
+ 'conversations.tools.generateVideo.active': '동영상 생성 중',
+ 'conversations.tools.generateVideo.done': '동영상 생성함',
+ 'conversations.tools.checkMediaModels.active': '미디어 모델 확인 중',
+ 'conversations.tools.checkMediaModels.done': '미디어 모델 확인함',
+ 'conversations.tools.createDocument.active': '문서 만드는 중',
+ 'conversations.tools.createDocument.done': '문서 만듦',
+ 'conversations.tools.createPresentation.active': '프레젠테이션 만드는 중',
+ 'conversations.tools.createPresentation.done': '프레젠테이션 만듦',
+ 'conversations.tools.generatePodcast.active': '팟캐스트 생성 중',
+ 'conversations.tools.generatePodcast.done': '팟캐스트 생성함',
+ 'conversations.tools.emailPodcast.active': '팟캐스트 이메일 보내는 중',
+ 'conversations.tools.emailPodcast.done': '팟캐스트 이메일 보냄',
+ 'conversations.tools.createAndEmailPodcast.active': '팟캐스트 만들어 이메일 보내는 중',
+ 'conversations.tools.createAndEmailPodcast.done': '팟캐스트 만들어 이메일 보냄',
+ 'conversations.tools.recallMemories.active': '기억 떠올리는 중',
+ 'conversations.tools.recallMemories.done': '기억 떠올림',
+ 'conversations.tools.saveToMemory.active': '메모리에 저장 중',
+ 'conversations.tools.saveToMemory.done': '메모리에 저장함',
+ 'conversations.tools.forgetMemory.active': '기억 삭제 중',
+ 'conversations.tools.forgetMemory.done': '기억 삭제함',
+ 'conversations.tools.searchMemory.active': '메모리 검색 중',
+ 'conversations.tools.searchMemory.done': '메모리 검색함',
+ 'conversations.tools.inspectMemory.active': '메모리 살펴보는 중',
+ 'conversations.tools.inspectMemory.done': '메모리 살펴봄',
+ 'conversations.tools.exploreMemory.active': '메모리 탐색 중',
+ 'conversations.tools.exploreMemory.done': '메모리 탐색함',
+ 'conversations.tools.saveDocumentToMemory.active': '문서를 메모리에 저장 중',
+ 'conversations.tools.saveDocumentToMemory.done': '문서를 메모리에 저장함',
+ 'conversations.tools.updateGoals.active': '목표 업데이트 중',
+ 'conversations.tools.updateGoals.done': '목표 업데이트함',
+ 'conversations.tools.reviewGoals.active': '목표 검토 중',
+ 'conversations.tools.reviewGoals.done': '목표 검토함',
+ 'conversations.tools.savePreference.active': '선호 설정 저장 중',
+ 'conversations.tools.savePreference.done': '선호 설정 저장함',
+ 'conversations.tools.reviewLearnings.active': '배운 내용 검토 중',
+ 'conversations.tools.reviewLearnings.done': '배운 내용 검토함',
+ 'conversations.tools.updateLearnings.active': '배운 내용 업데이트 중',
+ 'conversations.tools.updateLearnings.done': '배운 내용 업데이트함',
+ 'conversations.tools.delegateTask.active': '작업 위임 중',
+ 'conversations.tools.delegateTask.done': '작업 위임함',
+ 'conversations.tools.runAgentsInParallel.active': '에이전트 병렬 실행 중',
+ 'conversations.tools.runAgentsInParallel.done': '에이전트 병렬 실행함',
+ 'conversations.tools.messageAgent.active': '에이전트에게 메시지 보내는 중',
+ 'conversations.tools.messageAgent.done': '에이전트에게 메시지 보냄',
+ 'conversations.tools.waitForAgent.active': '에이전트 기다리는 중',
+ 'conversations.tools.waitForAgent.done': '에이전트 기다림',
+ 'conversations.tools.wait.active': '대기 중',
+ 'conversations.tools.wait.done': '대기함',
+ 'conversations.tools.closeAgent.active': '에이전트 닫는 중',
+ 'conversations.tools.closeAgent.done': '에이전트 닫음',
+ 'conversations.tools.checkAgents.active': '에이전트 확인 중',
+ 'conversations.tools.checkAgents.done': '에이전트 확인함',
+ 'conversations.tools.askQuestion.active': '질문하는 중',
+ 'conversations.tools.askQuestion.done': '질문함',
+ 'conversations.tools.prepareContext.active': '컨텍스트 준비 중',
+ 'conversations.tools.prepareContext.done': '컨텍스트 준비함',
+ 'conversations.tools.extractDetails.active': '세부 정보 추출 중',
+ 'conversations.tools.extractDetails.done': '세부 정보 추출함',
+ 'conversations.tools.planNextSteps.active': '다음 단계 계획 중',
+ 'conversations.tools.planNextSteps.done': '다음 단계 계획함',
+ 'conversations.tools.reviewWork.active': '작업 검토 중',
+ 'conversations.tools.reviewWork.done': '작업 검토함',
+ 'conversations.tools.scoutContext.active': '컨텍스트 파악 중',
+ 'conversations.tools.scoutContext.done': '컨텍스트 파악함',
+ 'conversations.tools.useTools.active': '도구 사용 중',
+ 'conversations.tools.useTools.done': '도구 사용함',
+ 'conversations.tools.checkConnectedApp.active': '연결된 앱 확인 중',
+ 'conversations.tools.checkConnectedApp.done': '연결된 앱 확인함',
+ 'conversations.tools.updateTodos.active': '할 일 목록 업데이트 중',
+ 'conversations.tools.updateTodos.done': '할 일 목록 업데이트함',
+ 'conversations.tools.requestPlanReview.active': '계획 검토 요청 중',
+ 'conversations.tools.requestPlanReview.done': '계획 검토 요청함',
+ 'conversations.tools.finishPlan.active': '계획 마무리 중',
+ 'conversations.tools.finishPlan.done': '계획 마무리함',
+ 'conversations.tools.setGoal.active': '목표 설정 중',
+ 'conversations.tools.setGoal.done': '목표 설정함',
+ 'conversations.tools.checkGoal.active': '목표 확인 중',
+ 'conversations.tools.checkGoal.done': '목표 확인함',
+ 'conversations.tools.completeGoal.active': '목표 완료 처리 중',
+ 'conversations.tools.completeGoal.done': '목표 완료함',
+ 'conversations.tools.scheduleTask.active': '작업 예약 중',
+ 'conversations.tools.scheduleTask.done': '작업 예약함',
+ 'conversations.tools.checkSchedules.active': '일정 확인 중',
+ 'conversations.tools.checkSchedules.done': '일정 확인함',
+ 'conversations.tools.updateSchedule.active': '예약된 작업 업데이트 중',
+ 'conversations.tools.updateSchedule.done': '예약된 작업 업데이트함',
+ 'conversations.tools.removeSchedule.active': '예약된 작업 삭제 중',
+ 'conversations.tools.removeSchedule.done': '예약된 작업 삭제함',
+ 'conversations.tools.runScheduledTask.active': '예약된 작업 실행 중',
+ 'conversations.tools.runScheduledTask.done': '예약된 작업 실행함',
+ 'conversations.tools.checkRunHistory.active': '실행 기록 확인 중',
+ 'conversations.tools.checkRunHistory.done': '실행 기록 확인함',
+ 'conversations.tools.useApp.active': '{app} 사용 중',
+ 'conversations.tools.useApp.done': '{app} 사용함',
+ 'conversations.tools.checkAvailableApps.active': '사용 가능한 앱 확인 중',
+ 'conversations.tools.checkAvailableApps.done': '사용 가능한 앱 확인함',
+ 'conversations.tools.checkConnections.active': '연결 확인 중',
+ 'conversations.tools.checkConnections.done': '연결 확인함',
+ 'conversations.tools.connectApp.active': '앱 연결 중',
+ 'conversations.tools.connectApp.done': '앱 연결함',
+ 'conversations.tools.authorizeApp.active': '앱 승인 중',
+ 'conversations.tools.authorizeApp.done': '앱 승인함',
+ 'conversations.tools.findAppActions.active': '앱 작업 찾는 중',
+ 'conversations.tools.findAppActions.done': '앱 작업 찾음',
+ 'conversations.tools.runAppAction.active': '앱 작업 실행 중',
+ 'conversations.tools.runAppAction.done': '앱 작업 실행함',
+ 'conversations.tools.findTools.active': '도구 찾는 중',
+ 'conversations.tools.findTools.done': '도구 찾음',
+ 'conversations.tools.useTool.active': '{tool} 사용 중',
+ 'conversations.tools.useTool.done': '{tool} 사용함',
+ 'conversations.tools.unsubscribe.active': '구독 취소 중',
+ 'conversations.tools.unsubscribe.done': '구독 취소함',
+ 'conversations.tools.searchPlaces.active': '장소 검색 중',
+ 'conversations.tools.searchPlaces.done': '장소 검색함',
+ 'conversations.tools.lookUpPlace.active': '장소 조회 중',
+ 'conversations.tools.lookUpPlace.done': '장소 조회함',
+ 'conversations.tools.checkMarkets.active': '시장 확인 중',
+ 'conversations.tools.checkMarkets.done': '시장 확인함',
+ 'conversations.tools.placeCall.active': '전화 거는 중',
+ 'conversations.tools.placeCall.done': '전화 걸었음',
+ 'conversations.tools.checkTaskSources.active': '작업 소스 확인 중',
+ 'conversations.tools.checkTaskSources.done': '작업 소스 확인함',
+ 'conversations.tools.updateTaskSources.active': '작업 소스 업데이트 중',
+ 'conversations.tools.updateTaskSources.done': '작업 소스 업데이트함',
+ 'conversations.tools.fetchTasks.active': '작업 가져오는 중',
+ 'conversations.tools.fetchTasks.done': '작업 가져옴',
+ 'conversations.tools.checkMcpServers.active': 'MCP 서버 확인 중',
+ 'conversations.tools.checkMcpServers.done': 'MCP 서버 확인함',
+ 'conversations.tools.checkMcpTools.active': 'MCP 도구 확인 중',
+ 'conversations.tools.checkMcpTools.done': 'MCP 도구 확인함',
+ 'conversations.tools.callMcpTool.active': '{tool} 호출 중',
+ 'conversations.tools.callMcpTool.done': '{tool} 호출함',
+ 'conversations.tools.searchMcpServers.active': 'MCP 서버 검색 중',
+ 'conversations.tools.searchMcpServers.done': 'MCP 서버 검색함',
+ 'conversations.tools.connectMcpServer.active': 'MCP 서버 연결 중',
+ 'conversations.tools.connectMcpServer.done': 'MCP 서버 연결함',
+ 'conversations.tools.disconnectMcpServer.active': 'MCP 서버 연결 해제 중',
+ 'conversations.tools.disconnectMcpServer.done': 'MCP 서버 연결 해제함',
+ 'conversations.tools.removeMcpServer.active': 'MCP 서버 삭제 중',
+ 'conversations.tools.removeMcpServer.done': 'MCP 서버 삭제함',
+ 'conversations.tools.uploadFile.active': '파일 업로드 중',
+ 'conversations.tools.uploadFile.done': '파일 업로드함',
+ 'conversations.tools.listStoredFiles.active': '저장된 파일 목록 확인 중',
+ 'conversations.tools.listStoredFiles.done': '저장된 파일 목록 확인함',
+ 'conversations.tools.createShareLink.active': '공유 링크 만드는 중',
+ 'conversations.tools.createShareLink.done': '공유 링크 만듦',
+ 'conversations.tools.deleteFile.active': '파일 삭제 중',
+ 'conversations.tools.deleteFile.done': '파일 삭제함',
+ 'conversations.tools.updateFileAccess.active': '파일 접근 권한 업데이트 중',
+ 'conversations.tools.updateFileAccess.done': '파일 접근 권한 업데이트함',
+ 'conversations.tools.deploySite.active': '사이트 배포 중',
+ 'conversations.tools.deploySite.done': '사이트 배포함',
+ 'conversations.tools.checkHosting.active': '호스팅 확인 중',
+ 'conversations.tools.checkHosting.done': '호스팅 확인함',
+ 'conversations.tools.updateHosting.active': '호스팅 업데이트 중',
+ 'conversations.tools.updateHosting.done': '호스팅 업데이트함',
+ 'conversations.tools.rollBackDeployment.active': '배포 롤백 중',
+ 'conversations.tools.rollBackDeployment.done': '배포 롤백함',
+ 'conversations.tools.checkWallet.active': '지갑 확인 중',
+ 'conversations.tools.checkWallet.done': '지갑 확인함',
+ 'conversations.tools.prepareTransfer.active': '송금 준비 중',
+ 'conversations.tools.prepareTransfer.done': '송금 준비함',
+ 'conversations.tools.checkTransaction.active': '거래 확인 중',
+ 'conversations.tools.checkTransaction.done': '거래 확인함',
+ 'conversations.tools.getSwapQuote.active': '스왑 견적 가져오는 중',
+ 'conversations.tools.getSwapQuote.done': '스왑 견적 가져옴',
+ 'conversations.tools.swapTokens.active': '토큰 스왑 중',
+ 'conversations.tools.swapTokens.done': '토큰 스왑함',
+ 'conversations.tools.getBridgeQuote.active': '브리지 견적 가져오는 중',
+ 'conversations.tools.getBridgeQuote.done': '브리지 견적 가져옴',
+ 'conversations.tools.bridgeTokens.active': '토큰 브리지 중',
+ 'conversations.tools.bridgeTokens.done': '토큰 브리지함',
+ 'conversations.tools.callDapp.active': '앱 컨트랙트 호출 중',
+ 'conversations.tools.callDapp.done': '앱 컨트랙트 호출함',
+ 'conversations.tools.useSkill.active': '스킬 사용 중',
+ 'conversations.tools.useSkill.done': '스킬 사용함',
+ 'conversations.tools.searchSkills.active': '스킬 검색 중',
+ 'conversations.tools.searchSkills.done': '스킬 검색함',
+ 'conversations.tools.checkSkills.active': '스킬 확인 중',
+ 'conversations.tools.checkSkills.done': '스킬 확인함',
+ 'conversations.tools.installSkill.active': '스킬 설치 중',
+ 'conversations.tools.installSkill.done': '스킬 설치함',
+ 'conversations.tools.removeSkill.active': '스킬 삭제 중',
+ 'conversations.tools.removeSkill.done': '스킬 삭제함',
+ 'conversations.tools.createSkill.active': '스킬 만드는 중',
+ 'conversations.tools.createSkill.done': '스킬 만듦',
+ 'conversations.tools.runWorkflow.active': '워크플로 실행 중',
+ 'conversations.tools.runWorkflow.done': '워크플로 실행함',
+ 'conversations.tools.waitForWorkflow.active': '워크플로 기다리는 중',
+ 'conversations.tools.waitForWorkflow.done': '워크플로 기다림',
+ 'conversations.tools.designWorkflow.active': '워크플로 설계 중',
+ 'conversations.tools.designWorkflow.done': '워크플로 설계함',
+ 'conversations.tools.saveWorkflow.active': '워크플로 저장 중',
+ 'conversations.tools.saveWorkflow.done': '워크플로 저장함',
+ 'conversations.tools.validateWorkflow.active': '워크플로 검증 중',
+ 'conversations.tools.validateWorkflow.done': '워크플로 검증함',
+ 'conversations.tools.testWorkflow.active': '워크플로 테스트 중',
+ 'conversations.tools.testWorkflow.done': '워크플로 테스트함',
+ 'conversations.tools.checkWorkflows.active': '워크플로 확인 중',
+ 'conversations.tools.checkWorkflows.done': '워크플로 확인함',
+ 'conversations.tools.cancelWorkflow.active': '워크플로 실행 취소 중',
+ 'conversations.tools.cancelWorkflow.done': '워크플로 실행 취소함',
+ 'conversations.tools.suggestWorkflows.active': '워크플로 제안 중',
+ 'conversations.tools.suggestWorkflows.done': '워크플로 제안함',
+ 'conversations.tools.checkSettings.active': '설정 확인 중',
+ 'conversations.tools.checkSettings.done': '설정 확인함',
+ 'conversations.tools.checkSecurity.active': '보안 확인 중',
+ 'conversations.tools.checkSecurity.done': '보안 확인함',
+ 'conversations.tools.runDiagnostics.active': '진단 실행 중',
+ 'conversations.tools.runDiagnostics.done': '진단 실행함',
+ 'conversations.tools.checkUsageCosts.active': '사용 비용 확인 중',
+ 'conversations.tools.checkUsageCosts.done': '사용 비용 확인함',
+ 'conversations.tools.manageService.active': '백그라운드 서비스 관리 중',
+ 'conversations.tools.manageService.done': '백그라운드 서비스 관리함',
+ 'conversations.tools.readPersona.active': '페르소나 읽는 중',
+ 'conversations.tools.readPersona.done': '페르소나 읽음',
+ 'conversations.tools.updatePersona.active': '페르소나 업데이트 중',
+ 'conversations.tools.updatePersona.done': '페르소나 업데이트함',
+ 'conversations.tools.setUpWorkspace.active': '작업 공간 설정 중',
+ 'conversations.tools.setUpWorkspace.done': '작업 공간 설정함',
+ 'conversations.tools.checkArtifacts.active': '아티팩트 확인 중',
+ 'conversations.tools.checkArtifacts.done': '아티팩트 확인함',
+ 'conversations.tools.deleteArtifact.active': '아티팩트 삭제 중',
+ 'conversations.tools.deleteArtifact.done': '아티팩트 삭제함',
'conversations.subagent.noOutput': '반환된 출력 없음',
'conversations.subagent.close': '닫기',
'conversations.subagent.cancel': '작업 취소',
diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts
index cdf3c79fd49..724a910ea69 100644
--- a/app/src/lib/i18n/pl.ts
+++ b/app/src/lib/i18n/pl.ts
@@ -3356,6 +3356,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'Brak wyników',
'conversations.subagent.input': 'Wejście',
'conversations.subagent.output': 'Wyjście',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} krok',
+ 'conversations.tools.steps.other': 'Kroki: {count}',
+ 'conversations.tools.working': 'Pracuje',
+ 'conversations.tools.noOutput': 'Brak wyniku',
+ 'conversations.tools.delegatedTo': 'Przekazano do {agent}',
+ 'conversations.tools.openInBrowser': 'Otwórz w przeglądarce',
+ 'conversations.tools.status.running': 'w toku',
+ 'conversations.tools.status.done': 'gotowe',
+ 'conversations.tools.status.failed': 'niepowodzenie',
+ 'conversations.tools.status.cancelled': 'anulowano',
+ 'conversations.tools.status.awaiting': 'czeka na dane',
+ 'conversations.tools.search.searching': 'Wyszukiwanie',
+ 'conversations.tools.search.none': 'Brak wyników',
+ 'conversations.tools.search.found.one': 'Znaleziono {count} wynik',
+ 'conversations.tools.search.found.other': 'Znalezione wyniki: {count}',
+ 'conversations.tools.search.via': 'przez {provider}',
+ 'conversations.tools.readFile.active': 'Czytanie pliku',
+ 'conversations.tools.readFile.done': 'Odczytano plik',
+ 'conversations.tools.writeFile.active': 'Zapisywanie pliku',
+ 'conversations.tools.writeFile.done': 'Zapisano plik',
+ 'conversations.tools.editFile.active': 'Edytowanie pliku',
+ 'conversations.tools.editFile.done': 'Edytowano plik',
+ 'conversations.tools.applyEdits.active': 'Wprowadzanie zmian',
+ 'conversations.tools.applyEdits.done': 'Wprowadzono zmiany',
+ 'conversations.tools.searchCode.active': 'Przeszukiwanie kodu',
+ 'conversations.tools.searchCode.done': 'Przeszukano kod',
+ 'conversations.tools.findFiles.active': 'Wyszukiwanie plików',
+ 'conversations.tools.findFiles.done': 'Znaleziono pliki',
+ 'conversations.tools.listFolder.active': 'Wyświetlanie folderu',
+ 'conversations.tools.listFolder.done': 'Wyświetlono folder',
+ 'conversations.tools.exportCsv.active': 'Eksportowanie CSV',
+ 'conversations.tools.exportCsv.done': 'Wyeksportowano CSV',
+ 'conversations.tools.updateMemoryNotes.active': 'Aktualizowanie notatek pamięci',
+ 'conversations.tools.updateMemoryNotes.done': 'Zaktualizowano notatki pamięci',
+ 'conversations.tools.runGit.active': 'Uruchamianie git',
+ 'conversations.tools.runGit.done': 'Uruchomiono git',
+ 'conversations.tools.readChanges.active': 'Czytanie zmian',
+ 'conversations.tools.readChanges.done': 'Odczytano zmiany',
+ 'conversations.tools.runLinter.active': 'Uruchamianie lintera',
+ 'conversations.tools.runLinter.done': 'Uruchomiono linter',
+ 'conversations.tools.runTests.active': 'Uruchamianie testów',
+ 'conversations.tools.runTests.done': 'Uruchomiono testy',
+ 'conversations.tools.analyzeCode.active': 'Analizowanie kodu',
+ 'conversations.tools.analyzeCode.done': 'Przeanalizowano kod',
+ 'conversations.tools.insertRecord.active': 'Wstawianie rekordu',
+ 'conversations.tools.insertRecord.done': 'Wstawiono rekord',
+ 'conversations.tools.runCommand.active': 'Wykonywanie polecenia',
+ 'conversations.tools.runCommand.done': 'Wykonano polecenie',
+ 'conversations.tools.runCode.active': 'Wykonywanie kodu',
+ 'conversations.tools.runCode.done': 'Wykonano kod',
+ 'conversations.tools.runPackageManager.active': 'Uruchamianie npm',
+ 'conversations.tools.runPackageManager.done': 'Uruchomiono npm',
+ 'conversations.tools.checkInstalledTools.active': 'Sprawdzanie zainstalowanych narzędzi',
+ 'conversations.tools.checkInstalledTools.done': 'Sprawdzono zainstalowane narzędzia',
+ 'conversations.tools.installTool.active': 'Instalowanie narzędzia',
+ 'conversations.tools.installTool.done': 'Zainstalowano narzędzie',
+ 'conversations.tools.checkTime.active': 'Sprawdzanie godziny',
+ 'conversations.tools.checkTime.done': 'Sprawdzono godzinę',
+ 'conversations.tools.resolveDate.active': 'Ustalanie daty',
+ 'conversations.tools.resolveDate.done': 'Ustalono datę',
+ 'conversations.tools.retrieveOutput.active': 'Pobieranie pełnego wyniku',
+ 'conversations.tools.retrieveOutput.done': 'Pobrano pełny wynik',
+ 'conversations.tools.reviewWorkspace.active': 'Przeglądanie obszaru roboczego',
+ 'conversations.tools.reviewWorkspace.done': 'Przejrzano obszar roboczy',
+ 'conversations.tools.configureProxy.active': 'Konfigurowanie proxy',
+ 'conversations.tools.configureProxy.done': 'Skonfigurowano proxy',
+ 'conversations.tools.checkUpdates.active': 'Sprawdzanie aktualizacji',
+ 'conversations.tools.checkUpdates.done': 'Sprawdzono aktualizacje',
+ 'conversations.tools.installUpdate.active': 'Instalowanie aktualizacji',
+ 'conversations.tools.installUpdate.done': 'Zainstalowano aktualizację',
+ 'conversations.tools.sendNotification.active': 'Wysyłanie powiadomienia',
+ 'conversations.tools.sendNotification.done': 'Wysłano powiadomienie',
+ 'conversations.tools.reviewToolUsage.active': 'Przeglądanie użycia narzędzi',
+ 'conversations.tools.reviewToolUsage.done': 'Przejrzano użycie narzędzi',
+ 'conversations.tools.typeKeys.active': 'Wpisywanie',
+ 'conversations.tools.typeKeys.done': 'Wpisano',
+ 'conversations.tools.click.active': 'Klikanie',
+ 'conversations.tools.click.done': 'Kliknięto',
+ 'conversations.tools.searchWeb.active': 'Przeszukiwanie internetu',
+ 'conversations.tools.searchWeb.done': 'Przeszukano internet',
+ 'conversations.tools.searchNews.active': 'Wyszukiwanie wiadomości',
+ 'conversations.tools.searchNews.done': 'Wyszukano wiadomości',
+ 'conversations.tools.searchImages.active': 'Wyszukiwanie obrazów',
+ 'conversations.tools.searchImages.done': 'Wyszukano obrazy',
+ 'conversations.tools.searchVideos.active': 'Wyszukiwanie filmów',
+ 'conversations.tools.searchVideos.done': 'Wyszukano filmy',
+ 'conversations.tools.findSimilarPages.active': 'Szukanie podobnych stron',
+ 'conversations.tools.findSimilarPages.done': 'Znaleziono podobne strony',
+ 'conversations.tools.readPages.active': 'Czytanie stron',
+ 'conversations.tools.readPages.done': 'Odczytano strony',
+ 'conversations.tools.readWebpage.active': 'Czytanie strony internetowej',
+ 'conversations.tools.readWebpage.done': 'Odczytano stronę internetową',
+ 'conversations.tools.research.active': 'Badanie tematu',
+ 'conversations.tools.research.done': 'Zbadano temat',
+ 'conversations.tools.enrichData.active': 'Wzbogacanie danych',
+ 'conversations.tools.enrichData.done': 'Wzbogacono dane',
+ 'conversations.tools.buildDataset.active': 'Tworzenie zbioru danych',
+ 'conversations.tools.buildDataset.done': 'Utworzono zbiór danych',
+ 'conversations.tools.askTheWeb.active': 'Pytanie internetu',
+ 'conversations.tools.askTheWeb.done': 'Zapytano internet',
+ 'conversations.tools.browseForYou.active': 'Przeglądanie stron za Ciebie',
+ 'conversations.tools.browseForYou.done': 'Przejrzano strony za Ciebie',
+ 'conversations.tools.callApi.active': 'Wywoływanie API',
+ 'conversations.tools.callApi.done': 'Wywołano API',
+ 'conversations.tools.downloadFile.active': 'Pobieranie pliku',
+ 'conversations.tools.downloadFile.done': 'Pobrano plik',
+ 'conversations.tools.makePaidRequest.active': 'Wysyłanie płatnego żądania',
+ 'conversations.tools.makePaidRequest.done': 'Wysłano płatne żądanie',
+ 'conversations.tools.searchDocs.active': 'Przeszukiwanie dokumentacji',
+ 'conversations.tools.searchDocs.done': 'Przeszukano dokumentację',
+ 'conversations.tools.readDocs.active': 'Czytanie dokumentacji',
+ 'conversations.tools.readDocs.done': 'Odczytano dokumentację',
+ 'conversations.tools.useBrowser.active': 'Korzystanie z przeglądarki',
+ 'conversations.tools.useBrowser.done': 'Skorzystano z przeglądarki',
+ 'conversations.tools.openPage.active': 'Otwieranie strony',
+ 'conversations.tools.openPage.done': 'Otwarto stronę',
+ 'conversations.tools.navigate.active': 'Przechodzenie',
+ 'conversations.tools.navigate.done': 'Przejście zakończone',
+ 'conversations.tools.takeScreenshot.active': 'Robienie zrzutu ekranu',
+ 'conversations.tools.takeScreenshot.done': 'Zrobiono zrzut ekranu',
+ 'conversations.tools.scrollPage.active': 'Przewijanie',
+ 'conversations.tools.scrollPage.done': 'Przewinięto',
+ 'conversations.tools.readPage.active': 'Czytanie strony',
+ 'conversations.tools.readPage.done': 'Odczytano stronę',
+ 'conversations.tools.analyzeImage.active': 'Analizowanie obrazu',
+ 'conversations.tools.analyzeImage.done': 'Przeanalizowano obraz',
+ 'conversations.tools.generateImage.active': 'Generowanie obrazu',
+ 'conversations.tools.generateImage.done': 'Wygenerowano obraz',
+ 'conversations.tools.generateVideo.active': 'Generowanie filmu',
+ 'conversations.tools.generateVideo.done': 'Wygenerowano film',
+ 'conversations.tools.checkMediaModels.active': 'Sprawdzanie modeli multimediów',
+ 'conversations.tools.checkMediaModels.done': 'Sprawdzono modele multimediów',
+ 'conversations.tools.createDocument.active': 'Tworzenie dokumentu',
+ 'conversations.tools.createDocument.done': 'Utworzono dokument',
+ 'conversations.tools.createPresentation.active': 'Tworzenie prezentacji',
+ 'conversations.tools.createPresentation.done': 'Utworzono prezentację',
+ 'conversations.tools.generatePodcast.active': 'Generowanie podcastu',
+ 'conversations.tools.generatePodcast.done': 'Wygenerowano podcast',
+ 'conversations.tools.emailPodcast.active': 'Wysyłanie podcastu e-mailem',
+ 'conversations.tools.emailPodcast.done': 'Wysłano podcast e-mailem',
+ 'conversations.tools.createAndEmailPodcast.active': 'Tworzenie i wysyłanie podcastu e-mailem',
+ 'conversations.tools.createAndEmailPodcast.done': 'Utworzono i wysłano podcast e-mailem',
+ 'conversations.tools.recallMemories.active': 'Przywoływanie wspomnień',
+ 'conversations.tools.recallMemories.done': 'Przywołano wspomnienia',
+ 'conversations.tools.saveToMemory.active': 'Zapisywanie w pamięci',
+ 'conversations.tools.saveToMemory.done': 'Zapisano w pamięci',
+ 'conversations.tools.forgetMemory.active': 'Usuwanie wspomnienia',
+ 'conversations.tools.forgetMemory.done': 'Usunięto wspomnienie',
+ 'conversations.tools.searchMemory.active': 'Przeszukiwanie pamięci',
+ 'conversations.tools.searchMemory.done': 'Przeszukano pamięć',
+ 'conversations.tools.inspectMemory.active': 'Sprawdzanie pamięci',
+ 'conversations.tools.inspectMemory.done': 'Sprawdzono pamięć',
+ 'conversations.tools.exploreMemory.active': 'Eksplorowanie pamięci',
+ 'conversations.tools.exploreMemory.done': 'Przejrzano pamięć',
+ 'conversations.tools.saveDocumentToMemory.active': 'Zapisywanie dokumentu w pamięci',
+ 'conversations.tools.saveDocumentToMemory.done': 'Zapisano dokument w pamięci',
+ 'conversations.tools.updateGoals.active': 'Aktualizowanie celów',
+ 'conversations.tools.updateGoals.done': 'Zaktualizowano cele',
+ 'conversations.tools.reviewGoals.active': 'Przeglądanie celów',
+ 'conversations.tools.reviewGoals.done': 'Przejrzano cele',
+ 'conversations.tools.savePreference.active': 'Zapisywanie preferencji',
+ 'conversations.tools.savePreference.done': 'Zapisano preferencję',
+ 'conversations.tools.reviewLearnings.active': 'Przeglądanie wniosków',
+ 'conversations.tools.reviewLearnings.done': 'Przejrzano wnioski',
+ 'conversations.tools.updateLearnings.active': 'Aktualizowanie wniosków',
+ 'conversations.tools.updateLearnings.done': 'Zaktualizowano wnioski',
+ 'conversations.tools.delegateTask.active': 'Przekazywanie zadania',
+ 'conversations.tools.delegateTask.done': 'Przekazano zadanie',
+ 'conversations.tools.runAgentsInParallel.active': 'Równoległe uruchamianie agentów',
+ 'conversations.tools.runAgentsInParallel.done': 'Uruchomiono agentów równolegle',
+ 'conversations.tools.messageAgent.active': 'Wysyłanie wiadomości do agenta',
+ 'conversations.tools.messageAgent.done': 'Wysłano wiadomość do agenta',
+ 'conversations.tools.waitForAgent.active': 'Oczekiwanie na agenta',
+ 'conversations.tools.waitForAgent.done': 'Agent odpowiedział',
+ 'conversations.tools.wait.active': 'Oczekiwanie',
+ 'conversations.tools.wait.done': 'Zakończono oczekiwanie',
+ 'conversations.tools.closeAgent.active': 'Zamykanie agenta',
+ 'conversations.tools.closeAgent.done': 'Zamknięto agenta',
+ 'conversations.tools.checkAgents.active': 'Sprawdzanie agentów',
+ 'conversations.tools.checkAgents.done': 'Sprawdzono agentów',
+ 'conversations.tools.askQuestion.active': 'Zadawanie Ci pytania',
+ 'conversations.tools.askQuestion.done': 'Zadano Ci pytanie',
+ 'conversations.tools.prepareContext.active': 'Przygotowywanie kontekstu',
+ 'conversations.tools.prepareContext.done': 'Przygotowano kontekst',
+ 'conversations.tools.extractDetails.active': 'Wyodrębnianie szczegółów',
+ 'conversations.tools.extractDetails.done': 'Wyodrębniono szczegóły',
+ 'conversations.tools.planNextSteps.active': 'Planowanie kolejnych kroków',
+ 'conversations.tools.planNextSteps.done': 'Zaplanowano kolejne kroki',
+ 'conversations.tools.reviewWork.active': 'Przeglądanie pracy',
+ 'conversations.tools.reviewWork.done': 'Przejrzano pracę',
+ 'conversations.tools.scoutContext.active': 'Rozpoznawanie kontekstu',
+ 'conversations.tools.scoutContext.done': 'Rozpoznano kontekst',
+ 'conversations.tools.useTools.active': 'Korzystanie z narzędzi',
+ 'conversations.tools.useTools.done': 'Skorzystano z narzędzi',
+ 'conversations.tools.checkConnectedApp.active': 'Sprawdzanie połączonej aplikacji',
+ 'conversations.tools.checkConnectedApp.done': 'Sprawdzono połączoną aplikację',
+ 'conversations.tools.updateTodos.active': 'Aktualizowanie listy zadań',
+ 'conversations.tools.updateTodos.done': 'Zaktualizowano listę zadań',
+ 'conversations.tools.requestPlanReview.active': 'Prośba o przegląd planu',
+ 'conversations.tools.requestPlanReview.done': 'Poproszono o przegląd planu',
+ 'conversations.tools.finishPlan.active': 'Kończenie planu',
+ 'conversations.tools.finishPlan.done': 'Ukończono plan',
+ 'conversations.tools.setGoal.active': 'Ustawianie celu',
+ 'conversations.tools.setGoal.done': 'Ustawiono cel',
+ 'conversations.tools.checkGoal.active': 'Sprawdzanie celu',
+ 'conversations.tools.checkGoal.done': 'Sprawdzono cel',
+ 'conversations.tools.completeGoal.active': 'Realizowanie celu',
+ 'conversations.tools.completeGoal.done': 'Zrealizowano cel',
+ 'conversations.tools.scheduleTask.active': 'Planowanie zadania',
+ 'conversations.tools.scheduleTask.done': 'Zaplanowano zadanie',
+ 'conversations.tools.checkSchedules.active': 'Sprawdzanie harmonogramów',
+ 'conversations.tools.checkSchedules.done': 'Sprawdzono harmonogramy',
+ 'conversations.tools.updateSchedule.active': 'Aktualizowanie zaplanowanego zadania',
+ 'conversations.tools.updateSchedule.done': 'Zaktualizowano zaplanowane zadanie',
+ 'conversations.tools.removeSchedule.active': 'Usuwanie zaplanowanego zadania',
+ 'conversations.tools.removeSchedule.done': 'Usunięto zaplanowane zadanie',
+ 'conversations.tools.runScheduledTask.active': 'Uruchamianie zaplanowanego zadania',
+ 'conversations.tools.runScheduledTask.done': 'Uruchomiono zaplanowane zadanie',
+ 'conversations.tools.checkRunHistory.active': 'Sprawdzanie historii uruchomień',
+ 'conversations.tools.checkRunHistory.done': 'Sprawdzono historię uruchomień',
+ 'conversations.tools.useApp.active': 'Korzystanie z {app}',
+ 'conversations.tools.useApp.done': 'Skorzystano z {app}',
+ 'conversations.tools.checkAvailableApps.active': 'Sprawdzanie dostępnych aplikacji',
+ 'conversations.tools.checkAvailableApps.done': 'Sprawdzono dostępne aplikacje',
+ 'conversations.tools.checkConnections.active': 'Sprawdzanie Twoich połączeń',
+ 'conversations.tools.checkConnections.done': 'Sprawdzono Twoje połączenia',
+ 'conversations.tools.connectApp.active': 'Łączenie aplikacji',
+ 'conversations.tools.connectApp.done': 'Połączono aplikację',
+ 'conversations.tools.authorizeApp.active': 'Autoryzowanie aplikacji',
+ 'conversations.tools.authorizeApp.done': 'Autoryzowano aplikację',
+ 'conversations.tools.findAppActions.active': 'Szukanie akcji aplikacji',
+ 'conversations.tools.findAppActions.done': 'Znaleziono akcje aplikacji',
+ 'conversations.tools.runAppAction.active': 'Uruchamianie akcji aplikacji',
+ 'conversations.tools.runAppAction.done': 'Uruchomiono akcję aplikacji',
+ 'conversations.tools.findTools.active': 'Szukanie narzędzi',
+ 'conversations.tools.findTools.done': 'Znaleziono narzędzia',
+ 'conversations.tools.useTool.active': 'Korzystanie z {tool}',
+ 'conversations.tools.useTool.done': 'Skorzystano z {tool}',
+ 'conversations.tools.unsubscribe.active': 'Wypisywanie z subskrypcji',
+ 'conversations.tools.unsubscribe.done': 'Wypisano z subskrypcji',
+ 'conversations.tools.searchPlaces.active': 'Wyszukiwanie miejsc',
+ 'conversations.tools.searchPlaces.done': 'Wyszukano miejsca',
+ 'conversations.tools.lookUpPlace.active': 'Sprawdzanie miejsca',
+ 'conversations.tools.lookUpPlace.done': 'Sprawdzono miejsce',
+ 'conversations.tools.checkMarkets.active': 'Sprawdzanie rynków',
+ 'conversations.tools.checkMarkets.done': 'Sprawdzono rynki',
+ 'conversations.tools.placeCall.active': 'Wykonywanie połączenia',
+ 'conversations.tools.placeCall.done': 'Wykonano połączenie',
+ 'conversations.tools.checkTaskSources.active': 'Sprawdzanie źródeł zadań',
+ 'conversations.tools.checkTaskSources.done': 'Sprawdzono źródła zadań',
+ 'conversations.tools.updateTaskSources.active': 'Aktualizowanie źródeł zadań',
+ 'conversations.tools.updateTaskSources.done': 'Zaktualizowano źródła zadań',
+ 'conversations.tools.fetchTasks.active': 'Pobieranie zadań',
+ 'conversations.tools.fetchTasks.done': 'Pobrano zadania',
+ 'conversations.tools.checkMcpServers.active': 'Sprawdzanie serwerów MCP',
+ 'conversations.tools.checkMcpServers.done': 'Sprawdzono serwery MCP',
+ 'conversations.tools.checkMcpTools.active': 'Sprawdzanie narzędzi MCP',
+ 'conversations.tools.checkMcpTools.done': 'Sprawdzono narzędzia MCP',
+ 'conversations.tools.callMcpTool.active': 'Wywoływanie {tool}',
+ 'conversations.tools.callMcpTool.done': 'Wywołano {tool}',
+ 'conversations.tools.searchMcpServers.active': 'Wyszukiwanie serwerów MCP',
+ 'conversations.tools.searchMcpServers.done': 'Wyszukano serwery MCP',
+ 'conversations.tools.connectMcpServer.active': 'Łączenie serwera MCP',
+ 'conversations.tools.connectMcpServer.done': 'Połączono serwer MCP',
+ 'conversations.tools.disconnectMcpServer.active': 'Rozłączanie serwera MCP',
+ 'conversations.tools.disconnectMcpServer.done': 'Rozłączono serwer MCP',
+ 'conversations.tools.removeMcpServer.active': 'Usuwanie serwera MCP',
+ 'conversations.tools.removeMcpServer.done': 'Usunięto serwer MCP',
+ 'conversations.tools.uploadFile.active': 'Przesyłanie pliku',
+ 'conversations.tools.uploadFile.done': 'Przesłano plik',
+ 'conversations.tools.listStoredFiles.active': 'Wyświetlanie zapisanych plików',
+ 'conversations.tools.listStoredFiles.done': 'Wyświetlono zapisane pliki',
+ 'conversations.tools.createShareLink.active': 'Tworzenie linku do udostępnienia',
+ 'conversations.tools.createShareLink.done': 'Utworzono link do udostępnienia',
+ 'conversations.tools.deleteFile.active': 'Usuwanie pliku',
+ 'conversations.tools.deleteFile.done': 'Usunięto plik',
+ 'conversations.tools.updateFileAccess.active': 'Aktualizowanie dostępu do pliku',
+ 'conversations.tools.updateFileAccess.done': 'Zaktualizowano dostęp do pliku',
+ 'conversations.tools.deploySite.active': 'Wdrażanie witryny',
+ 'conversations.tools.deploySite.done': 'Wdrożono witrynę',
+ 'conversations.tools.checkHosting.active': 'Sprawdzanie hostingu',
+ 'conversations.tools.checkHosting.done': 'Sprawdzono hosting',
+ 'conversations.tools.updateHosting.active': 'Aktualizowanie hostingu',
+ 'conversations.tools.updateHosting.done': 'Zaktualizowano hosting',
+ 'conversations.tools.rollBackDeployment.active': 'Wycofywanie wdrożenia',
+ 'conversations.tools.rollBackDeployment.done': 'Wycofano wdrożenie',
+ 'conversations.tools.checkWallet.active': 'Sprawdzanie portfela',
+ 'conversations.tools.checkWallet.done': 'Sprawdzono portfel',
+ 'conversations.tools.prepareTransfer.active': 'Przygotowywanie przelewu',
+ 'conversations.tools.prepareTransfer.done': 'Przygotowano przelew',
+ 'conversations.tools.checkTransaction.active': 'Sprawdzanie transakcji',
+ 'conversations.tools.checkTransaction.done': 'Sprawdzono transakcję',
+ 'conversations.tools.getSwapQuote.active': 'Pobieranie wyceny wymiany',
+ 'conversations.tools.getSwapQuote.done': 'Pobrano wycenę wymiany',
+ 'conversations.tools.swapTokens.active': 'Wymiana tokenów',
+ 'conversations.tools.swapTokens.done': 'Wymieniono tokeny',
+ 'conversations.tools.getBridgeQuote.active': 'Pobieranie wyceny mostu',
+ 'conversations.tools.getBridgeQuote.done': 'Pobrano wycenę mostu',
+ 'conversations.tools.bridgeTokens.active': 'Przenoszenie tokenów przez most',
+ 'conversations.tools.bridgeTokens.done': 'Przeniesiono tokeny przez most',
+ 'conversations.tools.callDapp.active': 'Wywoływanie kontraktu aplikacji',
+ 'conversations.tools.callDapp.done': 'Wywołano kontrakt aplikacji',
+ 'conversations.tools.useSkill.active': 'Korzystanie z umiejętności',
+ 'conversations.tools.useSkill.done': 'Skorzystano z umiejętności',
+ 'conversations.tools.searchSkills.active': 'Wyszukiwanie umiejętności',
+ 'conversations.tools.searchSkills.done': 'Wyszukano umiejętności',
+ 'conversations.tools.checkSkills.active': 'Sprawdzanie umiejętności',
+ 'conversations.tools.checkSkills.done': 'Sprawdzono umiejętności',
+ 'conversations.tools.installSkill.active': 'Instalowanie umiejętności',
+ 'conversations.tools.installSkill.done': 'Zainstalowano umiejętność',
+ 'conversations.tools.removeSkill.active': 'Usuwanie umiejętności',
+ 'conversations.tools.removeSkill.done': 'Usunięto umiejętność',
+ 'conversations.tools.createSkill.active': 'Tworzenie umiejętności',
+ 'conversations.tools.createSkill.done': 'Utworzono umiejętność',
+ 'conversations.tools.runWorkflow.active': 'Uruchamianie przepływu pracy',
+ 'conversations.tools.runWorkflow.done': 'Uruchomiono przepływ pracy',
+ 'conversations.tools.waitForWorkflow.active': 'Oczekiwanie na przepływ pracy',
+ 'conversations.tools.waitForWorkflow.done': 'Przepływ pracy zakończony',
+ 'conversations.tools.designWorkflow.active': 'Projektowanie przepływu pracy',
+ 'conversations.tools.designWorkflow.done': 'Zaprojektowano przepływ pracy',
+ 'conversations.tools.saveWorkflow.active': 'Zapisywanie przepływu pracy',
+ 'conversations.tools.saveWorkflow.done': 'Zapisano przepływ pracy',
+ 'conversations.tools.validateWorkflow.active': 'Weryfikowanie przepływu pracy',
+ 'conversations.tools.validateWorkflow.done': 'Zweryfikowano przepływ pracy',
+ 'conversations.tools.testWorkflow.active': 'Testowanie przepływu pracy',
+ 'conversations.tools.testWorkflow.done': 'Przetestowano przepływ pracy',
+ 'conversations.tools.checkWorkflows.active': 'Sprawdzanie przepływów pracy',
+ 'conversations.tools.checkWorkflows.done': 'Sprawdzono przepływy pracy',
+ 'conversations.tools.cancelWorkflow.active': 'Anulowanie uruchomienia przepływu pracy',
+ 'conversations.tools.cancelWorkflow.done': 'Anulowano uruchomienie przepływu pracy',
+ 'conversations.tools.suggestWorkflows.active': 'Proponowanie przepływów pracy',
+ 'conversations.tools.suggestWorkflows.done': 'Zaproponowano przepływy pracy',
+ 'conversations.tools.checkSettings.active': 'Sprawdzanie ustawień',
+ 'conversations.tools.checkSettings.done': 'Sprawdzono ustawienia',
+ 'conversations.tools.checkSecurity.active': 'Sprawdzanie zabezpieczeń',
+ 'conversations.tools.checkSecurity.done': 'Sprawdzono zabezpieczenia',
+ 'conversations.tools.runDiagnostics.active': 'Uruchamianie diagnostyki',
+ 'conversations.tools.runDiagnostics.done': 'Uruchomiono diagnostykę',
+ 'conversations.tools.checkUsageCosts.active': 'Sprawdzanie kosztów użycia',
+ 'conversations.tools.checkUsageCosts.done': 'Sprawdzono koszty użycia',
+ 'conversations.tools.manageService.active': 'Zarządzanie usługą w tle',
+ 'conversations.tools.manageService.done': 'Zarządzono usługą w tle',
+ 'conversations.tools.readPersona.active': 'Czytanie persony',
+ 'conversations.tools.readPersona.done': 'Odczytano personę',
+ 'conversations.tools.updatePersona.active': 'Aktualizowanie persony',
+ 'conversations.tools.updatePersona.done': 'Zaktualizowano personę',
+ 'conversations.tools.setUpWorkspace.active': 'Konfigurowanie obszaru roboczego',
+ 'conversations.tools.setUpWorkspace.done': 'Skonfigurowano obszar roboczy',
+ 'conversations.tools.checkArtifacts.active': 'Sprawdzanie artefaktów',
+ 'conversations.tools.checkArtifacts.done': 'Sprawdzono artefakty',
+ 'conversations.tools.deleteArtifact.active': 'Usuwanie artefaktu',
+ 'conversations.tools.deleteArtifact.done': 'Usunięto artefakt',
'conversations.subagent.noOutput': 'Brak zwróconych danych wyjściowych',
'conversations.subagent.close': 'Zamknij',
'conversations.subagent.cancel': 'Anuluj zadanie',
diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts
index dc6668e06c7..059b591edc9 100644
--- a/app/src/lib/i18n/pt.ts
+++ b/app/src/lib/i18n/pt.ts
@@ -3370,6 +3370,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'Ainda sem resultado',
'conversations.subagent.input': 'Entrada',
'conversations.subagent.output': 'Saída',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} etapa',
+ 'conversations.tools.steps.other': '{count} etapas',
+ 'conversations.tools.working': 'Trabalhando',
+ 'conversations.tools.noOutput': 'Sem saída',
+ 'conversations.tools.delegatedTo': 'Delegado a {agent}',
+ 'conversations.tools.openInBrowser': 'Abrir no navegador',
+ 'conversations.tools.status.running': 'em execução',
+ 'conversations.tools.status.done': 'concluído',
+ 'conversations.tools.status.failed': 'falhou',
+ 'conversations.tools.status.cancelled': 'cancelado',
+ 'conversations.tools.status.awaiting': 'aguardando resposta',
+ 'conversations.tools.search.searching': 'Pesquisando',
+ 'conversations.tools.search.none': 'Nenhum resultado',
+ 'conversations.tools.search.found.one': '{count} resultado encontrado',
+ 'conversations.tools.search.found.other': '{count} resultados encontrados',
+ 'conversations.tools.search.via': 'por meio de {provider}',
+ 'conversations.tools.readFile.active': 'Lendo arquivo',
+ 'conversations.tools.readFile.done': 'Arquivo lido',
+ 'conversations.tools.writeFile.active': 'Escrevendo arquivo',
+ 'conversations.tools.writeFile.done': 'Arquivo escrito',
+ 'conversations.tools.editFile.active': 'Editando arquivo',
+ 'conversations.tools.editFile.done': 'Arquivo editado',
+ 'conversations.tools.applyEdits.active': 'Aplicando alterações',
+ 'conversations.tools.applyEdits.done': 'Alterações aplicadas',
+ 'conversations.tools.searchCode.active': 'Pesquisando no código',
+ 'conversations.tools.searchCode.done': 'Código pesquisado',
+ 'conversations.tools.findFiles.active': 'Procurando arquivos',
+ 'conversations.tools.findFiles.done': 'Arquivos encontrados',
+ 'conversations.tools.listFolder.active': 'Listando pasta',
+ 'conversations.tools.listFolder.done': 'Pasta listada',
+ 'conversations.tools.exportCsv.active': 'Exportando CSV',
+ 'conversations.tools.exportCsv.done': 'CSV exportado',
+ 'conversations.tools.updateMemoryNotes.active': 'Atualizando notas de memória',
+ 'conversations.tools.updateMemoryNotes.done': 'Notas de memória atualizadas',
+ 'conversations.tools.runGit.active': 'Executando git',
+ 'conversations.tools.runGit.done': 'git executado',
+ 'conversations.tools.readChanges.active': 'Lendo alterações',
+ 'conversations.tools.readChanges.done': 'Alterações lidas',
+ 'conversations.tools.runLinter.active': 'Executando linter',
+ 'conversations.tools.runLinter.done': 'Linter executado',
+ 'conversations.tools.runTests.active': 'Executando testes',
+ 'conversations.tools.runTests.done': 'Testes executados',
+ 'conversations.tools.analyzeCode.active': 'Analisando código',
+ 'conversations.tools.analyzeCode.done': 'Código analisado',
+ 'conversations.tools.insertRecord.active': 'Inserindo registro',
+ 'conversations.tools.insertRecord.done': 'Registro inserido',
+ 'conversations.tools.runCommand.active': 'Executando comando',
+ 'conversations.tools.runCommand.done': 'Comando executado',
+ 'conversations.tools.runCode.active': 'Executando código',
+ 'conversations.tools.runCode.done': 'Código executado',
+ 'conversations.tools.runPackageManager.active': 'Executando npm',
+ 'conversations.tools.runPackageManager.done': 'npm executado',
+ 'conversations.tools.checkInstalledTools.active': 'Verificando ferramentas instaladas',
+ 'conversations.tools.checkInstalledTools.done': 'Ferramentas instaladas verificadas',
+ 'conversations.tools.installTool.active': 'Instalando ferramenta',
+ 'conversations.tools.installTool.done': 'Ferramenta instalada',
+ 'conversations.tools.checkTime.active': 'Verificando a hora',
+ 'conversations.tools.checkTime.done': 'Hora verificada',
+ 'conversations.tools.resolveDate.active': 'Calculando a data',
+ 'conversations.tools.resolveDate.done': 'Data calculada',
+ 'conversations.tools.retrieveOutput.active': 'Recuperando a saída completa',
+ 'conversations.tools.retrieveOutput.done': 'Saída completa recuperada',
+ 'conversations.tools.reviewWorkspace.active': 'Revisando o espaço de trabalho',
+ 'conversations.tools.reviewWorkspace.done': 'Espaço de trabalho revisado',
+ 'conversations.tools.configureProxy.active': 'Configurando proxy',
+ 'conversations.tools.configureProxy.done': 'Proxy configurado',
+ 'conversations.tools.checkUpdates.active': 'Procurando atualizações',
+ 'conversations.tools.checkUpdates.done': 'Atualizações verificadas',
+ 'conversations.tools.installUpdate.active': 'Instalando atualização',
+ 'conversations.tools.installUpdate.done': 'Atualização instalada',
+ 'conversations.tools.sendNotification.active': 'Enviando notificação',
+ 'conversations.tools.sendNotification.done': 'Notificação enviada',
+ 'conversations.tools.reviewToolUsage.active': 'Revisando o uso de ferramentas',
+ 'conversations.tools.reviewToolUsage.done': 'Uso de ferramentas revisado',
+ 'conversations.tools.typeKeys.active': 'Digitando',
+ 'conversations.tools.typeKeys.done': 'Texto digitado',
+ 'conversations.tools.click.active': 'Clicando',
+ 'conversations.tools.click.done': 'Clique feito',
+ 'conversations.tools.searchWeb.active': 'Pesquisando na web',
+ 'conversations.tools.searchWeb.done': 'Pesquisa na web concluída',
+ 'conversations.tools.searchNews.active': 'Pesquisando notícias',
+ 'conversations.tools.searchNews.done': 'Notícias pesquisadas',
+ 'conversations.tools.searchImages.active': 'Pesquisando imagens',
+ 'conversations.tools.searchImages.done': 'Imagens pesquisadas',
+ 'conversations.tools.searchVideos.active': 'Pesquisando vídeos',
+ 'conversations.tools.searchVideos.done': 'Vídeos pesquisados',
+ 'conversations.tools.findSimilarPages.active': 'Procurando páginas semelhantes',
+ 'conversations.tools.findSimilarPages.done': 'Páginas semelhantes encontradas',
+ 'conversations.tools.readPages.active': 'Lendo páginas',
+ 'conversations.tools.readPages.done': 'Páginas lidas',
+ 'conversations.tools.readWebpage.active': 'Lendo página da web',
+ 'conversations.tools.readWebpage.done': 'Página da web lida',
+ 'conversations.tools.research.active': 'Pesquisando a fundo',
+ 'conversations.tools.research.done': 'Pesquisa aprofundada concluída',
+ 'conversations.tools.enrichData.active': 'Enriquecendo dados',
+ 'conversations.tools.enrichData.done': 'Dados enriquecidos',
+ 'conversations.tools.buildDataset.active': 'Criando conjunto de dados',
+ 'conversations.tools.buildDataset.done': 'Conjunto de dados criado',
+ 'conversations.tools.askTheWeb.active': 'Consultando a web',
+ 'conversations.tools.askTheWeb.done': 'Web consultada',
+ 'conversations.tools.browseForYou.active': 'Navegando por você',
+ 'conversations.tools.browseForYou.done': 'Navegação feita por você',
+ 'conversations.tools.callApi.active': 'Chamando a API',
+ 'conversations.tools.callApi.done': 'API chamada',
+ 'conversations.tools.downloadFile.active': 'Baixando arquivo',
+ 'conversations.tools.downloadFile.done': 'Arquivo baixado',
+ 'conversations.tools.makePaidRequest.active': 'Fazendo solicitação paga',
+ 'conversations.tools.makePaidRequest.done': 'Solicitação paga feita',
+ 'conversations.tools.searchDocs.active': 'Pesquisando na documentação',
+ 'conversations.tools.searchDocs.done': 'Documentação pesquisada',
+ 'conversations.tools.readDocs.active': 'Lendo a documentação',
+ 'conversations.tools.readDocs.done': 'Documentação lida',
+ 'conversations.tools.useBrowser.active': 'Usando o navegador',
+ 'conversations.tools.useBrowser.done': 'Navegador usado',
+ 'conversations.tools.openPage.active': 'Abrindo página',
+ 'conversations.tools.openPage.done': 'Página aberta',
+ 'conversations.tools.navigate.active': 'Navegando',
+ 'conversations.tools.navigate.done': 'Navegação concluída',
+ 'conversations.tools.takeScreenshot.active': 'Fazendo captura de tela',
+ 'conversations.tools.takeScreenshot.done': 'Captura de tela feita',
+ 'conversations.tools.scrollPage.active': 'Rolando',
+ 'conversations.tools.scrollPage.done': 'Rolagem concluída',
+ 'conversations.tools.readPage.active': 'Lendo página',
+ 'conversations.tools.readPage.done': 'Página lida',
+ 'conversations.tools.analyzeImage.active': 'Analisando imagem',
+ 'conversations.tools.analyzeImage.done': 'Imagem analisada',
+ 'conversations.tools.generateImage.active': 'Gerando imagem',
+ 'conversations.tools.generateImage.done': 'Imagem gerada',
+ 'conversations.tools.generateVideo.active': 'Gerando vídeo',
+ 'conversations.tools.generateVideo.done': 'Vídeo gerado',
+ 'conversations.tools.checkMediaModels.active': 'Verificando modelos de mídia',
+ 'conversations.tools.checkMediaModels.done': 'Modelos de mídia verificados',
+ 'conversations.tools.createDocument.active': 'Criando documento',
+ 'conversations.tools.createDocument.done': 'Documento criado',
+ 'conversations.tools.createPresentation.active': 'Criando apresentação',
+ 'conversations.tools.createPresentation.done': 'Apresentação criada',
+ 'conversations.tools.generatePodcast.active': 'Gerando podcast',
+ 'conversations.tools.generatePodcast.done': 'Podcast gerado',
+ 'conversations.tools.emailPodcast.active': 'Enviando podcast por e-mail',
+ 'conversations.tools.emailPodcast.done': 'Podcast enviado por e-mail',
+ 'conversations.tools.createAndEmailPodcast.active': 'Criando e enviando podcast por e-mail',
+ 'conversations.tools.createAndEmailPodcast.done': 'Podcast criado e enviado por e-mail',
+ 'conversations.tools.recallMemories.active': 'Relembrando memórias',
+ 'conversations.tools.recallMemories.done': 'Memórias relembradas',
+ 'conversations.tools.saveToMemory.active': 'Salvando na memória',
+ 'conversations.tools.saveToMemory.done': 'Salvo na memória',
+ 'conversations.tools.forgetMemory.active': 'Esquecendo memória',
+ 'conversations.tools.forgetMemory.done': 'Memória esquecida',
+ 'conversations.tools.searchMemory.active': 'Pesquisando na memória',
+ 'conversations.tools.searchMemory.done': 'Memória pesquisada',
+ 'conversations.tools.inspectMemory.active': 'Inspecionando a memória',
+ 'conversations.tools.inspectMemory.done': 'Memória inspecionada',
+ 'conversations.tools.exploreMemory.active': 'Explorando a memória',
+ 'conversations.tools.exploreMemory.done': 'Memória explorada',
+ 'conversations.tools.saveDocumentToMemory.active': 'Salvando documento na memória',
+ 'conversations.tools.saveDocumentToMemory.done': 'Documento salvo na memória',
+ 'conversations.tools.updateGoals.active': 'Atualizando metas',
+ 'conversations.tools.updateGoals.done': 'Metas atualizadas',
+ 'conversations.tools.reviewGoals.active': 'Revisando metas',
+ 'conversations.tools.reviewGoals.done': 'Metas revisadas',
+ 'conversations.tools.savePreference.active': 'Salvando preferência',
+ 'conversations.tools.savePreference.done': 'Preferência salva',
+ 'conversations.tools.reviewLearnings.active': 'Revisando o que aprendi',
+ 'conversations.tools.reviewLearnings.done': 'Aprendizados revisados',
+ 'conversations.tools.updateLearnings.active': 'Atualizando o que aprendi',
+ 'conversations.tools.updateLearnings.done': 'Aprendizados atualizados',
+ 'conversations.tools.delegateTask.active': 'Delegando tarefa',
+ 'conversations.tools.delegateTask.done': 'Tarefa delegada',
+ 'conversations.tools.runAgentsInParallel.active': 'Executando agentes em paralelo',
+ 'conversations.tools.runAgentsInParallel.done': 'Agentes executados em paralelo',
+ 'conversations.tools.messageAgent.active': 'Enviando mensagem ao agente',
+ 'conversations.tools.messageAgent.done': 'Mensagem enviada ao agente',
+ 'conversations.tools.waitForAgent.active': 'Aguardando o agente',
+ 'conversations.tools.waitForAgent.done': 'Espera pelo agente concluída',
+ 'conversations.tools.wait.active': 'Aguardando',
+ 'conversations.tools.wait.done': 'Espera concluída',
+ 'conversations.tools.closeAgent.active': 'Fechando agente',
+ 'conversations.tools.closeAgent.done': 'Agente fechado',
+ 'conversations.tools.checkAgents.active': 'Verificando agentes',
+ 'conversations.tools.checkAgents.done': 'Agentes verificados',
+ 'conversations.tools.askQuestion.active': 'Fazendo uma pergunta a você',
+ 'conversations.tools.askQuestion.done': 'Pergunta feita a você',
+ 'conversations.tools.prepareContext.active': 'Preparando contexto',
+ 'conversations.tools.prepareContext.done': 'Contexto preparado',
+ 'conversations.tools.extractDetails.active': 'Extraindo detalhes',
+ 'conversations.tools.extractDetails.done': 'Detalhes extraídos',
+ 'conversations.tools.planNextSteps.active': 'Planejando próximos passos',
+ 'conversations.tools.planNextSteps.done': 'Próximos passos planejados',
+ 'conversations.tools.reviewWork.active': 'Revisando o trabalho',
+ 'conversations.tools.reviewWork.done': 'Trabalho revisado',
+ 'conversations.tools.scoutContext.active': 'Explorando o contexto',
+ 'conversations.tools.scoutContext.done': 'Contexto explorado',
+ 'conversations.tools.useTools.active': 'Usando ferramentas',
+ 'conversations.tools.useTools.done': 'Ferramentas usadas',
+ 'conversations.tools.checkConnectedApp.active': 'Verificando seu app conectado',
+ 'conversations.tools.checkConnectedApp.done': 'App conectado verificado',
+ 'conversations.tools.updateTodos.active': 'Atualizando lista de tarefas',
+ 'conversations.tools.updateTodos.done': 'Lista de tarefas atualizada',
+ 'conversations.tools.requestPlanReview.active': 'Solicitando revisão do plano',
+ 'conversations.tools.requestPlanReview.done': 'Revisão do plano solicitada',
+ 'conversations.tools.finishPlan.active': 'Finalizando o plano',
+ 'conversations.tools.finishPlan.done': 'Plano finalizado',
+ 'conversations.tools.setGoal.active': 'Definindo meta',
+ 'conversations.tools.setGoal.done': 'Meta definida',
+ 'conversations.tools.checkGoal.active': 'Verificando meta',
+ 'conversations.tools.checkGoal.done': 'Meta verificada',
+ 'conversations.tools.completeGoal.active': 'Concluindo meta',
+ 'conversations.tools.completeGoal.done': 'Meta concluída',
+ 'conversations.tools.scheduleTask.active': 'Agendando tarefa',
+ 'conversations.tools.scheduleTask.done': 'Tarefa agendada',
+ 'conversations.tools.checkSchedules.active': 'Verificando agendamentos',
+ 'conversations.tools.checkSchedules.done': 'Agendamentos verificados',
+ 'conversations.tools.updateSchedule.active': 'Atualizando tarefa agendada',
+ 'conversations.tools.updateSchedule.done': 'Tarefa agendada atualizada',
+ 'conversations.tools.removeSchedule.active': 'Removendo tarefa agendada',
+ 'conversations.tools.removeSchedule.done': 'Tarefa agendada removida',
+ 'conversations.tools.runScheduledTask.active': 'Executando tarefa agendada',
+ 'conversations.tools.runScheduledTask.done': 'Tarefa agendada executada',
+ 'conversations.tools.checkRunHistory.active': 'Verificando histórico de execuções',
+ 'conversations.tools.checkRunHistory.done': 'Histórico de execuções verificado',
+ 'conversations.tools.useApp.active': 'Usando {app}',
+ 'conversations.tools.useApp.done': '{app} usado',
+ 'conversations.tools.checkAvailableApps.active': 'Verificando apps disponíveis',
+ 'conversations.tools.checkAvailableApps.done': 'Apps disponíveis verificados',
+ 'conversations.tools.checkConnections.active': 'Verificando suas conexões',
+ 'conversations.tools.checkConnections.done': 'Conexões verificadas',
+ 'conversations.tools.connectApp.active': 'Conectando app',
+ 'conversations.tools.connectApp.done': 'App conectado',
+ 'conversations.tools.authorizeApp.active': 'Autorizando app',
+ 'conversations.tools.authorizeApp.done': 'App autorizado',
+ 'conversations.tools.findAppActions.active': 'Procurando ações do app',
+ 'conversations.tools.findAppActions.done': 'Ações do app encontradas',
+ 'conversations.tools.runAppAction.active': 'Executando ação do app',
+ 'conversations.tools.runAppAction.done': 'Ação do app executada',
+ 'conversations.tools.findTools.active': 'Procurando ferramentas',
+ 'conversations.tools.findTools.done': 'Ferramentas encontradas',
+ 'conversations.tools.useTool.active': 'Usando {tool}',
+ 'conversations.tools.useTool.done': '{tool} usado',
+ 'conversations.tools.unsubscribe.active': 'Cancelando inscrição',
+ 'conversations.tools.unsubscribe.done': 'Inscrição cancelada',
+ 'conversations.tools.searchPlaces.active': 'Pesquisando lugares',
+ 'conversations.tools.searchPlaces.done': 'Lugares pesquisados',
+ 'conversations.tools.lookUpPlace.active': 'Consultando local',
+ 'conversations.tools.lookUpPlace.done': 'Local consultado',
+ 'conversations.tools.checkMarkets.active': 'Consultando mercados',
+ 'conversations.tools.checkMarkets.done': 'Mercados consultados',
+ 'conversations.tools.placeCall.active': 'Fazendo ligação',
+ 'conversations.tools.placeCall.done': 'Ligação feita',
+ 'conversations.tools.checkTaskSources.active': 'Verificando fontes de tarefas',
+ 'conversations.tools.checkTaskSources.done': 'Fontes de tarefas verificadas',
+ 'conversations.tools.updateTaskSources.active': 'Atualizando fontes de tarefas',
+ 'conversations.tools.updateTaskSources.done': 'Fontes de tarefas atualizadas',
+ 'conversations.tools.fetchTasks.active': 'Buscando tarefas',
+ 'conversations.tools.fetchTasks.done': 'Tarefas obtidas',
+ 'conversations.tools.checkMcpServers.active': 'Verificando servidores MCP',
+ 'conversations.tools.checkMcpServers.done': 'Servidores MCP verificados',
+ 'conversations.tools.checkMcpTools.active': 'Verificando ferramentas MCP',
+ 'conversations.tools.checkMcpTools.done': 'Ferramentas MCP verificadas',
+ 'conversations.tools.callMcpTool.active': 'Chamando {tool}',
+ 'conversations.tools.callMcpTool.done': '{tool} chamado',
+ 'conversations.tools.searchMcpServers.active': 'Pesquisando servidores MCP',
+ 'conversations.tools.searchMcpServers.done': 'Servidores MCP pesquisados',
+ 'conversations.tools.connectMcpServer.active': 'Conectando servidor MCP',
+ 'conversations.tools.connectMcpServer.done': 'Servidor MCP conectado',
+ 'conversations.tools.disconnectMcpServer.active': 'Desconectando servidor MCP',
+ 'conversations.tools.disconnectMcpServer.done': 'Servidor MCP desconectado',
+ 'conversations.tools.removeMcpServer.active': 'Removendo servidor MCP',
+ 'conversations.tools.removeMcpServer.done': 'Servidor MCP removido',
+ 'conversations.tools.uploadFile.active': 'Enviando arquivo',
+ 'conversations.tools.uploadFile.done': 'Arquivo enviado',
+ 'conversations.tools.listStoredFiles.active': 'Listando arquivos armazenados',
+ 'conversations.tools.listStoredFiles.done': 'Arquivos armazenados listados',
+ 'conversations.tools.createShareLink.active': 'Criando link de compartilhamento',
+ 'conversations.tools.createShareLink.done': 'Link de compartilhamento criado',
+ 'conversations.tools.deleteFile.active': 'Excluindo arquivo',
+ 'conversations.tools.deleteFile.done': 'Arquivo excluído',
+ 'conversations.tools.updateFileAccess.active': 'Atualizando acesso ao arquivo',
+ 'conversations.tools.updateFileAccess.done': 'Acesso ao arquivo atualizado',
+ 'conversations.tools.deploySite.active': 'Implantando site',
+ 'conversations.tools.deploySite.done': 'Site implantado',
+ 'conversations.tools.checkHosting.active': 'Verificando hospedagem',
+ 'conversations.tools.checkHosting.done': 'Hospedagem verificada',
+ 'conversations.tools.updateHosting.active': 'Atualizando hospedagem',
+ 'conversations.tools.updateHosting.done': 'Hospedagem atualizada',
+ 'conversations.tools.rollBackDeployment.active': 'Revertendo implantação',
+ 'conversations.tools.rollBackDeployment.done': 'Implantação revertida',
+ 'conversations.tools.checkWallet.active': 'Verificando carteira',
+ 'conversations.tools.checkWallet.done': 'Carteira verificada',
+ 'conversations.tools.prepareTransfer.active': 'Preparando transferência',
+ 'conversations.tools.prepareTransfer.done': 'Transferência preparada',
+ 'conversations.tools.checkTransaction.active': 'Verificando transação',
+ 'conversations.tools.checkTransaction.done': 'Transação verificada',
+ 'conversations.tools.getSwapQuote.active': 'Obtendo cotação de troca',
+ 'conversations.tools.getSwapQuote.done': 'Cotação de troca obtida',
+ 'conversations.tools.swapTokens.active': 'Trocando tokens',
+ 'conversations.tools.swapTokens.done': 'Tokens trocados',
+ 'conversations.tools.getBridgeQuote.active': 'Obtendo cotação de ponte',
+ 'conversations.tools.getBridgeQuote.done': 'Cotação de ponte obtida',
+ 'conversations.tools.bridgeTokens.active': 'Transferindo tokens por ponte',
+ 'conversations.tools.bridgeTokens.done': 'Tokens transferidos por ponte',
+ 'conversations.tools.callDapp.active': 'Chamando o contrato do app',
+ 'conversations.tools.callDapp.done': 'Contrato do app chamado',
+ 'conversations.tools.useSkill.active': 'Usando habilidade',
+ 'conversations.tools.useSkill.done': 'Habilidade usada',
+ 'conversations.tools.searchSkills.active': 'Pesquisando habilidades',
+ 'conversations.tools.searchSkills.done': 'Habilidades pesquisadas',
+ 'conversations.tools.checkSkills.active': 'Verificando habilidades',
+ 'conversations.tools.checkSkills.done': 'Habilidades verificadas',
+ 'conversations.tools.installSkill.active': 'Instalando habilidade',
+ 'conversations.tools.installSkill.done': 'Habilidade instalada',
+ 'conversations.tools.removeSkill.active': 'Removendo habilidade',
+ 'conversations.tools.removeSkill.done': 'Habilidade removida',
+ 'conversations.tools.createSkill.active': 'Criando habilidade',
+ 'conversations.tools.createSkill.done': 'Habilidade criada',
+ 'conversations.tools.runWorkflow.active': 'Executando fluxo de trabalho',
+ 'conversations.tools.runWorkflow.done': 'Fluxo de trabalho executado',
+ 'conversations.tools.waitForWorkflow.active': 'Aguardando o fluxo de trabalho',
+ 'conversations.tools.waitForWorkflow.done': 'Espera pelo fluxo de trabalho concluída',
+ 'conversations.tools.designWorkflow.active': 'Projetando fluxo de trabalho',
+ 'conversations.tools.designWorkflow.done': 'Fluxo de trabalho projetado',
+ 'conversations.tools.saveWorkflow.active': 'Salvando fluxo de trabalho',
+ 'conversations.tools.saveWorkflow.done': 'Fluxo de trabalho salvo',
+ 'conversations.tools.validateWorkflow.active': 'Validando fluxo de trabalho',
+ 'conversations.tools.validateWorkflow.done': 'Fluxo de trabalho validado',
+ 'conversations.tools.testWorkflow.active': 'Testando fluxo de trabalho',
+ 'conversations.tools.testWorkflow.done': 'Fluxo de trabalho testado',
+ 'conversations.tools.checkWorkflows.active': 'Verificando fluxos de trabalho',
+ 'conversations.tools.checkWorkflows.done': 'Fluxos de trabalho verificados',
+ 'conversations.tools.cancelWorkflow.active': 'Cancelando execução do fluxo de trabalho',
+ 'conversations.tools.cancelWorkflow.done': 'Execução do fluxo de trabalho cancelada',
+ 'conversations.tools.suggestWorkflows.active': 'Sugerindo fluxos de trabalho',
+ 'conversations.tools.suggestWorkflows.done': 'Fluxos de trabalho sugeridos',
+ 'conversations.tools.checkSettings.active': 'Verificando configurações',
+ 'conversations.tools.checkSettings.done': 'Configurações verificadas',
+ 'conversations.tools.checkSecurity.active': 'Verificando segurança',
+ 'conversations.tools.checkSecurity.done': 'Segurança verificada',
+ 'conversations.tools.runDiagnostics.active': 'Executando diagnósticos',
+ 'conversations.tools.runDiagnostics.done': 'Diagnósticos executados',
+ 'conversations.tools.checkUsageCosts.active': 'Verificando custos de uso',
+ 'conversations.tools.checkUsageCosts.done': 'Custos de uso verificados',
+ 'conversations.tools.manageService.active': 'Gerenciando serviço em segundo plano',
+ 'conversations.tools.manageService.done': 'Serviço em segundo plano gerenciado',
+ 'conversations.tools.readPersona.active': 'Lendo persona',
+ 'conversations.tools.readPersona.done': 'Persona lida',
+ 'conversations.tools.updatePersona.active': 'Atualizando persona',
+ 'conversations.tools.updatePersona.done': 'Persona atualizada',
+ 'conversations.tools.setUpWorkspace.active': 'Configurando espaço de trabalho',
+ 'conversations.tools.setUpWorkspace.done': 'Espaço de trabalho configurado',
+ 'conversations.tools.checkArtifacts.active': 'Verificando artefatos',
+ 'conversations.tools.checkArtifacts.done': 'Artefatos verificados',
+ 'conversations.tools.deleteArtifact.active': 'Excluindo artefato',
+ 'conversations.tools.deleteArtifact.done': 'Artefato excluído',
'conversations.subagent.noOutput': 'Nenhuma saída retornada',
'conversations.subagent.close': 'Fechar',
'conversations.subagent.cancel': 'Cancelar tarefa',
diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts
index 4d95c0dfa2e..f2a9d74520a 100644
--- a/app/src/lib/i18n/ru.ts
+++ b/app/src/lib/i18n/ru.ts
@@ -3345,6 +3345,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': 'Пока нет результата',
'conversations.subagent.input': 'Ввод',
'conversations.subagent.output': 'Вывод',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} шаг',
+ 'conversations.tools.steps.other': 'Шагов: {count}',
+ 'conversations.tools.working': 'Выполняется',
+ 'conversations.tools.noOutput': 'Нет вывода',
+ 'conversations.tools.delegatedTo': 'Передано агенту {agent}',
+ 'conversations.tools.openInBrowser': 'Открыть в браузере',
+ 'conversations.tools.status.running': 'выполняется',
+ 'conversations.tools.status.done': 'готово',
+ 'conversations.tools.status.failed': 'ошибка',
+ 'conversations.tools.status.cancelled': 'отменено',
+ 'conversations.tools.status.awaiting': 'ожидает ввода',
+ 'conversations.tools.search.searching': 'Поиск',
+ 'conversations.tools.search.none': 'Нет результатов',
+ 'conversations.tools.search.found.one': 'Найден {count} результат',
+ 'conversations.tools.search.found.other': 'Найдено результатов: {count}',
+ 'conversations.tools.search.via': 'через {provider}',
+ 'conversations.tools.readFile.active': 'Чтение файла',
+ 'conversations.tools.readFile.done': 'Файл прочитан',
+ 'conversations.tools.writeFile.active': 'Запись файла',
+ 'conversations.tools.writeFile.done': 'Файл записан',
+ 'conversations.tools.editFile.active': 'Редактирование файла',
+ 'conversations.tools.editFile.done': 'Файл отредактирован',
+ 'conversations.tools.applyEdits.active': 'Применение правок',
+ 'conversations.tools.applyEdits.done': 'Правки применены',
+ 'conversations.tools.searchCode.active': 'Поиск по коду',
+ 'conversations.tools.searchCode.done': 'Поиск по коду выполнен',
+ 'conversations.tools.findFiles.active': 'Поиск файлов',
+ 'conversations.tools.findFiles.done': 'Файлы найдены',
+ 'conversations.tools.listFolder.active': 'Просмотр папки',
+ 'conversations.tools.listFolder.done': 'Папка просмотрена',
+ 'conversations.tools.exportCsv.active': 'Экспорт CSV',
+ 'conversations.tools.exportCsv.done': 'CSV экспортирован',
+ 'conversations.tools.updateMemoryNotes.active': 'Обновление заметок памяти',
+ 'conversations.tools.updateMemoryNotes.done': 'Заметки памяти обновлены',
+ 'conversations.tools.runGit.active': 'Запуск git',
+ 'conversations.tools.runGit.done': 'git выполнен',
+ 'conversations.tools.readChanges.active': 'Чтение изменений',
+ 'conversations.tools.readChanges.done': 'Изменения прочитаны',
+ 'conversations.tools.runLinter.active': 'Запуск линтера',
+ 'conversations.tools.runLinter.done': 'Линтер выполнен',
+ 'conversations.tools.runTests.active': 'Запуск тестов',
+ 'conversations.tools.runTests.done': 'Тесты выполнены',
+ 'conversations.tools.analyzeCode.active': 'Анализ кода',
+ 'conversations.tools.analyzeCode.done': 'Код проанализирован',
+ 'conversations.tools.insertRecord.active': 'Добавление записи',
+ 'conversations.tools.insertRecord.done': 'Запись добавлена',
+ 'conversations.tools.runCommand.active': 'Выполнение команды',
+ 'conversations.tools.runCommand.done': 'Команда выполнена',
+ 'conversations.tools.runCode.active': 'Выполнение кода',
+ 'conversations.tools.runCode.done': 'Код выполнен',
+ 'conversations.tools.runPackageManager.active': 'Запуск npm',
+ 'conversations.tools.runPackageManager.done': 'npm выполнен',
+ 'conversations.tools.checkInstalledTools.active': 'Проверка установленных инструментов',
+ 'conversations.tools.checkInstalledTools.done': 'Установленные инструменты проверены',
+ 'conversations.tools.installTool.active': 'Установка инструмента',
+ 'conversations.tools.installTool.done': 'Инструмент установлен',
+ 'conversations.tools.checkTime.active': 'Проверка времени',
+ 'conversations.tools.checkTime.done': 'Время проверено',
+ 'conversations.tools.resolveDate.active': 'Определение даты',
+ 'conversations.tools.resolveDate.done': 'Дата определена',
+ 'conversations.tools.retrieveOutput.active': 'Получение полного вывода',
+ 'conversations.tools.retrieveOutput.done': 'Полный вывод получен',
+ 'conversations.tools.reviewWorkspace.active': 'Обзор рабочей области',
+ 'conversations.tools.reviewWorkspace.done': 'Рабочая область просмотрена',
+ 'conversations.tools.configureProxy.active': 'Настройка прокси',
+ 'conversations.tools.configureProxy.done': 'Прокси настроен',
+ 'conversations.tools.checkUpdates.active': 'Проверка обновлений',
+ 'conversations.tools.checkUpdates.done': 'Обновления проверены',
+ 'conversations.tools.installUpdate.active': 'Установка обновления',
+ 'conversations.tools.installUpdate.done': 'Обновление установлено',
+ 'conversations.tools.sendNotification.active': 'Отправка уведомления',
+ 'conversations.tools.sendNotification.done': 'Уведомление отправлено',
+ 'conversations.tools.reviewToolUsage.active': 'Анализ использования инструментов',
+ 'conversations.tools.reviewToolUsage.done': 'Использование инструментов проанализировано',
+ 'conversations.tools.typeKeys.active': 'Ввод текста',
+ 'conversations.tools.typeKeys.done': 'Текст введён',
+ 'conversations.tools.click.active': 'Нажатие',
+ 'conversations.tools.click.done': 'Нажато',
+ 'conversations.tools.searchWeb.active': 'Поиск в интернете',
+ 'conversations.tools.searchWeb.done': 'Поиск в интернете выполнен',
+ 'conversations.tools.searchNews.active': 'Поиск новостей',
+ 'conversations.tools.searchNews.done': 'Новости найдены',
+ 'conversations.tools.searchImages.active': 'Поиск изображений',
+ 'conversations.tools.searchImages.done': 'Изображения найдены',
+ 'conversations.tools.searchVideos.active': 'Поиск видео',
+ 'conversations.tools.searchVideos.done': 'Видео найдены',
+ 'conversations.tools.findSimilarPages.active': 'Поиск похожих страниц',
+ 'conversations.tools.findSimilarPages.done': 'Похожие страницы найдены',
+ 'conversations.tools.readPages.active': 'Чтение страниц',
+ 'conversations.tools.readPages.done': 'Страницы прочитаны',
+ 'conversations.tools.readWebpage.active': 'Чтение веб-страницы',
+ 'conversations.tools.readWebpage.done': 'Веб-страница прочитана',
+ 'conversations.tools.research.active': 'Исследование',
+ 'conversations.tools.research.done': 'Исследование завершено',
+ 'conversations.tools.enrichData.active': 'Обогащение данных',
+ 'conversations.tools.enrichData.done': 'Данные обогащены',
+ 'conversations.tools.buildDataset.active': 'Создание набора данных',
+ 'conversations.tools.buildDataset.done': 'Набор данных создан',
+ 'conversations.tools.askTheWeb.active': 'Запрос к интернету',
+ 'conversations.tools.askTheWeb.done': 'Запрос к интернету выполнен',
+ 'conversations.tools.browseForYou.active': 'Просмотр сайтов за вас',
+ 'conversations.tools.browseForYou.done': 'Сайты просмотрены за вас',
+ 'conversations.tools.callApi.active': 'Вызов API',
+ 'conversations.tools.callApi.done': 'API вызван',
+ 'conversations.tools.downloadFile.active': 'Загрузка файла',
+ 'conversations.tools.downloadFile.done': 'Файл загружен',
+ 'conversations.tools.makePaidRequest.active': 'Платный запрос',
+ 'conversations.tools.makePaidRequest.done': 'Платный запрос выполнен',
+ 'conversations.tools.searchDocs.active': 'Поиск по документации',
+ 'conversations.tools.searchDocs.done': 'Поиск по документации выполнен',
+ 'conversations.tools.readDocs.active': 'Чтение документации',
+ 'conversations.tools.readDocs.done': 'Документация прочитана',
+ 'conversations.tools.useBrowser.active': 'Работа с браузером',
+ 'conversations.tools.useBrowser.done': 'Браузер использован',
+ 'conversations.tools.openPage.active': 'Открытие страницы',
+ 'conversations.tools.openPage.done': 'Страница открыта',
+ 'conversations.tools.navigate.active': 'Переход',
+ 'conversations.tools.navigate.done': 'Переход выполнен',
+ 'conversations.tools.takeScreenshot.active': 'Создание снимка экрана',
+ 'conversations.tools.takeScreenshot.done': 'Снимок экрана создан',
+ 'conversations.tools.scrollPage.active': 'Прокрутка',
+ 'conversations.tools.scrollPage.done': 'Прокручено',
+ 'conversations.tools.readPage.active': 'Чтение страницы',
+ 'conversations.tools.readPage.done': 'Страница прочитана',
+ 'conversations.tools.analyzeImage.active': 'Анализ изображения',
+ 'conversations.tools.analyzeImage.done': 'Изображение проанализировано',
+ 'conversations.tools.generateImage.active': 'Создание изображения',
+ 'conversations.tools.generateImage.done': 'Изображение создано',
+ 'conversations.tools.generateVideo.active': 'Создание видео',
+ 'conversations.tools.generateVideo.done': 'Видео создано',
+ 'conversations.tools.checkMediaModels.active': 'Проверка медиамоделей',
+ 'conversations.tools.checkMediaModels.done': 'Медиамодели проверены',
+ 'conversations.tools.createDocument.active': 'Создание документа',
+ 'conversations.tools.createDocument.done': 'Документ создан',
+ 'conversations.tools.createPresentation.active': 'Создание презентации',
+ 'conversations.tools.createPresentation.done': 'Презентация создана',
+ 'conversations.tools.generatePodcast.active': 'Создание подкаста',
+ 'conversations.tools.generatePodcast.done': 'Подкаст создан',
+ 'conversations.tools.emailPodcast.active': 'Отправка подкаста по почте',
+ 'conversations.tools.emailPodcast.done': 'Подкаст отправлен по почте',
+ 'conversations.tools.createAndEmailPodcast.active': 'Создание и отправка подкаста по почте',
+ 'conversations.tools.createAndEmailPodcast.done': 'Подкаст создан и отправлен по почте',
+ 'conversations.tools.recallMemories.active': 'Извлечение воспоминаний',
+ 'conversations.tools.recallMemories.done': 'Воспоминания извлечены',
+ 'conversations.tools.saveToMemory.active': 'Сохранение в память',
+ 'conversations.tools.saveToMemory.done': 'Сохранено в память',
+ 'conversations.tools.forgetMemory.active': 'Удаление из памяти',
+ 'conversations.tools.forgetMemory.done': 'Удалено из памяти',
+ 'conversations.tools.searchMemory.active': 'Поиск в памяти',
+ 'conversations.tools.searchMemory.done': 'Поиск в памяти выполнен',
+ 'conversations.tools.inspectMemory.active': 'Проверка памяти',
+ 'conversations.tools.inspectMemory.done': 'Память проверена',
+ 'conversations.tools.exploreMemory.active': 'Изучение памяти',
+ 'conversations.tools.exploreMemory.done': 'Память изучена',
+ 'conversations.tools.saveDocumentToMemory.active': 'Сохранение документа в память',
+ 'conversations.tools.saveDocumentToMemory.done': 'Документ сохранён в память',
+ 'conversations.tools.updateGoals.active': 'Обновление целей',
+ 'conversations.tools.updateGoals.done': 'Цели обновлены',
+ 'conversations.tools.reviewGoals.active': 'Просмотр целей',
+ 'conversations.tools.reviewGoals.done': 'Цели просмотрены',
+ 'conversations.tools.savePreference.active': 'Сохранение предпочтения',
+ 'conversations.tools.savePreference.done': 'Предпочтение сохранено',
+ 'conversations.tools.reviewLearnings.active': 'Просмотр изученного',
+ 'conversations.tools.reviewLearnings.done': 'Изученное просмотрено',
+ 'conversations.tools.updateLearnings.active': 'Обновление изученного',
+ 'conversations.tools.updateLearnings.done': 'Изученное обновлено',
+ 'conversations.tools.delegateTask.active': 'Передача задачи',
+ 'conversations.tools.delegateTask.done': 'Задача передана',
+ 'conversations.tools.runAgentsInParallel.active': 'Параллельный запуск агентов',
+ 'conversations.tools.runAgentsInParallel.done': 'Агенты запущены параллельно',
+ 'conversations.tools.messageAgent.active': 'Отправка сообщения агенту',
+ 'conversations.tools.messageAgent.done': 'Сообщение агенту отправлено',
+ 'conversations.tools.waitForAgent.active': 'Ожидание агента',
+ 'conversations.tools.waitForAgent.done': 'Агент ответил',
+ 'conversations.tools.wait.active': 'Ожидание',
+ 'conversations.tools.wait.done': 'Ожидание завершено',
+ 'conversations.tools.closeAgent.active': 'Закрытие агента',
+ 'conversations.tools.closeAgent.done': 'Агент закрыт',
+ 'conversations.tools.checkAgents.active': 'Проверка агентов',
+ 'conversations.tools.checkAgents.done': 'Агенты проверены',
+ 'conversations.tools.askQuestion.active': 'Вопрос к вам',
+ 'conversations.tools.askQuestion.done': 'Вопрос задан',
+ 'conversations.tools.prepareContext.active': 'Подготовка контекста',
+ 'conversations.tools.prepareContext.done': 'Контекст подготовлен',
+ 'conversations.tools.extractDetails.active': 'Извлечение деталей',
+ 'conversations.tools.extractDetails.done': 'Детали извлечены',
+ 'conversations.tools.planNextSteps.active': 'Планирование следующих шагов',
+ 'conversations.tools.planNextSteps.done': 'Следующие шаги спланированы',
+ 'conversations.tools.reviewWork.active': 'Проверка работы',
+ 'conversations.tools.reviewWork.done': 'Работа проверена',
+ 'conversations.tools.scoutContext.active': 'Сбор контекста',
+ 'conversations.tools.scoutContext.done': 'Контекст собран',
+ 'conversations.tools.useTools.active': 'Использование инструментов',
+ 'conversations.tools.useTools.done': 'Инструменты использованы',
+ 'conversations.tools.checkConnectedApp.active': 'Проверка подключённого приложения',
+ 'conversations.tools.checkConnectedApp.done': 'Подключённое приложение проверено',
+ 'conversations.tools.updateTodos.active': 'Обновление списка задач',
+ 'conversations.tools.updateTodos.done': 'Список задач обновлён',
+ 'conversations.tools.requestPlanReview.active': 'Запрос проверки плана',
+ 'conversations.tools.requestPlanReview.done': 'Проверка плана запрошена',
+ 'conversations.tools.finishPlan.active': 'Завершение плана',
+ 'conversations.tools.finishPlan.done': 'План завершён',
+ 'conversations.tools.setGoal.active': 'Установка цели',
+ 'conversations.tools.setGoal.done': 'Цель установлена',
+ 'conversations.tools.checkGoal.active': 'Проверка цели',
+ 'conversations.tools.checkGoal.done': 'Цель проверена',
+ 'conversations.tools.completeGoal.active': 'Выполнение цели',
+ 'conversations.tools.completeGoal.done': 'Цель выполнена',
+ 'conversations.tools.scheduleTask.active': 'Планирование задачи',
+ 'conversations.tools.scheduleTask.done': 'Задача запланирована',
+ 'conversations.tools.checkSchedules.active': 'Проверка расписаний',
+ 'conversations.tools.checkSchedules.done': 'Расписания проверены',
+ 'conversations.tools.updateSchedule.active': 'Обновление запланированной задачи',
+ 'conversations.tools.updateSchedule.done': 'Запланированная задача обновлена',
+ 'conversations.tools.removeSchedule.active': 'Удаление запланированной задачи',
+ 'conversations.tools.removeSchedule.done': 'Запланированная задача удалена',
+ 'conversations.tools.runScheduledTask.active': 'Запуск запланированной задачи',
+ 'conversations.tools.runScheduledTask.done': 'Запланированная задача выполнена',
+ 'conversations.tools.checkRunHistory.active': 'Проверка истории запусков',
+ 'conversations.tools.checkRunHistory.done': 'История запусков проверена',
+ 'conversations.tools.useApp.active': 'Использование {app}',
+ 'conversations.tools.useApp.done': 'Использовано: {app}',
+ 'conversations.tools.checkAvailableApps.active': 'Проверка доступных приложений',
+ 'conversations.tools.checkAvailableApps.done': 'Доступные приложения проверены',
+ 'conversations.tools.checkConnections.active': 'Проверка ваших подключений',
+ 'conversations.tools.checkConnections.done': 'Ваши подключения проверены',
+ 'conversations.tools.connectApp.active': 'Подключение приложения',
+ 'conversations.tools.connectApp.done': 'Приложение подключено',
+ 'conversations.tools.authorizeApp.active': 'Авторизация приложения',
+ 'conversations.tools.authorizeApp.done': 'Приложение авторизовано',
+ 'conversations.tools.findAppActions.active': 'Поиск действий приложения',
+ 'conversations.tools.findAppActions.done': 'Действия приложения найдены',
+ 'conversations.tools.runAppAction.active': 'Выполнение действия приложения',
+ 'conversations.tools.runAppAction.done': 'Действие приложения выполнено',
+ 'conversations.tools.findTools.active': 'Поиск инструментов',
+ 'conversations.tools.findTools.done': 'Инструменты найдены',
+ 'conversations.tools.useTool.active': 'Использование {tool}',
+ 'conversations.tools.useTool.done': 'Использовано: {tool}',
+ 'conversations.tools.unsubscribe.active': 'Отписка',
+ 'conversations.tools.unsubscribe.done': 'Отписка выполнена',
+ 'conversations.tools.searchPlaces.active': 'Поиск мест',
+ 'conversations.tools.searchPlaces.done': 'Места найдены',
+ 'conversations.tools.lookUpPlace.active': 'Поиск сведений о месте',
+ 'conversations.tools.lookUpPlace.done': 'Сведения о месте найдены',
+ 'conversations.tools.checkMarkets.active': 'Проверка рынков',
+ 'conversations.tools.checkMarkets.done': 'Рынки проверены',
+ 'conversations.tools.placeCall.active': 'Выполнение звонка',
+ 'conversations.tools.placeCall.done': 'Звонок выполнен',
+ 'conversations.tools.checkTaskSources.active': 'Проверка источников задач',
+ 'conversations.tools.checkTaskSources.done': 'Источники задач проверены',
+ 'conversations.tools.updateTaskSources.active': 'Обновление источников задач',
+ 'conversations.tools.updateTaskSources.done': 'Источники задач обновлены',
+ 'conversations.tools.fetchTasks.active': 'Получение задач',
+ 'conversations.tools.fetchTasks.done': 'Задачи получены',
+ 'conversations.tools.checkMcpServers.active': 'Проверка MCP-серверов',
+ 'conversations.tools.checkMcpServers.done': 'MCP-серверы проверены',
+ 'conversations.tools.checkMcpTools.active': 'Проверка инструментов MCP',
+ 'conversations.tools.checkMcpTools.done': 'Инструменты MCP проверены',
+ 'conversations.tools.callMcpTool.active': 'Вызов {tool}',
+ 'conversations.tools.callMcpTool.done': 'Вызвано: {tool}',
+ 'conversations.tools.searchMcpServers.active': 'Поиск MCP-серверов',
+ 'conversations.tools.searchMcpServers.done': 'Поиск MCP-серверов выполнен',
+ 'conversations.tools.connectMcpServer.active': 'Подключение MCP-сервера',
+ 'conversations.tools.connectMcpServer.done': 'MCP-сервер подключён',
+ 'conversations.tools.disconnectMcpServer.active': 'Отключение MCP-сервера',
+ 'conversations.tools.disconnectMcpServer.done': 'MCP-сервер отключён',
+ 'conversations.tools.removeMcpServer.active': 'Удаление MCP-сервера',
+ 'conversations.tools.removeMcpServer.done': 'MCP-сервер удалён',
+ 'conversations.tools.uploadFile.active': 'Отправка файла',
+ 'conversations.tools.uploadFile.done': 'Файл отправлен',
+ 'conversations.tools.listStoredFiles.active': 'Просмотр сохранённых файлов',
+ 'conversations.tools.listStoredFiles.done': 'Сохранённые файлы просмотрены',
+ 'conversations.tools.createShareLink.active': 'Создание ссылки для общего доступа',
+ 'conversations.tools.createShareLink.done': 'Ссылка для общего доступа создана',
+ 'conversations.tools.deleteFile.active': 'Удаление файла',
+ 'conversations.tools.deleteFile.done': 'Файл удалён',
+ 'conversations.tools.updateFileAccess.active': 'Обновление доступа к файлу',
+ 'conversations.tools.updateFileAccess.done': 'Доступ к файлу обновлён',
+ 'conversations.tools.deploySite.active': 'Развёртывание сайта',
+ 'conversations.tools.deploySite.done': 'Сайт развёрнут',
+ 'conversations.tools.checkHosting.active': 'Проверка хостинга',
+ 'conversations.tools.checkHosting.done': 'Хостинг проверен',
+ 'conversations.tools.updateHosting.active': 'Обновление хостинга',
+ 'conversations.tools.updateHosting.done': 'Хостинг обновлён',
+ 'conversations.tools.rollBackDeployment.active': 'Откат развёртывания',
+ 'conversations.tools.rollBackDeployment.done': 'Развёртывание откачено',
+ 'conversations.tools.checkWallet.active': 'Проверка кошелька',
+ 'conversations.tools.checkWallet.done': 'Кошелёк проверен',
+ 'conversations.tools.prepareTransfer.active': 'Подготовка перевода',
+ 'conversations.tools.prepareTransfer.done': 'Перевод подготовлен',
+ 'conversations.tools.checkTransaction.active': 'Проверка транзакции',
+ 'conversations.tools.checkTransaction.done': 'Транзакция проверена',
+ 'conversations.tools.getSwapQuote.active': 'Получение котировки обмена',
+ 'conversations.tools.getSwapQuote.done': 'Котировка обмена получена',
+ 'conversations.tools.swapTokens.active': 'Обмен токенов',
+ 'conversations.tools.swapTokens.done': 'Токены обменяны',
+ 'conversations.tools.getBridgeQuote.active': 'Получение котировки моста',
+ 'conversations.tools.getBridgeQuote.done': 'Котировка моста получена',
+ 'conversations.tools.bridgeTokens.active': 'Перевод токенов через мост',
+ 'conversations.tools.bridgeTokens.done': 'Токены переведены через мост',
+ 'conversations.tools.callDapp.active': 'Вызов контракта приложения',
+ 'conversations.tools.callDapp.done': 'Контракт приложения вызван',
+ 'conversations.tools.useSkill.active': 'Использование навыка',
+ 'conversations.tools.useSkill.done': 'Навык использован',
+ 'conversations.tools.searchSkills.active': 'Поиск навыков',
+ 'conversations.tools.searchSkills.done': 'Навыки найдены',
+ 'conversations.tools.checkSkills.active': 'Проверка навыков',
+ 'conversations.tools.checkSkills.done': 'Навыки проверены',
+ 'conversations.tools.installSkill.active': 'Установка навыка',
+ 'conversations.tools.installSkill.done': 'Навык установлен',
+ 'conversations.tools.removeSkill.active': 'Удаление навыка',
+ 'conversations.tools.removeSkill.done': 'Навык удалён',
+ 'conversations.tools.createSkill.active': 'Создание навыка',
+ 'conversations.tools.createSkill.done': 'Навык создан',
+ 'conversations.tools.runWorkflow.active': 'Запуск рабочего процесса',
+ 'conversations.tools.runWorkflow.done': 'Рабочий процесс выполнен',
+ 'conversations.tools.waitForWorkflow.active': 'Ожидание рабочего процесса',
+ 'conversations.tools.waitForWorkflow.done': 'Рабочий процесс завершён',
+ 'conversations.tools.designWorkflow.active': 'Проектирование рабочего процесса',
+ 'conversations.tools.designWorkflow.done': 'Рабочий процесс спроектирован',
+ 'conversations.tools.saveWorkflow.active': 'Сохранение рабочего процесса',
+ 'conversations.tools.saveWorkflow.done': 'Рабочий процесс сохранён',
+ 'conversations.tools.validateWorkflow.active': 'Проверка рабочего процесса',
+ 'conversations.tools.validateWorkflow.done': 'Рабочий процесс проверен',
+ 'conversations.tools.testWorkflow.active': 'Тестирование рабочего процесса',
+ 'conversations.tools.testWorkflow.done': 'Рабочий процесс протестирован',
+ 'conversations.tools.checkWorkflows.active': 'Проверка рабочих процессов',
+ 'conversations.tools.checkWorkflows.done': 'Рабочие процессы проверены',
+ 'conversations.tools.cancelWorkflow.active': 'Отмена запуска рабочего процесса',
+ 'conversations.tools.cancelWorkflow.done': 'Запуск рабочего процесса отменён',
+ 'conversations.tools.suggestWorkflows.active': 'Подбор рабочих процессов',
+ 'conversations.tools.suggestWorkflows.done': 'Рабочие процессы предложены',
+ 'conversations.tools.checkSettings.active': 'Проверка настроек',
+ 'conversations.tools.checkSettings.done': 'Настройки проверены',
+ 'conversations.tools.checkSecurity.active': 'Проверка безопасности',
+ 'conversations.tools.checkSecurity.done': 'Безопасность проверена',
+ 'conversations.tools.runDiagnostics.active': 'Запуск диагностики',
+ 'conversations.tools.runDiagnostics.done': 'Диагностика выполнена',
+ 'conversations.tools.checkUsageCosts.active': 'Проверка расходов',
+ 'conversations.tools.checkUsageCosts.done': 'Расходы проверены',
+ 'conversations.tools.manageService.active': 'Управление фоновой службой',
+ 'conversations.tools.manageService.done': 'Фоновая служба настроена',
+ 'conversations.tools.readPersona.active': 'Чтение персоны',
+ 'conversations.tools.readPersona.done': 'Персона прочитана',
+ 'conversations.tools.updatePersona.active': 'Обновление персоны',
+ 'conversations.tools.updatePersona.done': 'Персона обновлена',
+ 'conversations.tools.setUpWorkspace.active': 'Настройка рабочей области',
+ 'conversations.tools.setUpWorkspace.done': 'Рабочая область настроена',
+ 'conversations.tools.checkArtifacts.active': 'Проверка артефактов',
+ 'conversations.tools.checkArtifacts.done': 'Артефакты проверены',
+ 'conversations.tools.deleteArtifact.active': 'Удаление артефакта',
+ 'conversations.tools.deleteArtifact.done': 'Артефакт удалён',
'conversations.subagent.noOutput': 'Вывод отсутствует',
'conversations.subagent.close': 'Закрыть',
'conversations.subagent.cancel': 'Отменить задачу',
diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts
index 7d626c29caa..7e7e00e67b2 100644
--- a/app/src/lib/i18n/zh-CN.ts
+++ b/app/src/lib/i18n/zh-CN.ts
@@ -3122,6 +3122,359 @@ const messages: TranslationMap = {
'conversations.subagent.noOutputYet': '暂无输出',
'conversations.subagent.input': '输入',
'conversations.subagent.output': '输出',
+ // Tool-call presentation (features/conversations/tools/toolPhrases.ts).
+ 'conversations.tools.steps.one': '{count} 个步骤',
+ 'conversations.tools.steps.other': '{count} 个步骤',
+ 'conversations.tools.working': '处理中',
+ 'conversations.tools.noOutput': '无输出',
+ 'conversations.tools.delegatedTo': '已委派给 {agent}',
+ 'conversations.tools.openInBrowser': '在浏览器中打开',
+ 'conversations.tools.status.running': '运行中',
+ 'conversations.tools.status.done': '已完成',
+ 'conversations.tools.status.failed': '失败',
+ 'conversations.tools.status.cancelled': '已取消',
+ 'conversations.tools.status.awaiting': '等待输入',
+ 'conversations.tools.search.searching': '正在搜索',
+ 'conversations.tools.search.none': '无结果',
+ 'conversations.tools.search.found.one': '找到 {count} 条结果',
+ 'conversations.tools.search.found.other': '找到 {count} 条结果',
+ 'conversations.tools.search.via': '通过 {provider}',
+ 'conversations.tools.readFile.active': '正在读取文件',
+ 'conversations.tools.readFile.done': '已读取文件',
+ 'conversations.tools.writeFile.active': '正在写入文件',
+ 'conversations.tools.writeFile.done': '已写入文件',
+ 'conversations.tools.editFile.active': '正在编辑文件',
+ 'conversations.tools.editFile.done': '已编辑文件',
+ 'conversations.tools.applyEdits.active': '正在应用编辑',
+ 'conversations.tools.applyEdits.done': '已应用编辑',
+ 'conversations.tools.searchCode.active': '正在搜索代码',
+ 'conversations.tools.searchCode.done': '已搜索代码',
+ 'conversations.tools.findFiles.active': '正在查找文件',
+ 'conversations.tools.findFiles.done': '已查找文件',
+ 'conversations.tools.listFolder.active': '正在列出文件夹',
+ 'conversations.tools.listFolder.done': '已列出文件夹',
+ 'conversations.tools.exportCsv.active': '正在导出 CSV',
+ 'conversations.tools.exportCsv.done': '已导出 CSV',
+ 'conversations.tools.updateMemoryNotes.active': '正在更新记忆笔记',
+ 'conversations.tools.updateMemoryNotes.done': '已更新记忆笔记',
+ 'conversations.tools.runGit.active': '正在运行 git',
+ 'conversations.tools.runGit.done': '已运行 git',
+ 'conversations.tools.readChanges.active': '正在读取更改',
+ 'conversations.tools.readChanges.done': '已读取更改',
+ 'conversations.tools.runLinter.active': '正在运行代码检查',
+ 'conversations.tools.runLinter.done': '已运行代码检查',
+ 'conversations.tools.runTests.active': '正在运行测试',
+ 'conversations.tools.runTests.done': '已运行测试',
+ 'conversations.tools.analyzeCode.active': '正在分析代码',
+ 'conversations.tools.analyzeCode.done': '已分析代码',
+ 'conversations.tools.insertRecord.active': '正在插入记录',
+ 'conversations.tools.insertRecord.done': '已插入记录',
+ 'conversations.tools.runCommand.active': '正在运行命令',
+ 'conversations.tools.runCommand.done': '已运行命令',
+ 'conversations.tools.runCode.active': '正在运行代码',
+ 'conversations.tools.runCode.done': '已运行代码',
+ 'conversations.tools.runPackageManager.active': '正在运行 npm',
+ 'conversations.tools.runPackageManager.done': '已运行 npm',
+ 'conversations.tools.checkInstalledTools.active': '正在检查已安装的工具',
+ 'conversations.tools.checkInstalledTools.done': '已检查已安装的工具',
+ 'conversations.tools.installTool.active': '正在安装工具',
+ 'conversations.tools.installTool.done': '已安装工具',
+ 'conversations.tools.checkTime.active': '正在查看时间',
+ 'conversations.tools.checkTime.done': '已查看时间',
+ 'conversations.tools.resolveDate.active': '正在推算日期',
+ 'conversations.tools.resolveDate.done': '已推算日期',
+ 'conversations.tools.retrieveOutput.active': '正在获取完整输出',
+ 'conversations.tools.retrieveOutput.done': '已获取完整输出',
+ 'conversations.tools.reviewWorkspace.active': '正在查看工作区',
+ 'conversations.tools.reviewWorkspace.done': '已查看工作区',
+ 'conversations.tools.configureProxy.active': '正在配置代理',
+ 'conversations.tools.configureProxy.done': '已配置代理',
+ 'conversations.tools.checkUpdates.active': '正在检查更新',
+ 'conversations.tools.checkUpdates.done': '已检查更新',
+ 'conversations.tools.installUpdate.active': '正在安装更新',
+ 'conversations.tools.installUpdate.done': '已安装更新',
+ 'conversations.tools.sendNotification.active': '正在发送通知',
+ 'conversations.tools.sendNotification.done': '已发送通知',
+ 'conversations.tools.reviewToolUsage.active': '正在查看工具使用情况',
+ 'conversations.tools.reviewToolUsage.done': '已查看工具使用情况',
+ 'conversations.tools.typeKeys.active': '正在输入',
+ 'conversations.tools.typeKeys.done': '已输入',
+ 'conversations.tools.click.active': '正在点击',
+ 'conversations.tools.click.done': '已点击',
+ 'conversations.tools.searchWeb.active': '正在搜索网页',
+ 'conversations.tools.searchWeb.done': '已搜索网页',
+ 'conversations.tools.searchNews.active': '正在搜索新闻',
+ 'conversations.tools.searchNews.done': '已搜索新闻',
+ 'conversations.tools.searchImages.active': '正在搜索图片',
+ 'conversations.tools.searchImages.done': '已搜索图片',
+ 'conversations.tools.searchVideos.active': '正在搜索视频',
+ 'conversations.tools.searchVideos.done': '已搜索视频',
+ 'conversations.tools.findSimilarPages.active': '正在查找相似页面',
+ 'conversations.tools.findSimilarPages.done': '已查找相似页面',
+ 'conversations.tools.readPages.active': '正在阅读页面',
+ 'conversations.tools.readPages.done': '已阅读页面',
+ 'conversations.tools.readWebpage.active': '正在阅读网页',
+ 'conversations.tools.readWebpage.done': '已阅读网页',
+ 'conversations.tools.research.active': '正在研究',
+ 'conversations.tools.research.done': '已研究',
+ 'conversations.tools.enrichData.active': '正在丰富数据',
+ 'conversations.tools.enrichData.done': '已丰富数据',
+ 'conversations.tools.buildDataset.active': '正在构建数据集',
+ 'conversations.tools.buildDataset.done': '已构建数据集',
+ 'conversations.tools.askTheWeb.active': '正在向网络提问',
+ 'conversations.tools.askTheWeb.done': '已向网络提问',
+ 'conversations.tools.browseForYou.active': '正在为你浏览',
+ 'conversations.tools.browseForYou.done': '已为你浏览',
+ 'conversations.tools.callApi.active': '正在调用 API',
+ 'conversations.tools.callApi.done': '已调用 API',
+ 'conversations.tools.downloadFile.active': '正在下载文件',
+ 'conversations.tools.downloadFile.done': '已下载文件',
+ 'conversations.tools.makePaidRequest.active': '正在发起付费请求',
+ 'conversations.tools.makePaidRequest.done': '已发起付费请求',
+ 'conversations.tools.searchDocs.active': '正在搜索文档',
+ 'conversations.tools.searchDocs.done': '已搜索文档',
+ 'conversations.tools.readDocs.active': '正在阅读文档',
+ 'conversations.tools.readDocs.done': '已阅读文档',
+ 'conversations.tools.useBrowser.active': '正在使用浏览器',
+ 'conversations.tools.useBrowser.done': '已使用浏览器',
+ 'conversations.tools.openPage.active': '正在打开页面',
+ 'conversations.tools.openPage.done': '已打开页面',
+ 'conversations.tools.navigate.active': '正在导航',
+ 'conversations.tools.navigate.done': '已导航',
+ 'conversations.tools.takeScreenshot.active': '正在截图',
+ 'conversations.tools.takeScreenshot.done': '已截图',
+ 'conversations.tools.scrollPage.active': '正在滚动',
+ 'conversations.tools.scrollPage.done': '已滚动',
+ 'conversations.tools.readPage.active': '正在阅读页面',
+ 'conversations.tools.readPage.done': '已阅读页面',
+ 'conversations.tools.analyzeImage.active': '正在分析图片',
+ 'conversations.tools.analyzeImage.done': '已分析图片',
+ 'conversations.tools.generateImage.active': '正在生成图片',
+ 'conversations.tools.generateImage.done': '已生成图片',
+ 'conversations.tools.generateVideo.active': '正在生成视频',
+ 'conversations.tools.generateVideo.done': '已生成视频',
+ 'conversations.tools.checkMediaModels.active': '正在检查媒体模型',
+ 'conversations.tools.checkMediaModels.done': '已检查媒体模型',
+ 'conversations.tools.createDocument.active': '正在创建文档',
+ 'conversations.tools.createDocument.done': '已创建文档',
+ 'conversations.tools.createPresentation.active': '正在创建演示文稿',
+ 'conversations.tools.createPresentation.done': '已创建演示文稿',
+ 'conversations.tools.generatePodcast.active': '正在生成播客',
+ 'conversations.tools.generatePodcast.done': '已生成播客',
+ 'conversations.tools.emailPodcast.active': '正在通过邮件发送播客',
+ 'conversations.tools.emailPodcast.done': '已通过邮件发送播客',
+ 'conversations.tools.createAndEmailPodcast.active': '正在创建并通过邮件发送播客',
+ 'conversations.tools.createAndEmailPodcast.done': '已创建并通过邮件发送播客',
+ 'conversations.tools.recallMemories.active': '正在回忆记忆',
+ 'conversations.tools.recallMemories.done': '已回忆记忆',
+ 'conversations.tools.saveToMemory.active': '正在保存到记忆',
+ 'conversations.tools.saveToMemory.done': '已保存到记忆',
+ 'conversations.tools.forgetMemory.active': '正在遗忘记忆',
+ 'conversations.tools.forgetMemory.done': '已遗忘记忆',
+ 'conversations.tools.searchMemory.active': '正在搜索记忆',
+ 'conversations.tools.searchMemory.done': '已搜索记忆',
+ 'conversations.tools.inspectMemory.active': '正在检查记忆',
+ 'conversations.tools.inspectMemory.done': '已检查记忆',
+ 'conversations.tools.exploreMemory.active': '正在浏览记忆',
+ 'conversations.tools.exploreMemory.done': '已浏览记忆',
+ 'conversations.tools.saveDocumentToMemory.active': '正在将文档保存到记忆',
+ 'conversations.tools.saveDocumentToMemory.done': '已将文档保存到记忆',
+ 'conversations.tools.updateGoals.active': '正在更新目标',
+ 'conversations.tools.updateGoals.done': '已更新目标',
+ 'conversations.tools.reviewGoals.active': '正在查看目标',
+ 'conversations.tools.reviewGoals.done': '已查看目标',
+ 'conversations.tools.savePreference.active': '正在保存偏好',
+ 'conversations.tools.savePreference.done': '已保存偏好',
+ 'conversations.tools.reviewLearnings.active': '正在回顾学到的内容',
+ 'conversations.tools.reviewLearnings.done': '已回顾学到的内容',
+ 'conversations.tools.updateLearnings.active': '正在更新学到的内容',
+ 'conversations.tools.updateLearnings.done': '已更新学到的内容',
+ 'conversations.tools.delegateTask.active': '正在委派任务',
+ 'conversations.tools.delegateTask.done': '已委派任务',
+ 'conversations.tools.runAgentsInParallel.active': '正在并行运行智能体',
+ 'conversations.tools.runAgentsInParallel.done': '已并行运行智能体',
+ 'conversations.tools.messageAgent.active': '正在向智能体发送消息',
+ 'conversations.tools.messageAgent.done': '已向智能体发送消息',
+ 'conversations.tools.waitForAgent.active': '正在等待智能体',
+ 'conversations.tools.waitForAgent.done': '已等待智能体',
+ 'conversations.tools.wait.active': '正在等待',
+ 'conversations.tools.wait.done': '已等待',
+ 'conversations.tools.closeAgent.active': '正在关闭智能体',
+ 'conversations.tools.closeAgent.done': '已关闭智能体',
+ 'conversations.tools.checkAgents.active': '正在检查智能体',
+ 'conversations.tools.checkAgents.done': '已检查智能体',
+ 'conversations.tools.askQuestion.active': '正在向你提问',
+ 'conversations.tools.askQuestion.done': '已向你提问',
+ 'conversations.tools.prepareContext.active': '正在准备上下文',
+ 'conversations.tools.prepareContext.done': '已准备上下文',
+ 'conversations.tools.extractDetails.active': '正在提取细节',
+ 'conversations.tools.extractDetails.done': '已提取细节',
+ 'conversations.tools.planNextSteps.active': '正在规划后续步骤',
+ 'conversations.tools.planNextSteps.done': '已规划后续步骤',
+ 'conversations.tools.reviewWork.active': '正在审查工作',
+ 'conversations.tools.reviewWork.done': '已审查工作',
+ 'conversations.tools.scoutContext.active': '正在探查上下文',
+ 'conversations.tools.scoutContext.done': '已探查上下文',
+ 'conversations.tools.useTools.active': '正在使用工具',
+ 'conversations.tools.useTools.done': '已使用工具',
+ 'conversations.tools.checkConnectedApp.active': '正在检查你已连接的应用',
+ 'conversations.tools.checkConnectedApp.done': '已检查你已连接的应用',
+ 'conversations.tools.updateTodos.active': '正在更新待办清单',
+ 'conversations.tools.updateTodos.done': '已更新待办清单',
+ 'conversations.tools.requestPlanReview.active': '正在请求审查计划',
+ 'conversations.tools.requestPlanReview.done': '已请求审查计划',
+ 'conversations.tools.finishPlan.active': '正在完成计划',
+ 'conversations.tools.finishPlan.done': '已完成计划',
+ 'conversations.tools.setGoal.active': '正在设定目标',
+ 'conversations.tools.setGoal.done': '已设定目标',
+ 'conversations.tools.checkGoal.active': '正在检查目标',
+ 'conversations.tools.checkGoal.done': '已检查目标',
+ 'conversations.tools.completeGoal.active': '正在完成目标',
+ 'conversations.tools.completeGoal.done': '已完成目标',
+ 'conversations.tools.scheduleTask.active': '正在安排任务',
+ 'conversations.tools.scheduleTask.done': '已安排任务',
+ 'conversations.tools.checkSchedules.active': '正在检查日程安排',
+ 'conversations.tools.checkSchedules.done': '已检查日程安排',
+ 'conversations.tools.updateSchedule.active': '正在更新计划任务',
+ 'conversations.tools.updateSchedule.done': '已更新计划任务',
+ 'conversations.tools.removeSchedule.active': '正在移除计划任务',
+ 'conversations.tools.removeSchedule.done': '已移除计划任务',
+ 'conversations.tools.runScheduledTask.active': '正在运行计划任务',
+ 'conversations.tools.runScheduledTask.done': '已运行计划任务',
+ 'conversations.tools.checkRunHistory.active': '正在检查运行历史',
+ 'conversations.tools.checkRunHistory.done': '已检查运行历史',
+ 'conversations.tools.useApp.active': '正在使用 {app}',
+ 'conversations.tools.useApp.done': '已使用 {app}',
+ 'conversations.tools.checkAvailableApps.active': '正在检查可用应用',
+ 'conversations.tools.checkAvailableApps.done': '已检查可用应用',
+ 'conversations.tools.checkConnections.active': '正在检查你的连接',
+ 'conversations.tools.checkConnections.done': '已检查你的连接',
+ 'conversations.tools.connectApp.active': '正在连接应用',
+ 'conversations.tools.connectApp.done': '已连接应用',
+ 'conversations.tools.authorizeApp.active': '正在授权应用',
+ 'conversations.tools.authorizeApp.done': '已授权应用',
+ 'conversations.tools.findAppActions.active': '正在查找应用操作',
+ 'conversations.tools.findAppActions.done': '已查找应用操作',
+ 'conversations.tools.runAppAction.active': '正在运行应用操作',
+ 'conversations.tools.runAppAction.done': '已运行应用操作',
+ 'conversations.tools.findTools.active': '正在查找工具',
+ 'conversations.tools.findTools.done': '已查找工具',
+ 'conversations.tools.useTool.active': '正在使用 {tool}',
+ 'conversations.tools.useTool.done': '已使用 {tool}',
+ 'conversations.tools.unsubscribe.active': '正在退订',
+ 'conversations.tools.unsubscribe.done': '已退订',
+ 'conversations.tools.searchPlaces.active': '正在搜索地点',
+ 'conversations.tools.searchPlaces.done': '已搜索地点',
+ 'conversations.tools.lookUpPlace.active': '正在查询地点',
+ 'conversations.tools.lookUpPlace.done': '已查询地点',
+ 'conversations.tools.checkMarkets.active': '正在查看市场行情',
+ 'conversations.tools.checkMarkets.done': '已查看市场行情',
+ 'conversations.tools.placeCall.active': '正在拨打电话',
+ 'conversations.tools.placeCall.done': '已拨打电话',
+ 'conversations.tools.checkTaskSources.active': '正在检查任务来源',
+ 'conversations.tools.checkTaskSources.done': '已检查任务来源',
+ 'conversations.tools.updateTaskSources.active': '正在更新任务来源',
+ 'conversations.tools.updateTaskSources.done': '已更新任务来源',
+ 'conversations.tools.fetchTasks.active': '正在获取任务',
+ 'conversations.tools.fetchTasks.done': '已获取任务',
+ 'conversations.tools.checkMcpServers.active': '正在检查 MCP 服务器',
+ 'conversations.tools.checkMcpServers.done': '已检查 MCP 服务器',
+ 'conversations.tools.checkMcpTools.active': '正在检查 MCP 工具',
+ 'conversations.tools.checkMcpTools.done': '已检查 MCP 工具',
+ 'conversations.tools.callMcpTool.active': '正在调用 {tool}',
+ 'conversations.tools.callMcpTool.done': '已调用 {tool}',
+ 'conversations.tools.searchMcpServers.active': '正在搜索 MCP 服务器',
+ 'conversations.tools.searchMcpServers.done': '已搜索 MCP 服务器',
+ 'conversations.tools.connectMcpServer.active': '正在连接 MCP 服务器',
+ 'conversations.tools.connectMcpServer.done': '已连接 MCP 服务器',
+ 'conversations.tools.disconnectMcpServer.active': '正在断开 MCP 服务器',
+ 'conversations.tools.disconnectMcpServer.done': '已断开 MCP 服务器',
+ 'conversations.tools.removeMcpServer.active': '正在移除 MCP 服务器',
+ 'conversations.tools.removeMcpServer.done': '已移除 MCP 服务器',
+ 'conversations.tools.uploadFile.active': '正在上传文件',
+ 'conversations.tools.uploadFile.done': '已上传文件',
+ 'conversations.tools.listStoredFiles.active': '正在列出已存储的文件',
+ 'conversations.tools.listStoredFiles.done': '已列出已存储的文件',
+ 'conversations.tools.createShareLink.active': '正在创建分享链接',
+ 'conversations.tools.createShareLink.done': '已创建分享链接',
+ 'conversations.tools.deleteFile.active': '正在删除文件',
+ 'conversations.tools.deleteFile.done': '已删除文件',
+ 'conversations.tools.updateFileAccess.active': '正在更新文件访问权限',
+ 'conversations.tools.updateFileAccess.done': '已更新文件访问权限',
+ 'conversations.tools.deploySite.active': '正在部署网站',
+ 'conversations.tools.deploySite.done': '已部署网站',
+ 'conversations.tools.checkHosting.active': '正在检查托管',
+ 'conversations.tools.checkHosting.done': '已检查托管',
+ 'conversations.tools.updateHosting.active': '正在更新托管',
+ 'conversations.tools.updateHosting.done': '已更新托管',
+ 'conversations.tools.rollBackDeployment.active': '正在回滚部署',
+ 'conversations.tools.rollBackDeployment.done': '已回滚部署',
+ 'conversations.tools.checkWallet.active': '正在检查钱包',
+ 'conversations.tools.checkWallet.done': '已检查钱包',
+ 'conversations.tools.prepareTransfer.active': '正在准备转账',
+ 'conversations.tools.prepareTransfer.done': '已准备转账',
+ 'conversations.tools.checkTransaction.active': '正在检查交易',
+ 'conversations.tools.checkTransaction.done': '已检查交易',
+ 'conversations.tools.getSwapQuote.active': '正在获取兑换报价',
+ 'conversations.tools.getSwapQuote.done': '已获取兑换报价',
+ 'conversations.tools.swapTokens.active': '正在兑换代币',
+ 'conversations.tools.swapTokens.done': '已兑换代币',
+ 'conversations.tools.getBridgeQuote.active': '正在获取跨链报价',
+ 'conversations.tools.getBridgeQuote.done': '已获取跨链报价',
+ 'conversations.tools.bridgeTokens.active': '正在跨链转移代币',
+ 'conversations.tools.bridgeTokens.done': '已跨链转移代币',
+ 'conversations.tools.callDapp.active': '正在调用应用合约',
+ 'conversations.tools.callDapp.done': '已调用应用合约',
+ 'conversations.tools.useSkill.active': '正在使用技能',
+ 'conversations.tools.useSkill.done': '已使用技能',
+ 'conversations.tools.searchSkills.active': '正在搜索技能',
+ 'conversations.tools.searchSkills.done': '已搜索技能',
+ 'conversations.tools.checkSkills.active': '正在检查技能',
+ 'conversations.tools.checkSkills.done': '已检查技能',
+ 'conversations.tools.installSkill.active': '正在安装技能',
+ 'conversations.tools.installSkill.done': '已安装技能',
+ 'conversations.tools.removeSkill.active': '正在移除技能',
+ 'conversations.tools.removeSkill.done': '已移除技能',
+ 'conversations.tools.createSkill.active': '正在创建技能',
+ 'conversations.tools.createSkill.done': '已创建技能',
+ 'conversations.tools.runWorkflow.active': '正在运行工作流',
+ 'conversations.tools.runWorkflow.done': '已运行工作流',
+ 'conversations.tools.waitForWorkflow.active': '正在等待工作流',
+ 'conversations.tools.waitForWorkflow.done': '已等待工作流',
+ 'conversations.tools.designWorkflow.active': '正在设计工作流',
+ 'conversations.tools.designWorkflow.done': '已设计工作流',
+ 'conversations.tools.saveWorkflow.active': '正在保存工作流',
+ 'conversations.tools.saveWorkflow.done': '已保存工作流',
+ 'conversations.tools.validateWorkflow.active': '正在验证工作流',
+ 'conversations.tools.validateWorkflow.done': '已验证工作流',
+ 'conversations.tools.testWorkflow.active': '正在测试工作流',
+ 'conversations.tools.testWorkflow.done': '已测试工作流',
+ 'conversations.tools.checkWorkflows.active': '正在检查工作流',
+ 'conversations.tools.checkWorkflows.done': '已检查工作流',
+ 'conversations.tools.cancelWorkflow.active': '正在取消工作流运行',
+ 'conversations.tools.cancelWorkflow.done': '已取消工作流运行',
+ 'conversations.tools.suggestWorkflows.active': '正在推荐工作流',
+ 'conversations.tools.suggestWorkflows.done': '已推荐工作流',
+ 'conversations.tools.checkSettings.active': '正在检查设置',
+ 'conversations.tools.checkSettings.done': '已检查设置',
+ 'conversations.tools.checkSecurity.active': '正在检查安全性',
+ 'conversations.tools.checkSecurity.done': '已检查安全性',
+ 'conversations.tools.runDiagnostics.active': '正在运行诊断',
+ 'conversations.tools.runDiagnostics.done': '已运行诊断',
+ 'conversations.tools.checkUsageCosts.active': '正在检查使用费用',
+ 'conversations.tools.checkUsageCosts.done': '已检查使用费用',
+ 'conversations.tools.manageService.active': '正在管理后台服务',
+ 'conversations.tools.manageService.done': '已管理后台服务',
+ 'conversations.tools.readPersona.active': '正在读取角色设定',
+ 'conversations.tools.readPersona.done': '已读取角色设定',
+ 'conversations.tools.updatePersona.active': '正在更新角色设定',
+ 'conversations.tools.updatePersona.done': '已更新角色设定',
+ 'conversations.tools.setUpWorkspace.active': '正在设置工作区',
+ 'conversations.tools.setUpWorkspace.done': '已设置工作区',
+ 'conversations.tools.checkArtifacts.active': '正在检查工件',
+ 'conversations.tools.checkArtifacts.done': '已检查工件',
+ 'conversations.tools.deleteArtifact.active': '正在删除工件',
+ 'conversations.tools.deleteArtifact.done': '已删除工件',
'conversations.subagent.noOutput': '无输出返回',
'conversations.subagent.close': '关闭',
'conversations.subagent.cancel': '取消任务',
diff --git a/app/src/pages/dev/ToolCallGallery.tsx b/app/src/pages/dev/ToolCallGallery.tsx
new file mode 100644
index 00000000000..d087a146e4e
--- /dev/null
+++ b/app/src/pages/dev/ToolCallGallery.tsx
@@ -0,0 +1,194 @@
+/**
+ * Dev-only gallery of tool-call presentation (`/dev/tools`).
+ *
+ * Renders the chat's tool-call card and assistant-ui tool timeline with
+ * realistic payloads in every state, plus the whole core tool catalog with
+ * each tool's icon and both tenses, so a label or icon regression is visible
+ * at a glance. Registered only in dev builds (see `AppRoutes.tsx`).
+ */
+import { useState } from 'react';
+
+import { ToolTimeline } from '../../components/assistant-ui/elements/tool-timeline';
+import { AssistantUiToolCallCard } from '../../features/conversations/components/AssistantUiToolCall';
+import coreToolNames from '../../features/conversations/tools/__fixtures__/coreToolNames.json';
+import { ToolIcon } from '../../features/conversations/tools/ToolIcon';
+import { describeToolCall, toolLabel } from '../../features/conversations/tools/toolPresentation';
+import { useT } from '../../lib/i18n/I18nContext';
+
+const SEARCH_RESULT = [
+ 'Search results for: rust async traits (via Exa)',
+ '1. Announcing async fn and return-position impl Trait in traits',
+ ' https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html',
+ ' Published: 2023-12-21',
+ ' The Rust Async Working Group is excited to announce major progress.',
+ '2. async-trait crate',
+ ' https://docs.rs/async-trait/latest/async_trait/',
+ ' Type erasure for async trait methods.',
+ '3. Async in traits: the design',
+ ' https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/',
+ '4. Tokio tutorial',
+ ' https://tokio.rs/tokio/tutorial',
+].join('\n');
+
+const SAMPLES = [
+ {
+ toolName: 'web_search_tool',
+ args: { query: 'rust async traits' },
+ result: SEARCH_RESULT,
+ status: 'success' as const,
+ elapsedMs: 1840,
+ },
+ {
+ toolName: 'web_search_tool',
+ args: { query: 'tauri v2 deep links' },
+ status: 'running' as const,
+ },
+ {
+ toolName: 'file_read',
+ args: { path: 'crates/openhuman-core/src/agent/progress.rs' },
+ result: 'pub enum AgentProgress {\n ToolCallStarted { .. },\n}',
+ status: 'success' as const,
+ elapsedMs: 12,
+ },
+ {
+ toolName: 'edit',
+ args: {
+ path: 'app/src/App.tsx',
+ old_string: 'const theme = "light";',
+ new_string: 'const theme = useTheme();\nconst accent = theme.accent;',
+ },
+ result: 'ok',
+ status: 'success' as const,
+ elapsedMs: 40,
+ },
+ {
+ toolName: 'shell',
+ args: { command: 'pnpm test --run tools' },
+ result:
+ ' ✓ toolPresentation.test.ts (22)\n ✓ parseWebSearchResult.test.ts (8)\n\n Test Files 2 passed',
+ status: 'success' as const,
+ elapsedMs: 5230,
+ },
+ {
+ toolName: 'web_fetch',
+ args: { url: 'https://docs.rs/tokio/latest/tokio/' },
+ result:
+ 'status=200 url=https://docs.rs/tokio/latest/tokio/ content=markdown\n# Tokio\n\nA runtime for writing **reliable** asynchronous applications with Rust.',
+ status: 'success' as const,
+ elapsedMs: 620,
+ },
+ {
+ toolName: 'GMAIL_SEND_EMAIL',
+ args: { to: 'alex@example.com', subject: 'Q3 plan' },
+ result: '{"successful":true}',
+ status: 'success' as const,
+ elapsedMs: 910,
+ },
+ {
+ toolName: 'mcp_call_tool',
+ args: { server: 'linear', tool: 'create_issue', arguments: { title: 'Fix labels' } },
+ status: 'running' as const,
+ },
+ {
+ toolName: 'memory',
+ args: { action: 'recall', query: 'preferred meeting times' },
+ result: 'Mornings before 11am.',
+ status: 'success' as const,
+ elapsedMs: 88,
+ },
+ {
+ toolName: 'grep',
+ args: { pattern: 'display_label' },
+ status: 'error' as const,
+ result: 'regex parse error',
+ failure: {
+ class: 'InvalidInput',
+ category: 'Recoverable',
+ recoverable: true,
+ causePlain: 'The search pattern was not a valid regular expression.',
+ nextAction: 'The agent will retry with an escaped pattern.',
+ },
+ },
+ { toolName: 'cron', args: { action: 'add', name: 'Daily digest' }, status: 'cancelled' as const },
+ { toolName: 'some_new_tool', args: { name: 'widget' }, status: 'success' as const, result: 'ok' },
+];
+
+function CatalogRow({ name }: { name: string }) {
+ const { t } = useT();
+ const running = describeToolCall({ name, status: 'running' });
+ const done = describeToolCall({ name, status: 'success' });
+ return (
+
+
+ {name}
+ {toolLabel(running, t)}
+ {toolLabel(done, t)}
+
+ );
+}
+
+export default function ToolCallGallery() {
+ const { t } = useT();
+ const [streaming, setStreaming] = useState(true);
+ return (
+
+
+
+
+
+
+
+ Every state
+ {SAMPLES.map((sample, index) => (
+
+ ))}
+ (approval card renders here)}
+ />
+
+
+
+
+ Core catalog ({(coreToolNames as string[]).length})
+
+
+ {(coreToolNames as string[]).map(name => (
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/app/src/providers/ChatRuntimeProvider.tsx b/app/src/providers/ChatRuntimeProvider.tsx
index dbde30ee9b5..e918e8d036d 100644
--- a/app/src/providers/ChatRuntimeProvider.tsx
+++ b/app/src/providers/ChatRuntimeProvider.tsx
@@ -762,6 +762,11 @@ const ChatRuntimeProvider = ({ children }: { children: React.ReactNode }) => {
success: event.success,
output: event.output,
failure: event.failure,
+ args: event.args,
+ elapsedMs: event.elapsed_ms,
+ structured: event.structured,
+ displayLabel: event.tool_display_label,
+ displayDetail: event.tool_display_detail,
})
);
diff --git a/app/src/providers/assistantUiMessages.ts b/app/src/providers/assistantUiMessages.ts
index bb26a5aeca7..62c57923284 100644
--- a/app/src/providers/assistantUiMessages.ts
+++ b/app/src/providers/assistantUiMessages.ts
@@ -123,6 +123,41 @@ function toolResultPayload(entry: ToolTimelineEntry): unknown {
};
}
+/**
+ * Presentation data that rides a tool part's `artifact`.
+ *
+ * assistant-ui's tool-call part has no slot for a display label, a duration
+ * or a structured result, and this adapter used to drop all three, so the
+ * chat card fell back to guessing a label from the tool name and arguments.
+ * `artifact` is the part's UI-only field, which is exactly this.
+ */
+export interface OpenHumanToolArtifact {
+ kind: 'openhuman-tool';
+ /** Server label, for dynamic tools the client registry cannot describe. */
+ displayName?: string;
+ detail?: string;
+ elapsedMs?: number;
+ structured?: unknown;
+}
+
+export function readOpenHumanToolArtifact(value: unknown): OpenHumanToolArtifact | undefined {
+ if (!value || typeof value !== 'object') return undefined;
+ return (value as { kind?: unknown }).kind === 'openhuman-tool'
+ ? (value as OpenHumanToolArtifact)
+ : undefined;
+}
+
+function toolArtifact(entry: ToolTimelineEntry): OpenHumanToolArtifact | undefined {
+ const artifact: OpenHumanToolArtifact = {
+ kind: 'openhuman-tool',
+ ...(entry.displayName ? { displayName: entry.displayName } : {}),
+ ...(entry.detail ? { detail: entry.detail } : {}),
+ ...(entry.elapsedMs !== undefined ? { elapsedMs: entry.elapsedMs } : {}),
+ ...(entry.structured !== undefined ? { structured: entry.structured } : {}),
+ };
+ return Object.keys(artifact).length > 1 ? artifact : undefined;
+}
+
function toolPart(entry: ToolTimelineEntry): ThreadAssistantMessagePart {
const running = isActiveTimelineStatus(entry.status);
const isSubagent = entry.name.startsWith('subagent:') || entry.subagent !== undefined;
@@ -140,6 +175,7 @@ function toolPart(entry: ToolTimelineEntry): ThreadAssistantMessagePart {
toolName: isSubagent ? 'task' : entry.name,
args,
argsText: JSON.stringify(args, null, 2),
+ ...(!isSubagent && toolArtifact(entry) ? { artifact: toolArtifact(entry) } : {}),
...(!running
? {
result: isSubagent
diff --git a/app/src/services/chatService.ts b/app/src/services/chatService.ts
index 4857b2c931a..3039e3edfbd 100644
--- a/app/src/services/chatService.ts
+++ b/app/src/services/chatService.ts
@@ -59,6 +59,18 @@ export interface ChatToolResultEvent {
* `parseToolFailure` before it reaches the store.
*/
failure?: unknown;
+ /** The call's arguments. The start event may carry none; this is the fallback. */
+ args?: unknown;
+ /** Wall time the call took. */
+ elapsed_ms?: number;
+ /**
+ * Machine-readable result, when the tool produced one (the tool's
+ * `ToolResult.metadata`), e.g. `{ kind: "web_search", results: [...] }`.
+ */
+ structured?: unknown;
+ /** Label / detail recomputed by the core with the call's real arguments. */
+ tool_display_label?: string;
+ tool_display_detail?: string;
}
/** One sub-agent's token/cost contribution within a turn (hover breakdown). */
diff --git a/app/src/store/chatRuntimeSlice.ts b/app/src/store/chatRuntimeSlice.ts
index 551474a9660..623e65db528 100644
--- a/app/src/store/chatRuntimeSlice.ts
+++ b/app/src/store/chatRuntimeSlice.ts
@@ -285,6 +285,37 @@ export function parseToolFailure(raw: unknown): ToolFailureExplanation | undefin
};
}
+/**
+ * Fold the optional completion fields of a `tool_result` into its row.
+ *
+ * The core's start event may carry no arguments (the harness reports them at
+ * completion), so `args` backfills an empty `argsBuffer`; without it a row
+ * could never show its target. A recomputed server label replaces the one
+ * sent at start, which was derived without arguments.
+ */
+function applyResultExtras(
+ entry: ToolTimelineEntry,
+ extras: {
+ args?: unknown;
+ elapsedMs?: number;
+ structured?: unknown;
+ displayLabel?: string;
+ displayDetail?: string;
+ }
+): void {
+ if (!entry.argsBuffer && extras.args && typeof extras.args === 'object') {
+ entry.argsBuffer = JSON.stringify(extras.args);
+ }
+ if (typeof extras.elapsedMs === 'number' && Number.isFinite(extras.elapsedMs)) {
+ entry.elapsedMs = extras.elapsedMs;
+ }
+ if (extras.structured && typeof extras.structured === 'object') {
+ entry.structured = extras.structured;
+ }
+ if (extras.displayLabel?.trim()) entry.displayName = extras.displayLabel.trim();
+ if (extras.displayDetail?.trim()) entry.detail = extras.displayDetail.trim();
+}
+
/**
* Attach a human label/detail to a tool-timeline row. The server supplies a
* label/detail for dynamic Composio/MCP/integration tools the client can't know
@@ -293,11 +324,15 @@ export function parseToolFailure(raw: unknown): ToolFailureExplanation | undefin
* caller that materialises a row.
*/
function decorateEntry(entry: ToolTimelineEntry): ToolTimelineEntry {
+ // `displayName` holds only what the server said. Baking the client title in
+ // here froze its tense at call time, so a finished row kept reading
+ // "Reading file"; every surface now resolves the title at render time.
const formatted = formatTimelineEntry(entry);
if (entry.displayName && !isKnownClientTool(entry.name)) {
- return { ...entry, displayName: entry.displayName, detail: entry.detail ?? formatted.detail };
+ return { ...entry, detail: entry.detail ?? formatted.detail };
}
- return { ...entry, displayName: formatted.title, detail: formatted.detail ?? entry.detail };
+ const { displayName: _serverLabel, ...rest } = entry;
+ return { ...rest, detail: entry.detail ?? formatted.detail };
}
/**
@@ -368,6 +403,15 @@ export interface ToolTimelineEntry {
* and on rows from cores that predate output forwarding.
*/
result?: string;
+ /**
+ * Machine-readable result the core attached to `tool_result` as
+ * `structured` (today `{ kind: "web_search", query, provider, results }`).
+ * Lets a rich renderer skip re-parsing `result` text. Absent on rows from
+ * older cores, which fall back to parsing.
+ */
+ structured?: unknown;
+ /** Wall time the call took, from `tool_result.elapsed_ms`. */
+ elapsedMs?: number;
}
export interface StreamingAssistantState {
@@ -1365,6 +1409,11 @@ const chatRuntimeSlice = createSlice({
success: boolean;
output?: string;
failure?: unknown;
+ args?: unknown;
+ elapsedMs?: number;
+ structured?: unknown;
+ displayLabel?: string;
+ displayDetail?: string;
}>
) => {
const { threadId, round, toolName, success, output, failure } = action.payload;
@@ -1381,12 +1430,16 @@ const chatRuntimeSlice = createSlice({
// The core forwards the (size-capped) tool result text on `output`; accept
// only non-empty payloads so a stub-less row stays `undefined`.
const result = output && output.length > 0 ? output : undefined;
+ const settle = (entry: ToolTimelineEntry) => {
+ entry.status = status;
+ entry.failure = parsedFailure;
+ entry.result = result;
+ applyResultExtras(entry, action.payload);
+ };
if (toolCallId) {
const entry = entries.find(e => e.id === toolCallId);
if (entry) {
- entry.status = status;
- entry.failure = parsedFailure;
- entry.result = result;
+ settle(entry);
return;
}
}
@@ -1400,9 +1453,7 @@ const chatRuntimeSlice = createSlice({
for (let i = 0; i < entries.length; i += 1) {
const entry = entries[i];
if (entry.status === 'running' && entry.name === toolName && entry.round === round) {
- entry.status = status;
- entry.failure = parsedFailure;
- entry.result = result;
+ settle(entry);
return;
}
}
diff --git a/app/src/utils/__tests__/toolTimelineFormatting.test.ts b/app/src/utils/__tests__/toolTimelineFormatting.test.ts
index 5fbbdb0d2ea..51b6ece07a9 100644
--- a/app/src/utils/__tests__/toolTimelineFormatting.test.ts
+++ b/app/src/utils/__tests__/toolTimelineFormatting.test.ts
@@ -27,10 +27,7 @@ describe('formatTimelineEntry', () => {
argsBuffer: JSON.stringify({ prompt: 'Find the project brief in Notion.' }),
})
)
- ).toEqual({
- title: 'Working in your Notion workspace',
- detail: 'Find the project brief in Notion.',
- });
+ ).toEqual({ title: 'Using Notion', detail: 'Find the project brief in Notion.' });
});
it('formats spawn_subagent for integrations_agent from toolkit args', () => {
@@ -47,7 +44,7 @@ describe('formatTimelineEntry', () => {
})
)
).toEqual({
- title: 'Making requests to your Gmail account',
+ title: 'Using Gmail',
detail:
'Get my 5 most recent emails. Show subject, sender, date, and a short preview for each.',
});
@@ -62,10 +59,7 @@ describe('formatTimelineEntry', () => {
detail: 'Search Notion for the latest roadmap.',
})
)
- ).toEqual({
- title: 'Working in your Notion workspace',
- detail: 'Search Notion for the latest roadmap.',
- });
+ ).toEqual({ title: 'Using Notion', detail: 'Search Notion for the latest roadmap.' });
});
it('labels a direct connected-service action by its provider', () => {
@@ -73,17 +67,21 @@ describe('formatTimelineEntry', () => {
formatTimelineEntry(
entry({ name: 'GMAIL_SEND_EMAIL', argsBuffer: JSON.stringify({ to: 'alex@example.com' }) })
)
- ).toEqual({ title: 'Making requests to your Gmail account', detail: 'Send email' });
+ ).toEqual({ title: 'Using Gmail', detail: 'Send email · alex@example.com' });
expect(formatTimelineEntry(entry({ name: 'GOOGLE_CALENDAR_CREATE_EVENT' }))).toEqual({
- title: 'Updating your Google Calendar',
+ title: 'Using Google Calendar',
detail: 'Create event',
});
});
- it('keeps the generic label for upper-case names on unknown toolkits', () => {
+ it('never shouts an upper-case action slug, known toolkit or not', () => {
expect(formatTimelineEntry(entry({ name: 'STRIPE_LIST_CHARGES' }))).toEqual({
- title: 'STRIPE LIST CHARGES',
- detail: undefined,
+ title: 'Using Stripe',
+ detail: 'List charges',
+ });
+ expect(formatTimelineEntry(entry({ name: 'ACME_DO_THING' }))).toEqual({
+ title: 'Using Acme',
+ detail: 'Do thing',
});
});
@@ -98,10 +96,7 @@ describe('formatTimelineEntry', () => {
}),
})
)
- ).toEqual({
- title: 'Making requests to your GitHub account',
- detail: 'List my open pull requests in GitHub.',
- });
+ ).toEqual({ title: 'Using GitHub', detail: 'List my open pull requests in GitHub.' });
});
it('falls back to humanized generic labels for non-integration subagents', () => {
@@ -113,7 +108,7 @@ describe('formatTimelineEntry', () => {
it('formats composio_list_connections with user-facing copy', () => {
expect(formatTimelineEntry(entry({ name: 'composio_list_connections' }))).toEqual({
- title: 'Viewing your Connections',
+ title: 'Checking your connections',
detail: undefined,
});
});
@@ -126,7 +121,7 @@ describe('formatTimelineEntry', () => {
).toEqual({ title: 'Running command', detail: 'cargo test --lib' });
});
- it('formats web_fetch with hostname in title', () => {
+ it('formats web_fetch with the page as detail', () => {
expect(
formatTimelineEntry(
entry({
@@ -134,21 +129,18 @@ describe('formatTimelineEntry', () => {
argsBuffer: JSON.stringify({ url: 'https://docs.example.com/api/v2/users' }),
})
)
- ).toEqual({
- title: 'Fetching docs.example.com',
- detail: 'https://docs.example.com/api/v2/users',
- });
+ ).toEqual({ title: 'Reading webpage', detail: 'docs.example.com/api/v2/users' });
});
- it('formats web_search with query in title', () => {
+ it('formats web_search with the query as detail', () => {
expect(
formatTimelineEntry(
entry({ name: 'web_search', argsBuffer: JSON.stringify({ query: 'rust async trait' }) })
)
- ).toEqual({ title: 'Searching: rust async trait' });
+ ).toEqual({ title: 'Searching the web', detail: 'rust async trait' });
});
- it('attributes a completed web_search to the resolved provider', () => {
+ it('settles a completed web_search into the past tense (provider shows in the search element)', () => {
expect(
formatTimelineEntry(
entry({
@@ -158,10 +150,10 @@ describe('formatTimelineEntry', () => {
result: 'Search results for: rust async trait (via Exa)\n1. Some title\n https://x.dev',
})
)
- ).toEqual({ title: 'Searched with Exa', detail: 'rust async trait' });
+ ).toEqual({ title: 'Searched the web', detail: 'rust async trait' });
});
- it('reflects a different provider from the result (attribution is dynamic)', () => {
+ it('settles whichever provider served the search', () => {
expect(
formatTimelineEntry(
entry({
@@ -171,7 +163,7 @@ describe('formatTimelineEntry', () => {
result: 'Search results for: weather (via Brave)\n1. Forecast',
})
)
- ).toEqual({ title: 'Searched with Brave', detail: 'weather' });
+ ).toEqual({ title: 'Searched the web', detail: 'weather' });
});
it('keeps the running label when no result is present yet', () => {
@@ -183,7 +175,7 @@ describe('formatTimelineEntry', () => {
argsBuffer: JSON.stringify({ query: 'rust async trait' }),
})
)
- ).toEqual({ title: 'Searching: rust async trait' });
+ ).toEqual({ title: 'Searching the web', detail: 'rust async trait' });
});
// `web_search_tool` is the name the core actually registers and streams for
@@ -198,7 +190,7 @@ describe('formatTimelineEntry', () => {
argsBuffer: JSON.stringify({ query: 'rust async trait' }),
})
)
- ).toEqual({ title: 'Searching: rust async trait' });
+ ).toEqual({ title: 'Searching the web', detail: 'rust async trait' });
});
it('attributes a completed web_search_tool from the markdown result', () => {
@@ -213,7 +205,7 @@ describe('formatTimelineEntry', () => {
result: '# Search results — `rust async trait` (via Exa)\n\n## [T](https://x.dev)',
})
)
- ).toEqual({ title: 'Searched with Exa', detail: 'rust async trait' });
+ ).toEqual({ title: 'Searched the web', detail: 'rust async trait' });
});
it('attributes a completed web_search_tool that returned no results', () => {
@@ -226,7 +218,7 @@ describe('formatTimelineEntry', () => {
result: '_No results for `zzzz`_ (via Exa)',
})
)
- ).toEqual({ title: 'Searched with Exa', detail: 'zzzz' });
+ ).toEqual({ title: 'Searched the web', detail: 'zzzz' });
});
it('formats file_read with shortened path', () => {
@@ -256,7 +248,7 @@ describe('formatTimelineEntry', () => {
formatTimelineEntry(
entry({ name: 'grep', argsBuffer: JSON.stringify({ pattern: 'SubagentSpawned' }) })
)
- ).toEqual({ title: 'Searching: SubagentSpawned' });
+ ).toEqual({ title: 'Searching code', detail: 'SubagentSpawned' });
});
it('formats git_operations with subcommand', () => {
@@ -264,7 +256,7 @@ describe('formatTimelineEntry', () => {
formatTimelineEntry(
entry({ name: 'git_operations', argsBuffer: JSON.stringify({ command: 'diff --stat' }) })
)
- ).toEqual({ title: 'Git diff', detail: 'diff --stat' });
+ ).toEqual({ title: 'Running git', detail: 'diff --stat' });
});
it('formats glob with pattern detail', () => {
@@ -272,7 +264,7 @@ describe('formatTimelineEntry', () => {
formatTimelineEntry(
entry({ name: 'glob', argsBuffer: JSON.stringify({ pattern: '**/*.test.ts' }) })
)
- ).toEqual({ title: 'Finding: **/*.test.ts' });
+ ).toEqual({ title: 'Finding files', detail: '**/*.test.ts' });
});
it('formats list with directory path', () => {
@@ -283,10 +275,10 @@ describe('formatTimelineEntry', () => {
argsBuffer: JSON.stringify({ path: 'crates/openhuman-core/src/tools' }),
})
)
- ).toEqual({ title: 'Listing directory', detail: '…/src/tools' });
+ ).toEqual({ title: 'Listing folder', detail: '…/src/tools' });
});
- it('formats browser_open with hostname', () => {
+ it('formats browser_open with the page as detail', () => {
expect(
formatTimelineEntry(
entry({
@@ -294,7 +286,7 @@ describe('formatTimelineEntry', () => {
argsBuffer: JSON.stringify({ url: 'https://github.com/tinyhumansai/openhuman' }),
})
)
- ).toEqual({ title: 'Browsing github.com' });
+ ).toEqual({ title: 'Opening page', detail: 'github.com/tinyhumansai/openhuman' });
});
});
@@ -342,16 +334,16 @@ describe('extractSearchProvider', () => {
describe('formatToolName', () => {
it('returns human-readable names for known tools', () => {
expect(formatToolName('shell')).toBe('Running command');
- expect(formatToolName('web_fetch')).toBe('Fetching');
+ expect(formatToolName('web_fetch')).toBe('Reading webpage');
expect(formatToolName('file_read')).toBe('Reading file');
expect(formatToolName('edit')).toBe('Editing file');
expect(formatToolName('grep')).toBe('Searching code');
- expect(formatToolName('git_operations')).toBe('Git operation');
- expect(formatToolName('lsp')).toBe('Code intelligence');
+ expect(formatToolName('git_operations')).toBe('Running git');
+ expect(formatToolName('lsp')).toBe('Analyzing code');
});
- it('falls back to humanized identifier for unknown tools', () => {
- expect(formatToolName('custom_fancy_tool')).toBe('Custom Fancy Tool');
+ it('falls back to a sentence-cased activity for unknown tools', () => {
+ expect(formatToolName('custom_fancy_tool')).toBe('Using custom fancy tool');
});
});
@@ -387,9 +379,10 @@ describe('isKnownClientTool', () => {
expect(isKnownClientTool('web_search_tool')).toBe(true);
});
- it('does not recognize dynamic Composio/MCP actions (server labels them)', () => {
- expect(isKnownClientTool('GMAIL_SEND_EMAIL')).toBe(false);
- expect(isKnownClientTool('composio_notion_create_page')).toBe(false);
+ it('recognizes Composio actions by their toolkit, and nothing it cannot describe', () => {
+ // The registry names a Composio action by its app ("Used Gmail"), so the
+ // core's sentence-cased slug does not override it.
+ expect(isKnownClientTool('GMAIL_SEND_EMAIL')).toBe(true);
expect(isKnownClientTool('some_random_mcp_tool')).toBe(false);
});
});
@@ -407,25 +400,25 @@ describe('summarizeToolGroup', () => {
entry({ id: 'a', name: 'file_read' }),
entry({ id: 'b', name: 'file_read' }),
])
- ).toBe('Read 2 files');
+ ).toBe('2 steps · Read file ×2');
});
- it('joins distinct category phrases for a mixed group', () => {
+ it('lists the distinct steps of a mixed group, most frequent first', () => {
expect(
summarizeToolGroup([
entry({ id: 'a', name: 'file_write' }),
entry({ id: 'b', name: 'shell' }),
entry({ id: 'c', name: 'shell' }),
])
- ).toBe('Edited 1 file, ran 2 commands');
+ ).toBe('3 steps · Ran command ×2, Wrote file');
});
});
describe('categorizeTool', () => {
it('maps tools (incl. subagent-prefixed) to a category', () => {
- expect(categorizeTool('grep')).toBe('search');
- expect(categorizeTool('subagent:web_fetch')).toBe('fetch');
- expect(categorizeTool('GMAIL_SEND_EMAIL')).toBe('other');
+ expect(categorizeTool('grep')).toBe('code');
+ expect(categorizeTool('subagent:web_fetch')).toBe('web');
+ expect(categorizeTool('GMAIL_SEND_EMAIL')).toBe('app');
});
it('categorizes the canonical web-search name, not only its settings id', () => {
@@ -436,9 +429,9 @@ describe('categorizeTool', () => {
// was the one place that had not, so a real search row categorized as
// `other` — wrong icon and wrong group summary in the rail, and (since
// #6169) a row kept on the main transcript that belongs in the rail.
- expect(categorizeTool('web_search_tool')).toBe('search');
- expect(categorizeTool('web_search')).toBe('search');
- expect(categorizeTool('subagent:web_search_tool')).toBe('search');
+ expect(categorizeTool('web_search_tool')).toBe('web');
+ expect(categorizeTool('web_search')).toBe('web');
+ expect(categorizeTool('subagent:web_search_tool')).toBe('web');
});
});
@@ -462,7 +455,7 @@ describe('buildProcessingBlocks', () => {
expect(blocks.map(b => b.kind)).toEqual(['thinking', 'narration', 'toolGroup', 'narration']);
const group = blocks[2];
if (group.kind !== 'toolGroup') throw new Error('expected toolGroup');
- expect(group.summary).toBe('Read 2 files');
+ expect(group.summary).toBe('2 steps · Read file ×2');
expect(group.entries).toHaveLength(2);
});
diff --git a/app/src/utils/toolTimelineFormatting.ts b/app/src/utils/toolTimelineFormatting.ts
index 966e225d86f..bd6c06ab79f 100644
--- a/app/src/utils/toolTimelineFormatting.ts
+++ b/app/src/utils/toolTimelineFormatting.ts
@@ -1,169 +1,58 @@
+/**
+ * Timeline-row formatting for tool calls.
+ *
+ * Every label, icon and category is resolved by the tool presentation
+ * registry (`features/conversations/tools/toolPresentation.ts`); this module
+ * adapts {@link ToolTimelineEntry} rows onto it and keeps the timeline-only
+ * helpers (processing blocks, sources, envelope stripping).
+ */
+import {
+ extractSearchProvider,
+ parseWebSearchResult,
+} from '../features/conversations/tools/parseWebSearchResult';
+import { fillPlaceholders } from '../features/conversations/tools/toolPhrases';
+import {
+ describeToolCall,
+ type ToolCallPresentation,
+ type ToolCategory,
+ toolLabel,
+ type Translate,
+} from '../features/conversations/tools/toolPresentation';
import type { ToolTimelineEntry } from '../store/chatRuntimeSlice';
import type { PersistedTranscriptItem } from '../types/turnState';
-interface ParsedToolArgs {
- agent_id?: string;
- prompt?: string;
- toolkit?: string;
- command?: string;
- url?: string;
- path?: string;
- file_path?: string;
- pattern?: string;
- query?: string;
- tool_name?: string;
- question?: string;
+export type { ToolCategory, Translate };
+export { extractSearchProvider };
+
+/** Resolve a timeline row through the registry. */
+export function presentTimelineEntry(entry: ToolTimelineEntry): ToolCallPresentation {
+ return describeToolCall({
+ name: entry.name,
+ args: entry.argsBuffer,
+ status: entry.status,
+ serverLabel: entry.displayName,
+ serverDetail: entry.detail,
+ toolkitHint: entry.sourceToolName,
+ });
}
-const TOOL_DISPLAY_NAMES: Record = {
- shell: 'Running command',
- node_exec: 'Running command',
- npm_exec: 'Running command',
- web_fetch: 'Fetching',
- http_request: 'Fetching',
- curl: 'Fetching',
- web_search: 'Searching the web',
- // The name the core actually registers and streams for the canonical search
- // slot, whichever engine owns it (`crates/openhuman-core/src/search/registry.rs`).
- // `web_search` above is the settings-family id, which never reaches a
- // timeline row — without this entry a real search rendered as the
- // humanized "Web Search Tool".
- web_search_tool: 'Searching the web',
- // The harness's tool-discovery bridge — NOT a web search.
- // `vendor/tinyagents/crates/tinyagents-harness/src/tool/discover/bridge.rs`
- // advertises two intrinsics: `tool_search` ranks the *deferred tool
- // catalogue* (Composio actions, MCP tools) and `tool_call` invokes a hit by
- // name. Neither reaches the network.
- //
- // They are `ToolSchema` values rather than `Tool` impls, so they carry no
- // server `display_label` and there is nothing upstream to override — the
- // label is the client's to get right. Without these entries `tool_search`
- // fell through to a substring heuristic that fires on "search", and a
- // Composio calendar fetch rendered as "Searched the web".
- tool_search: 'Finding the right tool',
- tool_call: 'Using a tool',
- gitbooks_search: 'Searching docs',
- file_read: 'Reading file',
- file_write: 'Writing file',
- edit: 'Editing file',
- apply_patch: 'Applying patch',
- grep: 'Searching code',
- glob: 'Finding files',
- list: 'Listing directory',
- read_diff: 'Reading diff',
- git_operations: 'Git operation',
- browser: 'Browsing',
- browser_open: 'Opening browser',
- image_info: 'Analyzing image',
- install_tool: 'Installing tool',
- lsp: 'Code intelligence',
- keyboard: 'Typing',
- mouse: 'Clicking',
- csv_export: 'Exporting CSV',
- update_memory_md: 'Updating memory',
- read_workspace_state: 'Reading workspace',
- current_time: 'Checking time',
- schedule: 'Scheduling',
- detect_tools: 'Detecting tools',
- tool_stats: 'Tool statistics',
- vault_write_markdown: 'Writing to vault',
- run_linter: 'Running linter',
- run_tests: 'Running tests',
- proxy_config: 'Configuring proxy',
- update_check: 'Checking for updates',
- update_apply: 'Applying update',
- pushover: 'Sending notification',
- insert_sql_record: 'Inserting record',
- mcp_list_servers: 'Listing MCP servers',
- mcp_list_tools: 'Listing MCP tools',
- mcp_call_tool: 'Calling MCP tool',
- gmail_unsubscribe: 'Unsubscribing',
- gitbooks_get_page: 'Reading docs page',
- audio_generate_podcast: 'Generating podcast',
- audio_email_podcast: 'Emailing podcast',
- audio_generate_and_email_podcast: 'Generating & emailing podcast',
- composio_list_connections: 'Viewing your Connections',
- agent_prepare_context: 'Preparing context',
- propose_workflow: 'Proposing workflow',
- // Harness work state: the session todo list and the thread goal. The pane
- // renders both from these calls' results (`utils/harnessState.ts`), so the
- // rows read as bookkeeping, not as work in their own right.
- todo: 'Updating todo list',
- goal_set: 'Setting goal',
- goal_get: 'Checking goal',
- goal_complete: 'Completing goal',
-};
-
/**
- * Format a raw tool name into a short human-readable label.
- * Used for subagent child tool rows and sub-mascot activity text.
+ * Present-tense label for a bare tool name ("Searching the web"). Used where
+ * only the name is known: sub-agent child rows and the mascot's activity line.
*/
-export function formatToolName(toolName: string | undefined): string {
+export function formatToolName(toolName: string | undefined, t?: Translate): string {
if (!toolName) return '';
- return TOOL_DISPLAY_NAMES[toolName] ?? humanizeIdentifier(toolName);
+ return toolLabel(describeToolCall({ name: toolName, status: 'running' }), t);
}
/**
- * The fixed set of built-in / special tools this client formatter labels
- * well on its own (with args-aware detail). For these, the client label is
- * authoritative and a server-supplied `display_label` is ignored — the
- * server label only wins for *dynamic* tools (Composio/MCP/integration
- * actions) the client can't possibly know, which is where raw `snake_case`
- * used to leak through. Keep in sync with {@link formatTimelineEntry} /
- * {@link formatToolDetail}.
- */
-const CLIENT_KNOWN_TOOLS = new Set([
- ...Object.keys(TOOL_DISPLAY_NAMES),
- // args-aware built-ins handled by formatToolDetail()
- 'shell',
- 'node_exec',
- 'npm_exec',
- 'web_fetch',
- 'http_request',
- 'curl',
- 'web_search',
- 'web_search_tool',
- 'gitbooks_search',
- 'file_read',
- 'file_write',
- 'vault_write_markdown',
- 'edit',
- 'apply_patch',
- 'grep',
- 'glob',
- 'list',
- 'git_operations',
- 'browser',
- 'browser_open',
- 'image_info',
- 'install_tool',
- 'lsp',
- 'run_tests',
- 'run_linter',
- 'read_diff',
- // special-cased agent / integration rows
- 'spawn_subagent',
- 'integrations_agent',
- 'researcher',
- 'agent_prepare_context',
- 'context_scout',
- 'composio_list_connections',
- 'orchestrator',
- 'critic',
- 'tools_agent',
- 'code_executor',
-]);
-
-/**
- * Whether the client formatter recognizes this tool (so its label should win
- * over any server-supplied one). True for built-ins, the special agent rows,
- * and the `subagent:` / `delegate_` families that {@link formatTimelineEntry}
- * handles explicitly.
+ * Whether the registry describes this tool on its own. For these the client
+ * label is authoritative and a server `display_label` is ignored; the server
+ * label wins only for dynamic tools the registry cannot know.
*/
export function isKnownClientTool(name: string): boolean {
- return (
- name.startsWith('subagent:') || name.startsWith('delegate_') || CLIENT_KNOWN_TOOLS.has(name)
- );
+ const { source } = describeToolCall({ name });
+ return source !== 'server' && source !== 'fallback';
}
/**
@@ -181,185 +70,86 @@ export function stripToolCallEnvelopes(text: string | undefined | null): string
.replace(/]*>[\s\S]*$/i, '');
}
-/** Broad activity category for a tool, used to group + icon timeline rows. */
-export type ToolCategory = 'read' | 'write' | 'search' | 'run' | 'fetch' | 'browse' | 'other';
-
-const TOOL_CATEGORIES: Record = {
- file_read: 'read',
- list: 'read',
- read_diff: 'read',
- file_write: 'write',
- vault_write_markdown: 'write',
- edit: 'write',
- apply_patch: 'write',
- grep: 'search',
- glob: 'search',
- // `web_search_tool` is the runtime tool name; `web_search` is only the UI
- // toggle id the core expands from (`tools/user_filter.rs:79-80`, and
- // `test/e2e/specs/harness-search-tool-flow.spec.ts:10` says so outright).
- // Both are mapped: the toggle id never reaches a timeline row, but leaving
- // it out would break any older snapshot that recorded the alias.
- web_search: 'search',
- web_search_tool: 'search',
- gitbooks_search: 'search',
- gitbooks_get_page: 'read',
- shell: 'run',
- node_exec: 'run',
- npm_exec: 'run',
- run_tests: 'run',
- run_linter: 'run',
- git_operations: 'run',
- web_fetch: 'fetch',
- http_request: 'fetch',
- curl: 'fetch',
- browser: 'browse',
- browser_open: 'browse',
-};
-
/** Categorize a (possibly `subagent:`-prefixed) tool name for grouping/icons. */
export function categorizeTool(name: string): ToolCategory {
- const base = name.replace(/^subagent:/, '');
- return TOOL_CATEGORIES[base] ?? 'other';
+ return describeToolCall({ name }).category;
}
-/** Plural-aware verb phrase per category, e.g. `read` + 2 → "Read 2 files". */
-const CATEGORY_PHRASE: Record<
- ToolCategory,
- { verb: string; noun: [singular: string, plural: string] }
-> = {
- read: { verb: 'Read', noun: ['file', 'files'] },
- write: { verb: 'Edited', noun: ['file', 'files'] },
- search: { verb: 'Ran', noun: ['search', 'searches'] },
- run: { verb: 'Ran', noun: ['command', 'commands'] },
- fetch: { verb: 'Fetched', noun: ['page', 'pages'] },
- browse: { verb: 'Browsed', noun: ['page', 'pages'] },
- other: { verb: 'Ran', noun: ['step', 'steps'] },
+const STEPS_KEY = {
+ one: 'conversations.tools.steps.one',
+ other: 'conversations.tools.steps.other',
};
+const STEPS_EN = { one: '{count} step', other: '{count} steps' };
+
+/** "3 steps" in the caller's locale. */
+export function formatStepCount(count: number, t?: Translate): string {
+ const form = count === 1 ? 'one' : 'other';
+ const template = t ? t(STEPS_KEY[form], STEPS_EN[form]) : STEPS_EN[form];
+ return fillPlaceholders(template, { count: String(count) });
+}
/**
- * Summarize a group of consecutive tool rows into a single Hermes-style
- * header — "Viewed 2 files", "Ran 3 commands", or, for a mixed group, the
- * distinct category phrases joined ("Edited a file, read a file"). A
- * single-row group defers to that row's specific label (more informative
- * than a generic count). Pure + deterministic for unit testing.
+ * Summarize a group of tool rows for a timeline header.
+ *
+ * One row reads as that row's own label. Several read as a step count plus
+ * the distinct things done, most frequent first, e.g. "6 steps · Read file
+ * ×3, Searched the web ×2, Ran command". Labels come from the registry, so
+ * the summary is translated with the rows and never invents a category
+ * phrase that disagrees with them.
*/
-export function summarizeToolGroup(entries: ToolTimelineEntry[]): string {
+export function summarizeToolGroup(entries: ToolTimelineEntry[], t?: Translate): string {
if (entries.length === 0) return '';
- if (entries.length === 1) {
- return formatTimelineEntry(entries[0]).title;
- }
- // Count per category, preserving first-seen order.
- const order: ToolCategory[] = [];
- const counts = new Map();
- for (const entry of entries) {
- const cat = categorizeTool(entry.name);
- if (!counts.has(cat)) order.push(cat);
- counts.set(cat, (counts.get(cat) ?? 0) + 1);
- }
- const phrases = order.map((cat, i) => {
- const n = counts.get(cat) ?? 0;
- const { verb, noun } = CATEGORY_PHRASE[cat];
- const word = n === 1 ? noun[0] : noun[1];
- const phrase = `${verb} ${n} ${word}`;
- // Lowercase the leading verb on all but the first phrase so the joined
- // sentence reads naturally ("Edited a file, ran 2 commands").
- return i === 0 ? phrase : phrase.charAt(0).toLowerCase() + phrase.slice(1);
- });
- return phrases.join(', ');
+ if (entries.length === 1) return formatTimelineEntry(entries[0], t).title;
+ return summarizeToolCalls(entries.map(presentTimelineEntry), t);
}
-export function formatTimelineEntry(entry: ToolTimelineEntry): { title: string; detail?: string } {
- const parsedArgs = parseToolArgs(entry.argsBuffer);
-
- if (entry.name === 'spawn_subagent' && parsedArgs?.agent_id === 'integrations_agent') {
- const provider =
- inferIntegrationName(parsedArgs.toolkit) ?? inferIntegrationNameFromPrompt(parsedArgs.prompt);
- return {
- title: provider ? integrationActivityTitle(provider) : 'Checking your connected app',
- detail: parsedArgs.prompt?.trim() || entry.detail,
- };
- }
-
- if (entry.name === 'integrations_agent' || entry.name === 'subagent:integrations_agent') {
- const provider =
- inferIntegrationName(entry.sourceToolName) ??
- inferIntegrationName(parsedArgs?.toolkit) ??
- inferIntegrationNameFromPrompt(entry.detail) ??
- inferIntegrationNameFromPrompt(parsedArgs?.prompt);
-
- return {
- title: provider ? integrationActivityTitle(provider) : 'Checking your connected app',
- detail: entry.detail,
- };
- }
-
- if (entry.name === 'subagent:researcher' || entry.name === 'researcher') {
- return { title: 'Researching', detail: entry.detail };
- }
- if (entry.name === 'agent_prepare_context') {
- return { title: 'Preparing context', detail: parsedArgs?.question?.trim() || entry.detail };
- }
- if (entry.name === 'subagent:context_scout' || entry.name === 'context_scout') {
- return { title: 'Scouting context', detail: entry.detail };
- }
- if (entry.name === 'composio_list_connections') {
- return { title: 'Viewing your Connections', detail: entry.detail };
- }
- if (entry.name === 'subagent:orchestrator' || entry.name === 'orchestrator') {
- return { title: 'Planning next steps', detail: entry.detail };
- }
- if (entry.name === 'subagent:critic' || entry.name === 'critic') {
- return { title: 'Reviewing the work', detail: entry.detail };
- }
- if (entry.name === 'subagent:tools_agent' || entry.name === 'tools_agent') {
- return { title: 'Using tools', detail: entry.detail };
- }
- if (entry.name === 'subagent:code_executor' || entry.name === 'code_executor') {
- return { title: 'Running code', detail: entry.detail };
- }
-
- if (entry.name.startsWith('delegate_')) {
- const provider =
- inferIntegrationName(parsedArgs?.toolkit) ??
- inferIntegrationNameFromPrompt(parsedArgs?.prompt) ??
- inferIntegrationName(entry.name);
-
- const title = provider ? integrationActivityTitle(provider) : humanizeIdentifier(entry.name);
- return { title, detail: entry.detail ?? parsedArgs?.prompt };
+/**
+ * The multi-step summary over already-resolved presentations; shared by the
+ * processing panel and the chat's tool timeline header.
+ */
+export function summarizeToolCalls(presentations: ToolCallPresentation[], t?: Translate): string {
+ if (presentations.length === 0) return '';
+ if (presentations.length === 1) return toolLabel(presentations[0], t);
+ const counts = new Map();
+ for (const presentation of presentations) {
+ const label = toolLabel({ ...presentation, tense: 'done' }, t);
+ counts.set(label, (counts.get(label) ?? 0) + 1);
}
+ const parts = [...counts.entries()]
+ .sort((a, b) => b[1] - a[1])
+ .slice(0, 3)
+ .map(([label, n]) => (n > 1 ? `${label} ×${n}` : label));
+ if (counts.size > 3) parts.push('…');
+ return `${formatStepCount(presentations.length, t)} · ${parts.join(', ')}`;
+}
- // A connected-service action called directly (`GMAIL_SEND_EMAIL`,
- // `SLACK_SEND_MESSAGE`): the orchestrator finds these through
- // `tool_search` and calls them itself, so this is the row a user sees
- // for "send that email". Label it by the service, with the action as
- // the detail, rather than a raw humanised slug.
- const directAction = inferIntegrationActionName(entry.name);
- if (directAction) {
+/**
+ * Title and detail for one timeline row. The title is tense-aware ("Reading
+ * file" while running, "Read file" once settled); the detail is the row's
+ * target, or for a delegation the full prompt the agent was given.
+ */
+export function formatTimelineEntry(
+ entry: ToolTimelineEntry,
+ t?: Translate
+): { title: string; detail?: string } {
+ const presentation = presentTimelineEntry(entry);
+ const title = toolLabel(presentation, t);
+ if (presentation.category === 'agent' || presentation.source === 'agent') {
+ // A delegation's detail is the whole brief the agent was given, not a
+ // capped chip: the rail shows it under the row.
return {
- title: integrationActivityTitle(directAction.provider),
- detail: entry.detail ?? directAction.action,
+ title,
+ detail: entry.detail ?? promptFromArgsBuffer(entry.argsBuffer) ?? presentation.chip,
};
}
-
- // ── Tool-specific formatting with args-derived detail ──────────────
- // Pass the completed result text so args-aware formatters can surface
- // details only known post-execution (e.g. the resolved search provider).
- const toolDetail = formatToolDetail(entry.name, parsedArgs, entry.result);
- if (toolDetail) {
- return { title: toolDetail.title, detail: toolDetail.detail ?? entry.detail };
- }
-
- return {
- title: entry.displayName ?? humanizeIdentifier(entry.name),
- detail: entry.detail ?? parsedArgs?.prompt,
- };
+ return { title, detail: presentation.chip ?? entry.detail };
}
/**
* A render block for the "View processing" panel — either a prose block
* (the agent's narration or hidden reasoning) or a group of consecutive
- * tool rows under a Hermes-style summary. {@link buildProcessingBlocks}
- * derives an ordered list of these from the interleaved transcript.
+ * tool rows under a summary. {@link buildProcessingBlocks} derives an
+ * ordered list of these from the interleaved transcript.
*/
type ProcessingBlock =
| { kind: 'narration'; key: string; text: string }
@@ -368,9 +158,9 @@ type ProcessingBlock =
/**
* Turn the ordered transcript (narration / thinking / tool-call pointers)
- * plus the tool timeline into the interleaved Hermes render model: prose
- * flows inline, and runs of consecutive tool calls collapse into one group
- * with a summary header. Tool pointers are resolved against `entries` by id;
+ * plus the tool timeline into the interleaved render model: prose flows
+ * inline, and runs of consecutive tool calls collapse into one group with a
+ * summary header. Tool pointers are resolved against `entries` by id;
* unknown ids are skipped. Pure + deterministic for unit testing.
*
* When `transcript` is empty (legacy snapshot / pre-streaming row), returns a
@@ -378,13 +168,14 @@ type ProcessingBlock =
*/
export function buildProcessingBlocks(
transcript: PersistedTranscriptItem[],
- entries: ToolTimelineEntry[]
+ entries: ToolTimelineEntry[],
+ t?: Translate
): ProcessingBlock[] {
const byId = new Map(entries.map(e => [e.id, e]));
if (transcript.length === 0) {
return entries.length > 0
- ? [{ kind: 'toolGroup', key: 'all', summary: summarizeToolGroup(entries), entries }]
+ ? [{ kind: 'toolGroup', key: 'all', summary: summarizeToolGroup(entries, t), entries }]
: [];
}
@@ -397,7 +188,7 @@ export function buildProcessingBlocks(
blocks.push({
kind: 'toolGroup',
key: `tg-${group[0].id}`,
- summary: summarizeToolGroup(group),
+ summary: summarizeToolGroup(group, t),
entries: group,
});
group = [];
@@ -433,54 +224,67 @@ export function buildProcessingBlocks(
}
export function promptFromArgsBuffer(argsBuffer?: string): string | undefined {
- return parseToolArgs(argsBuffer)?.prompt?.trim() || undefined;
+ const prompt = parseArgsObject(argsBuffer)?.prompt;
+ return typeof prompt === 'string' ? prompt.trim() || undefined : undefined;
}
-/** A web source an agent fetched/browsed during a run. */
+/** A web source an agent fetched, browsed or found during a run. */
export interface AgentSource {
- /** Stable id (the originating timeline entry id). */
+ /** Stable id (the originating timeline entry id, plus a hit index for searches). */
id: string;
- /** Display title — the URL hostname. */
+ /** Display title — the page title for a search hit, else the URL hostname. */
title: string;
/** Full URL. */
url: string;
}
/** Tools whose `url` arg represents a real web source the agent visited. */
-const URL_SOURCE_TOOLS = new Set(['web_fetch', 'http_request', 'curl', 'browser', 'browser_open']);
+const URL_SOURCE_TOOLS = new Set([
+ 'web_fetch',
+ 'http_request',
+ 'curl',
+ 'browser',
+ 'browser_open',
+ 'tinyfish_fetch',
+ 'gitbooks_get_page',
+]);
/**
- * Extract the distinct web sources an agent run touched, for the
- * "Agent Process Source" panel. Derived from real `url` args on
- * fetch/browse timeline entries — never fabricated. Deduplicated by URL,
- * preserving first-seen order.
+ * Extract the distinct web sources an agent run touched, for the sources
+ * list under an answer. Two kinds, both from real data, never fabricated:
+ * the `url` argument of fetch/browse calls, and the hits a completed web
+ * search returned. Deduplicated by URL, first-seen order.
*/
export function extractAgentSources(entries: ToolTimelineEntry[]): AgentSource[] {
const seen = new Set();
const sources: AgentSource[] = [];
+ const add = (source: AgentSource) => {
+ // `url` is model- or provider-supplied — prompt-injection-influenceable
+ // and not guaranteed to be a real web address. Only http(s) sources may
+ // reach an ``, so a `javascript:` / `data:` / `file:` value never
+ // becomes clickable.
+ if (!source.url || seen.has(source.url) || !isHttpUrl(source.url)) return;
+ seen.add(source.url);
+ sources.push(source);
+ };
for (const entry of entries) {
- const baseName = entry.name.replace(/^subagent:/, '');
- if (!URL_SOURCE_TOOLS.has(baseName)) continue;
- const url = parseToolArgs(entry.argsBuffer)?.url?.trim();
- // `url` is the raw tool-call argument the model emitted — it is
- // prompt-injection-influenceable and not guaranteed to be a real web
- // address. Only surface http(s) sources as clickable links so a
- // `javascript:` / `data:` / `file:` value can never reach an ` `.
- if (!url || seen.has(url) || !isHttpUrl(url)) continue;
- seen.add(url);
- sources.push({ id: entry.id, title: hostnameFromUrl(url) ?? url, url });
+ const presentation = presentTimelineEntry(entry);
+ if (presentation.body === 'webSearch' && entry.status === 'success') {
+ const parsed = parseWebSearchResult(entry.result, entry.structured);
+ parsed?.results.forEach((hit, index) =>
+ add({ id: `${entry.id}#${index}`, title: hit.title, url: hit.url })
+ );
+ continue;
+ }
+ if (!URL_SOURCE_TOOLS.has(presentation.baseName)) continue;
+ const url = parseArgsObject(entry.argsBuffer)?.url;
+ if (typeof url !== 'string') continue;
+ const trimmed = url.trim();
+ add({ id: entry.id, title: hostnameFromUrl(trimmed) ?? trimmed, url: trimmed });
}
return sources;
}
-const MAX_DETAIL_LEN = 120;
-
-function truncateDetail(value: string): string {
- const cleaned = value.trim().replace(/\s+/g, ' ');
- if (cleaned.length <= MAX_DETAIL_LEN) return cleaned;
- return `${cleaned.slice(0, MAX_DETAIL_LEN - 1)}…`;
-}
-
function hostnameFromUrl(url: string): string | undefined {
try {
return new URL(url).hostname;
@@ -499,285 +303,14 @@ function isHttpUrl(url: string): boolean {
}
}
-function shortenPath(filePath: string): string {
- const parts = filePath.split('/');
- if (parts.length <= 3) return filePath;
- return `…/${parts.slice(-2).join('/')}`;
-}
-
-/** Upper bound on a provider label, so a malformed marker can't blow up a row. */
-const MAX_SEARCH_PROVIDER_LENGTH = 32;
-
-/**
- * Extract the resolved search provider from a completed web-search result.
- * Every search engine tags its output with a `(via )` marker on the
- * heading line (managed resolves to "Exa" by default, or to whatever the
- * backend reports; BYOK engines tag "Brave"/"Querit"/"Seltz"/"Tavily"). Reading it back
- * keeps the timeline attribution dynamic: it is driven by what actually ran,
- * never by a hardcoded provider name (#5136).
- *
- * Only the first line is inspected, and only its *trailing* marker, so neither
- * a `(via …)` string inside a result excerpt nor one inside the echoed query
- * (`Search results for: login (via OAuth) (via Exa)`) can be mistaken for the
- * provider. Returns `undefined` while the call is still running (no result
- * yet) or if no marker is present.
- */
-export function extractSearchProvider(result: string | undefined): string | undefined {
- if (!result) return undefined;
- const headingLine = result.split('\n', 1)[0];
- const provider = headingLine?.match(/\(via ([^)]+)\)\s*$/i)?.[1]?.trim();
- if (!provider || provider.length > MAX_SEARCH_PROVIDER_LENGTH) return undefined;
- return provider;
-}
-
-function formatToolDetail(
- name: string,
- args: ParsedToolArgs | null,
- result?: string
-): { title: string; detail?: string } | null {
- switch (name) {
- case 'shell':
- case 'node_exec':
- case 'npm_exec': {
- const cmd = args?.command?.trim();
- return { title: 'Running command', detail: cmd ? truncateDetail(cmd) : undefined };
- }
-
- case 'web_fetch':
- case 'http_request':
- case 'curl': {
- const url = args?.url?.trim();
- const host = url ? hostnameFromUrl(url) : undefined;
- return {
- title: host ? `Fetching ${host}` : 'Fetching',
- detail: url ? truncateDetail(url) : undefined,
- };
- }
-
- // `web_search_tool` is the name the core streams; `web_search` is kept for
- // the settings-family id and older persisted rows.
- case 'web_search':
- case 'web_search_tool': {
- const query = args?.query?.trim();
- // Once the call completes, attribute the search to the provider that
- // actually served it ("Searched with Exa"); the query moves to the
- // detail line so it stays visible.
- const provider = extractSearchProvider(result);
- if (provider) {
- return {
- title: `Searched with ${provider}`,
- detail: query ? truncateDetail(query) : undefined,
- };
- }
- return { title: query ? `Searching: ${truncateDetail(query)}` : 'Searching the web' };
- }
-
- case 'gitbooks_search': {
- const query = args?.query?.trim();
- return { title: query ? `Searching docs: ${truncateDetail(query)}` : 'Searching docs' };
- }
-
- case 'file_read': {
- const p = args?.path?.trim() ?? args?.file_path?.trim();
- return { title: 'Reading file', detail: p ? shortenPath(p) : undefined };
- }
-
- case 'file_write':
- case 'vault_write_markdown': {
- const p = args?.path?.trim() ?? args?.file_path?.trim();
- return { title: 'Writing file', detail: p ? shortenPath(p) : undefined };
- }
-
- case 'edit':
- case 'apply_patch': {
- const p = args?.path?.trim() ?? args?.file_path?.trim();
- return { title: 'Editing file', detail: p ? shortenPath(p) : undefined };
- }
-
- case 'grep': {
- const pat = args?.pattern?.trim();
- return { title: pat ? `Searching: ${truncateDetail(pat)}` : 'Searching code' };
- }
-
- case 'glob': {
- const pat = args?.pattern?.trim();
- return { title: pat ? `Finding: ${truncateDetail(pat)}` : 'Finding files' };
- }
-
- case 'list': {
- const p = args?.path?.trim();
- return { title: 'Listing directory', detail: p ? shortenPath(p) : undefined };
- }
-
- case 'git_operations': {
- const cmd = args?.command?.trim();
- if (cmd) {
- const verb = cmd.split(/\s+/)[0];
- return { title: `Git ${verb}`, detail: truncateDetail(cmd) };
- }
- return { title: 'Git operation' };
- }
-
- case 'browser':
- case 'browser_open': {
- const url = args?.url?.trim();
- const host = url ? hostnameFromUrl(url) : undefined;
- return { title: host ? `Browsing ${host}` : 'Browsing' };
- }
-
- case 'image_info':
- return { title: 'Analyzing image' };
-
- case 'install_tool': {
- const tn = args?.tool_name?.trim();
- return { title: tn ? `Installing ${tn}` : 'Installing tool' };
- }
-
- case 'lsp':
- return { title: 'Code intelligence' };
-
- case 'run_tests':
- return { title: 'Running tests' };
-
- case 'run_linter':
- return { title: 'Running linter' };
-
- case 'read_diff':
- return { title: 'Reading diff' };
-
- default:
- return null;
- }
-}
-
-/**
- * Recognise the small set of known integration toolkit slugs. Used to
- * gate `inferIntegrationName` so unknown `delegate_` names (e.g.
- * `delegate_summarize`, `delegate_router`) don't get fake-humanised
- * into bogus "integration" labels in the tool timeline.
- */
-// Composio's own slugs have NO separator inside a multi-word toolkit:
-// `GOOGLECALENDAR_EVENTS_LIST`, not `GOOGLE_CALENDAR_EVENTS_LIST`. Only the
-// underscored spellings were listed here, so `GMAIL_*` and `DISCORD_*`
-// resolved while every `GOOGLECALENDAR_*` action fell through to the raw
-// humanizer and rendered "GOOGLECALENDAR EVENTS LIST". Both spellings are
-// kept: the underscored ones are how `delegate_` names arrive.
-const KNOWN_TOOLKIT_RE =
- /^(gmail|notion|github|slack|discord|linear|jira|google_calendar|googlecalendar|google_drive|googledrive|calendar)$/i;
-
-function inferIntegrationName(input?: string): string | undefined {
- if (!input) return undefined;
-
- const delegateMatch = input.match(/^delegate_(.+)$/);
- if (delegateMatch && KNOWN_TOOLKIT_RE.test(delegateMatch[1])) {
- return normalizeIntegrationName(delegateMatch[1]);
- }
-
- if (KNOWN_TOOLKIT_RE.test(input)) {
- return normalizeIntegrationName(input);
- }
-
- return undefined;
-}
-
-/**
- * Split a Composio action slug (`GMAIL_SEND_EMAIL`) into its known provider
- * and a readable action ("Send email"). `undefined` for anything that is not
- * an upper-case `_` name on a known toolkit, so ordinary
- * tools and unknown toolkits keep their generic label.
- */
-export function inferIntegrationActionName(
- name: string
-): { provider: string; action: string } | undefined {
- if (!/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/.test(name)) return undefined;
- // Try the longest toolkit prefix first (`GOOGLE_CALENDAR_...`), then the
- // shortest (`GMAIL_...`).
- const parts = name.split('_');
- for (let i = Math.min(parts.length - 1, 2); i >= 1; i -= 1) {
- const toolkit = parts.slice(0, i).join('_');
- if (KNOWN_TOOLKIT_RE.test(toolkit)) {
- const action = parts.slice(i).join(' ').toLowerCase();
- return {
- provider: normalizeIntegrationName(toolkit),
- action: action.charAt(0).toUpperCase() + action.slice(1),
- };
- }
- }
- return undefined;
-}
-
-function integrationActivityTitle(provider: string): string {
- switch (provider) {
- case 'GitHub':
- case 'Gmail':
- case 'Linear':
- case 'Jira':
- return `Making requests to your ${provider} account`;
- case 'Notion':
- return 'Working in your Notion workspace';
- case 'Slack':
- case 'Discord':
- return `Working in your ${provider} workspace`;
- case 'Google Calendar':
- return 'Updating your Google Calendar';
- case 'Google Drive':
- return 'Working in your Google Drive';
- default:
- return `Checking your ${provider}`;
- }
-}
-
-function inferIntegrationNameFromPrompt(prompt?: string): string | undefined {
- if (!prompt) return undefined;
- const known = [
- 'Notion',
- 'Gmail',
- 'GitHub',
- 'Slack',
- 'Discord',
- 'Linear',
- 'Jira',
- 'Google Calendar',
- 'Google Drive',
- ];
-
- const lower = prompt.toLowerCase();
- return known.find(name => lower.includes(name.toLowerCase()));
-}
-
-function parseToolArgs(argsBuffer?: string): ParsedToolArgs | null {
+function parseArgsObject(argsBuffer?: string): Record | null {
if (!argsBuffer) return null;
try {
- const parsed = JSON.parse(argsBuffer) as ParsedToolArgs;
- return parsed && typeof parsed === 'object' ? parsed : null;
+ const parsed: unknown = JSON.parse(argsBuffer);
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
+ ? (parsed as Record)
+ : null;
} catch {
return null;
}
}
-
-function normalizeIntegrationName(value: string): string {
- switch (value.toLowerCase()) {
- case 'github':
- return 'GitHub';
- case 'gmail':
- return 'Gmail';
- case 'google_calendar':
- case 'googlecalendar':
- case 'calendar':
- return 'Google Calendar';
- case 'google_drive':
- case 'googledrive':
- return 'Google Drive';
- default:
- return humanizeIdentifier(value);
- }
-}
-
-function humanizeIdentifier(value: string | undefined | null): string {
- if (!value) return '';
- return value
- .replace(/^subagent:/, '')
- .replace(/^delegate_/, '')
- .replace(/_/g, ' ')
- .replace(/\b\w/g, char => char.toUpperCase());
-}
diff --git a/app/test/playwright/specs/tool-call-presentation.spec.ts b/app/test/playwright/specs/tool-call-presentation.spec.ts
new file mode 100644
index 00000000000..c36d8f37eb2
--- /dev/null
+++ b/app/test/playwright/specs/tool-call-presentation.spec.ts
@@ -0,0 +1,175 @@
+/**
+ * Tool-call presentation, end to end.
+ *
+ * Drives a real core against the mock backend: the mock LLM calls the
+ * managed web search and a file read, the core executes both, and the chat
+ * renders them through assistant-ui's tool-timeline, tool-call and
+ * web-search elements. Pins what the mislabelling bugs broke: the search reads
+ * "Searched the web" (not a raw name), its hits render as the web-search
+ * element, and a settled step reads in the past tense.
+ */
+import { expect, type Page, test } from '@playwright/test';
+
+import {
+ bootAuthenticatedPage,
+ dismissWalkthroughIfPresent,
+ waitForAppReady,
+} from '../helpers/core-rpc';
+
+const MOCK_ADMIN_BASE = `http://127.0.0.1:${process.env.E2E_MOCK_PORT || '18473'}`;
+const USER_ID = 'pw-tool-call-presentation';
+
+async function resetMock(): Promise {
+ await fetch(`${MOCK_ADMIN_BASE}/__admin/reset`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({}),
+ });
+}
+
+async function setMockBehavior(key: string, value: string): Promise {
+ await fetch(`${MOCK_ADMIN_BASE}/__admin/behavior`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ key, value }),
+ });
+}
+
+async function openChat(page: Page): Promise {
+ await bootAuthenticatedPage(page, USER_ID, '/chat');
+ await page.goto('/#/chat');
+ await waitForAppReady(page);
+ await dismissWalkthroughIfPresent(page);
+ await expect(page.getByTestId('chat-message-input')).toBeVisible();
+}
+
+async function selectedThreadId(page: Page): Promise {
+ return page.evaluate(() => {
+ const store = (
+ window as unknown as {
+ __OPENHUMAN_STORE__?: {
+ getState?: () => { thread?: { selectedThreadId?: string | null } };
+ };
+ }
+ ).__OPENHUMAN_STORE__;
+ return store?.getState?.().thread?.selectedThreadId ?? null;
+ });
+}
+
+async function createNewThread(page: Page): Promise {
+ const before = await selectedThreadId(page);
+ await dismissWalkthroughIfPresent(page);
+ const sidebarButton = page.getByTestId('new-thread-sidebar-button');
+ if (await sidebarButton.isVisible().catch(() => false)) {
+ await sidebarButton.click({ force: true });
+ } else {
+ await page.getByTestId('new-thread-button').click({ force: true });
+ }
+ const changed = await expect
+ .poll(
+ async () => {
+ const current = await selectedThreadId(page);
+ return current && current !== before ? current : null;
+ },
+ { timeout: 10_000 }
+ )
+ .not.toBeNull()
+ .then(
+ () => true,
+ () => false
+ );
+ const id = await selectedThreadId(page);
+ if (changed && id) return id;
+ if (id) return id;
+ if (before) return before;
+ throw new Error('selectedThreadId was not populated');
+}
+
+async function waitForSocketConnected(page: Page): Promise {
+ await expect
+ .poll(
+ async () =>
+ page.evaluate(() => {
+ const store = (
+ window as unknown as {
+ __OPENHUMAN_STORE__?: {
+ getState?: () => { socket?: { byUser?: Record } };
+ };
+ }
+ ).__OPENHUMAN_STORE__;
+ const byUser = store?.getState?.().socket?.byUser ?? {};
+ return Object.values(byUser).some(entry => entry?.status === 'connected');
+ }),
+ { timeout: 30_000 }
+ )
+ .toBe(true);
+}
+
+async function sendMessage(page: Page, prompt: string): Promise {
+ await waitForSocketConnected(page);
+ await dismissWalkthroughIfPresent(page);
+ await page.getByTestId('chat-message-input').fill(prompt);
+ await dismissWalkthroughIfPresent(page);
+ await expect(page.getByTestId('send-message-button')).toBeEnabled();
+ await page.getByTestId('send-message-button').click();
+}
+
+test.describe('Tool-call presentation', () => {
+ test.beforeEach(async ({ page }) => {
+ await resetMock();
+ await openChat(page);
+ await createNewThread(page);
+ });
+
+ test('renders a web search and a file read as labelled timeline steps', async ({ page }) => {
+ const CANARY = 'canary-tool-presentation-7f3e';
+ const forced = [
+ {
+ content: '',
+ toolCalls: [
+ {
+ id: 'call_web_search_1',
+ name: 'web_search_tool',
+ arguments: JSON.stringify({ query: 'rust async traits' }),
+ },
+ {
+ id: 'call_file_read_1',
+ name: 'file_read',
+ arguments: JSON.stringify({ path: 'e2e/definitely-missing/README.md' }),
+ },
+ ],
+ },
+ { content: `Here is what I found. ${CANARY}` },
+ ];
+ await setMockBehavior('llmForcedResponses', JSON.stringify(forced));
+ await setMockBehavior('llmStreamChunkDelayMs', '10');
+
+ await sendMessage(page, 'search the web for rust async traits and read the README');
+ await expect(page.getByText(CANARY).last()).toBeVisible({ timeout: 60_000 });
+
+ const timeline = page.getByTestId('tool-timeline').last();
+ await expect(timeline).toBeVisible();
+ // Settled summary, not "2 tool calls".
+ await expect(timeline).toContainText('2 steps');
+
+ const calls = page.getByTestId('assistant-ui-tool-call');
+ const search = calls.filter({ hasText: 'Searched the web' });
+ await expect(search).toHaveCount(1);
+ await expect(search.getByText('rust async traits').first()).toBeVisible();
+ // The hits render through the web-search element as links.
+ const results = page.getByTestId('web-search-results');
+ await expect(results).toBeVisible();
+ await expect(results.getByTestId('web-search-hit').first()).toBeVisible();
+
+ // The file read settled (it fails on a missing path) and reads in the
+ // past tense, never the raw tool name.
+ const read = calls.filter({ hasText: 'Read file' });
+ await expect(read).toHaveCount(1);
+ await expect(page.getByText('file_read', { exact: true })).toHaveCount(0);
+ await expect(page.getByText('web_search_tool', { exact: true })).toHaveCount(0);
+
+ if (process.env.PW_TOOL_SCREENSHOT) {
+ await timeline.screenshot({ path: process.env.PW_TOOL_SCREENSHOT });
+ }
+ });
+});
diff --git a/crates/openhuman-core/src/agent/messages.rs b/crates/openhuman-core/src/agent/messages.rs
index 87daccc16b3..ea3d937981b 100644
--- a/crates/openhuman-core/src/agent/messages.rs
+++ b/crates/openhuman-core/src/agent/messages.rs
@@ -14,6 +14,12 @@ const REPLAYED_METADATA_KEY: &str = "openhuman_replayed";
const WRAPPED_VALUE_KEY: &str = "openhuman_wrapped_value";
const WRAPPED_FLAG: &str = "wrapped";
+/// Durable `extra_metadata` key on a text-dialect `[Tool results]` user row:
+/// the call ids whose results in that row failed. The per-result analogue of
+/// the `tool_failure` a native `tool` row carries; written by the session codec
+/// and read by the thread transcript projection.
+pub(crate) const TOOL_RESULT_FAILURES_METADATA_KEY: &str = "openhuman_tool_failures";
+
fn would_wrap(message: &ChatMessage) -> bool {
matches!(&message.extra_metadata, Some(value) if !value.is_object())
}
diff --git a/crates/openhuman-core/src/agent/progress.rs b/crates/openhuman-core/src/agent/progress.rs
index 014f0c760ba..c77c421dda1 100644
--- a/crates/openhuman-core/src/agent/progress.rs
+++ b/crates/openhuman-core/src/agent/progress.rs
@@ -74,6 +74,21 @@ pub enum AgentProgress {
/// the chat "View processing" timeline renders. `None` on success and
/// on legacy snapshots. See `crate::tools::status`.
failure: Option,
+ /// Server-computed human label recomputed from the tool's OWN
+ /// [`tinytools::Tool::display_label`] using the real call arguments
+ /// (the matching `ToolCallStarted.display_label` was computed with no
+ /// arguments, since the harness start event carries none). Forwarded
+ /// on the wire as `tool_display_label` so a completed row can pick up
+ /// a label that only became knowable once the arguments existed.
+ display_label: Option,
+ /// Server-computed contextual detail (e.g. "steven@gmail.com"),
+ /// recomputed the same way from `Tool::display_detail`.
+ display_detail: Option,
+ /// Structured, tool-specific result payload copied from
+ /// [`tinytools::ToolResult::metadata`] when it is a JSON object
+ /// carrying a `"kind"` discriminator (e.g. `{"kind":"web_search",...}`).
+ /// `None` for tools that don't populate metadata of that shape.
+ structured: Option,
},
/// A sub-agent was spawned during tool execution.
@@ -249,6 +264,14 @@ pub enum AgentProgress {
/// a failed sub-agent row carries the same "why + what to do next" copy
/// instead of discarding the already-computed classification (#4459).
failure: Option,
+ /// Mirrors [`Self::ToolCallCompleted::display_label`], recomputed from
+ /// the child tool's own `Tool::display_label` using the real call
+ /// arguments.
+ display_label: Option,
+ /// Mirrors [`Self::ToolCallCompleted::display_detail`].
+ display_detail: Option,
+ /// Mirrors [`Self::ToolCallCompleted::structured`].
+ structured: Option,
},
/// A chunk of a sub-agent's visible assistant text arrived from the
diff --git a/crates/openhuman-core/src/agent/progress_tracing/journal_projection.rs b/crates/openhuman-core/src/agent/progress_tracing/journal_projection.rs
index a631afb453c..55c2a0cfa95 100644
--- a/crates/openhuman-core/src/agent/progress_tracing/journal_projection.rs
+++ b/crates/openhuman-core/src/agent/progress_tracing/journal_projection.rs
@@ -217,6 +217,9 @@ fn observation_to_progress(obs: &AgentObservation, state: &mut ReplayState) -> V
elapsed_ms: *latency_ms,
iteration: scope.iteration,
failure: None,
+ display_label: Some("Searching tools".to_string()),
+ display_detail: None,
+ structured: None,
},
],
None => vec![
@@ -238,6 +241,9 @@ fn observation_to_progress(obs: &AgentObservation, state: &mut ReplayState) -> V
elapsed_ms: *latency_ms,
iteration: state.iteration,
failure: None,
+ display_label: Some("Searching tools".to_string()),
+ display_detail: None,
+ structured: None,
},
],
}
@@ -300,6 +306,12 @@ fn observation_to_progress(obs: &AgentObservation, state: &mut ReplayState) -> V
elapsed_ms: duration_ms.unwrap_or(0),
iteration: scope.iteration,
failure,
+ // The journal has no live tool registry to recompute a
+ // real label/detail from, and no `ToolResult.metadata` to
+ // replay structured payloads from.
+ display_label: None,
+ display_detail: None,
+ structured: None,
}],
None => vec![AgentProgress::ToolCallCompleted {
call_id: call_id.as_str().to_string(),
@@ -311,6 +323,9 @@ fn observation_to_progress(obs: &AgentObservation, state: &mut ReplayState) -> V
elapsed_ms: duration_ms.unwrap_or(0),
iteration: state.iteration,
failure,
+ display_label: None,
+ display_detail: None,
+ structured: None,
}],
}
}
@@ -345,8 +360,8 @@ fn observation_to_progress(obs: &AgentObservation, state: &mut ReplayState) -> V
tool_name: requested_name.clone(),
arguments: arguments.clone(),
iteration: scope.iteration,
- display_label: Some(label),
- display_detail: detail,
+ display_label: Some(label.clone()),
+ display_detail: detail.clone(),
},
AgentProgress::SubagentToolCallCompleted {
agent_id: scope.agent_id.clone(),
@@ -360,6 +375,9 @@ fn observation_to_progress(obs: &AgentObservation, state: &mut ReplayState) -> V
elapsed_ms: 0,
iteration: scope.iteration,
failure,
+ display_label: Some(label.clone()),
+ display_detail: detail.clone(),
+ structured: None,
},
],
None => vec![
@@ -368,8 +386,8 @@ fn observation_to_progress(obs: &AgentObservation, state: &mut ReplayState) -> V
tool_name: requested_name.clone(),
arguments: arguments.clone(),
iteration: state.iteration,
- display_label: Some(label),
- display_detail: detail,
+ display_label: Some(label.clone()),
+ display_detail: detail.clone(),
},
AgentProgress::ToolCallCompleted {
call_id: call_id.as_str().to_string(),
@@ -381,6 +399,9 @@ fn observation_to_progress(obs: &AgentObservation, state: &mut ReplayState) -> V
elapsed_ms: 0,
iteration: state.iteration,
failure,
+ display_label: Some(label),
+ display_detail: detail,
+ structured: None,
},
],
}
diff --git a/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_attribution_tests.rs b/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_attribution_tests.rs
index 3166d6c8581..6163db9d7be 100644
--- a/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_attribution_tests.rs
+++ b/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_attribution_tests.rs
@@ -89,6 +89,9 @@ fn tool_io_is_captured_when_capture_content_is_on() {
elapsed_ms: 4,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
},
4,
);
@@ -138,6 +141,9 @@ fn tool_io_is_never_recorded_when_capture_content_is_off() {
elapsed_ms: 4,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
},
4,
),
@@ -413,6 +419,9 @@ fn failed_tool_records_classified_cause_only_when_capture_on() {
next_action: "Try again".to_string(),
recoverable: true,
}),
+ display_label: None,
+ display_detail: None,
+ structured: None,
};
// Capture ON → plain-language cause lands as error.message.
@@ -581,6 +590,9 @@ fn parent_tool_completion_backfills_arguments_and_records_output() {
elapsed_ms: 40,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
},
45,
),
diff --git a/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_span_tree_tests.rs b/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_span_tree_tests.rs
index 6e5e7e01231..429be231bab 100644
--- a/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_span_tree_tests.rs
+++ b/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_span_tree_tests.rs
@@ -219,6 +219,9 @@ fn subagent_lifecycle_nests_under_the_turn() {
elapsed_ms: 40,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
},
30,
),
diff --git a/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_tests.rs b/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_tests.rs
index 3a1eb54eaad..b8b54508248 100644
--- a/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_tests.rs
+++ b/crates/openhuman-core/src/agent/progress_tracing/progress_tracing_tests.rs
@@ -57,6 +57,9 @@ fn tool_completed(
elapsed_ms: elapsed,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
}
}
diff --git a/crates/openhuman-core/src/agent/session_host/codec.rs b/crates/openhuman-core/src/agent/session_host/codec.rs
index bd553be830b..34eae1f95dd 100644
--- a/crates/openhuman-core/src/agent/session_host/codec.rs
+++ b/crates/openhuman-core/src/agent/session_host/codec.rs
@@ -6,14 +6,18 @@
use crate::agent::{
message_convert,
- messages::{chat_message_from_transcript, transcript_message_from_chat},
+ messages::{
+ chat_message_from_transcript, transcript_message_from_chat,
+ TOOL_RESULT_FAILURES_METADATA_KEY,
+ },
tinyagents::host::OpenHumanRunContext,
};
use tinyagents_runtime::{RuntimeError, TranscriptCodec, TranscriptTurnOptions};
use tinyagents_session::transcript::{
- MessageUsage, SessionTranscript, ToolFailure, TranscriptMessage, TurnUsage,
+ MessageUsage, SessionTranscript, ToolFailure, TranscriptMessage, TranscriptToolCall, TurnUsage,
};
use tinyinference_llm::message::Message;
+use tinytools_agent::dialect::parse_replayed_results;
/// Converts OpenHuman's durable transcript rows at the TinyAgents boundary.
#[derive(Default)]
@@ -51,6 +55,7 @@ impl TranscriptCodec for OpenHumanTranscriptCodec {
// keeps non-prefix messages. New messages alone receive this turn's
// request correlation id.
let mut consumed = vec![false; previous.len().min(prior.len())];
+ let mut fresh = vec![false; rows.len()];
for (next_index, next_message) in next.iter().enumerate() {
let matched = previous.iter().enumerate().take(consumed.len()).find_map(
|(previous_index, previous_message)| {
@@ -63,6 +68,7 @@ impl TranscriptCodec for OpenHumanTranscriptCodec {
consumed[previous_index] = true;
} else {
rows[next_index].request_id = options.request_id.clone();
+ fresh[next_index] = true;
}
}
// The generic inference `Message::Tool` intentionally carries only a
@@ -70,11 +76,13 @@ impl TranscriptCodec for OpenHumanTranscriptCodec {
// sidecar preserves the execution failure bit until this persistence
// boundary, so resumed transcript rows retain the same failure status
// the live tool timeline observed.
- let failures = options
+ let sidecar = options
.context
.session_sidecar
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
+ .clone();
+ let failures = sidecar
.tool_outcomes
.iter()
.filter(|outcome| !outcome.success)
@@ -88,6 +96,12 @@ impl TranscriptCodec for OpenHumanTranscriptCodec {
});
}
}
+ attach_text_dialect_rounds(
+ &mut rows,
+ &fresh,
+ &sidecar.tool_outcomes,
+ sidecar.resolved_route.as_ref(),
+ );
Ok(rows)
}
@@ -173,9 +187,139 @@ impl TranscriptCodec for OpenHumanTranscriptCodec {
// as an interim step followed by duplicate, never-settled tool
// rows (which also mis-paired later FIFO results). Each call is
// already recorded, once, in the envelope of the assistant row
- // that issued it.
+ // that issued it (or in a provenance-only usage record for text
+ // dialects).
tool_calls: Vec::new(),
iteration: sidecar.model_calls.min(u32::MAX as usize) as u32,
}))
}
}
+
+/// Give each text-dialect tool round's calls to the assistant row that issued
+/// them, and record which of its results failed.
+///
+/// A native round is persisted as a `{content, tool_calls}` envelope followed
+/// by `tool` rows, so its calls and failure bits already sit on the right rows.
+/// A text dialect (`xml`, `pformat`, code) persists its replay form instead: the
+/// issuing assistant row holds only prose, and every result of the round is
+/// folded into one `[Tool results]` user row. Neither shape can say which calls
+/// were made or which of them failed, so this reads the round's call ids back
+/// out of that results row and takes names, arguments and outcomes from the
+/// turn sidecar:
+///
+/// - the issuing row (the fresh assistant row directly before the results row)
+/// gets a provenance-only [`TurnUsage`] — zero spend, since the turn's spend
+/// is recorded once on its final row — whose `tool_calls` are this round's;
+/// - the results row gets the ids of its failed results under
+/// [`TOOL_RESULT_FAILURES_METADATA_KEY`], the per-result analogue of a native
+/// `tool` row's `tool_failure`.
+///
+/// Rows carried over from a previous turn are left untouched.
+fn attach_text_dialect_rounds(
+ rows: &mut [TranscriptMessage],
+ fresh: &[bool],
+ outcomes: &[crate::agent::tinyagents::ToolCallOutcome],
+ route: Option<&tinyinference_llm::model::ResolvedModelRoute>,
+) {
+ let mut iteration = 0u32;
+ for index in 0..rows.len() {
+ if !fresh[index] {
+ continue;
+ }
+ if rows[index].role == "assistant" {
+ iteration = iteration.saturating_add(1);
+ continue;
+ }
+ if rows[index].role != "user" {
+ continue;
+ }
+ let Some(results) = parse_replayed_results(&rows[index].content) else {
+ continue;
+ };
+ let outcome_for = |id: &str| outcomes.iter().find(|outcome| outcome.call_id == id);
+
+ let failed: Vec = results
+ .iter()
+ .filter(|result| outcome_for(&result.tool_call_id).is_some_and(|o| !o.success))
+ .map(|result| serde_json::Value::String(result.tool_call_id.clone()))
+ .collect();
+ if !failed.is_empty() {
+ match rows[index]
+ .extra_metadata
+ .get_or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()))
+ {
+ serde_json::Value::Object(map) => {
+ map.insert(
+ TOOL_RESULT_FAILURES_METADATA_KEY.to_string(),
+ serde_json::Value::Array(failed),
+ );
+ }
+ _ => log::warn!(
+ "[session_host][codec] text-dialect results row has non-object metadata; \
+ failure status not recorded"
+ ),
+ }
+ }
+
+ let Some(issuer) = index.checked_sub(1) else {
+ continue;
+ };
+ if !fresh[issuer] || rows[issuer].role != "assistant" {
+ continue;
+ }
+ let calls: Vec = results
+ .iter()
+ .filter_map(|result| outcome_for(&result.tool_call_id))
+ .map(|outcome| TranscriptToolCall {
+ id: outcome.call_id.clone(),
+ name: outcome.name.clone(),
+ arguments: outcome.arguments.to_string(),
+ extra_content: None,
+ })
+ .collect();
+ log::debug!(
+ "[session_host][codec] text-dialect round iteration={iteration} results={} \
+ attached_calls={} failed={}",
+ results.len(),
+ calls.len(),
+ rows[index]
+ .extra_metadata
+ .as_ref()
+ .and_then(|meta| meta.get(TOOL_RESULT_FAILURES_METADATA_KEY))
+ .and_then(serde_json::Value::as_array)
+ .map_or(0, Vec::len)
+ );
+ if calls.is_empty() {
+ continue;
+ }
+ if let Some(usage) = rows[issuer].turn_usage.as_mut() {
+ // The final assistant row already owns the turn's spend. Keep it
+ // intact and add this text-dialect round's provenance without
+ // duplicating a call an earlier adapter has recorded.
+ usage.tool_calls.extend(calls.into_iter().filter(|call| {
+ !usage
+ .tool_calls
+ .iter()
+ .any(|existing| existing.id == call.id)
+ }));
+ } else {
+ rows[issuer].turn_usage = Some(TurnUsage {
+ provider: route
+ .map(|route| route.provider.clone())
+ .unwrap_or_default(),
+ model: route.map(|route| route.model.clone()).unwrap_or_default(),
+ usage: MessageUsage {
+ input: 0,
+ output: 0,
+ cached_input: 0,
+ context_window: 0,
+ cost_usd: 0.0,
+ },
+ ts: chrono::Utc::now().to_rfc3339(),
+ reasoning_content: None,
+ tool_calls: calls,
+ iteration,
+ });
+ }
+ }
+}
diff --git a/crates/openhuman-core/src/agent/session_host/tool_progress.rs b/crates/openhuman-core/src/agent/session_host/tool_progress.rs
index c64a402b423..158b80a42e7 100644
--- a/crates/openhuman-core/src/agent/session_host/tool_progress.rs
+++ b/crates/openhuman-core/src/agent/session_host/tool_progress.rs
@@ -183,6 +183,12 @@ impl ProgressReporter for TurnProgress {
elapsed_ms,
iteration,
failure: None,
+ // The legacy reporter path has no live tool registry or
+ // captured `ToolResult` to recompute a label or read
+ // structured metadata from.
+ display_label: None,
+ display_detail: None,
+ structured: None,
},
);
}
diff --git a/crates/openhuman-core/src/agent/tinyagents/host/progress_sink.rs b/crates/openhuman-core/src/agent/tinyagents/host/progress_sink.rs
index c1cf3a6a810..0593c45673e 100644
--- a/crates/openhuman-core/src/agent/tinyagents/host/progress_sink.rs
+++ b/crates/openhuman-core/src/agent/tinyagents/host/progress_sink.rs
@@ -508,6 +508,14 @@ impl ProgressSink for OpenHumanProgressSink {
elapsed_ms,
iteration: opened.iteration,
failure,
+ // Same registry gap as the `ToolCallStarted` arm above —
+ // TODO(phase4): resolve labels from the tool registry.
+ display_label: None,
+ display_detail: None,
+ // The coarse `ProgressEvent` stream carries no
+ // `ToolResult`, so there is no metadata to copy structured
+ // payloads from on this path.
+ structured: None,
})
.await;
}
diff --git a/crates/openhuman-core/src/agent/tinyagents/middleware/tool_outcome_capture.rs b/crates/openhuman-core/src/agent/tinyagents/middleware/tool_outcome_capture.rs
index 443d052a60c..c7a2b3894ed 100644
--- a/crates/openhuman-core/src/agent/tinyagents/middleware/tool_outcome_capture.rs
+++ b/crates/openhuman-core/src/agent/tinyagents/middleware/tool_outcome_capture.rs
@@ -131,6 +131,16 @@ impl Middleware<(), crate::agent::tinyagents::host::OpenHumanRunContext>
let timed_out = combined.contains("timed out");
Some(crate::tools::status::classify(&combined, timed_out))
};
+ // Host-only structured payload (e.g. `{"kind":"web_search", ...}`) a
+ // tool attached via `ToolResult::metadata` for a richer UI
+ // presentation than plain text allows. Only forwarded when it is a
+ // JSON object carrying a `"kind"` discriminator, so an arbitrary
+ // metadata shape a tool sets for its own bookkeeping doesn't leak onto
+ // the wire as if it were a presentation contract.
+ let structured = result
+ .metadata
+ .clone()
+ .filter(|v| v.is_object() && v.get("kind").is_some());
if let Ok(mut map) = self.failure_map.lock() {
// Keep duration + rendered output size as a compatibility fallback
// for old/deserialized completion events; TinyAgents 1.6 supplies
@@ -144,6 +154,7 @@ impl Middleware<(), crate::agent::tinyagents::host::OpenHumanRunContext>
crate::agent::tinyagents::middleware::tool_result_text(result)
.chars()
.count(),
+ structured,
),
);
}
diff --git a/crates/openhuman-core/src/agent/tinyagents/observability/cap_pauser.rs b/crates/openhuman-core/src/agent/tinyagents/observability/cap_pauser.rs
index d5dfeb368b5..6bd6db2726f 100644
--- a/crates/openhuman-core/src/agent/tinyagents/observability/cap_pauser.rs
+++ b/crates/openhuman-core/src/agent/tinyagents/observability/cap_pauser.rs
@@ -39,17 +39,21 @@ pub(crate) type IterationCursor = Arc;
/// `tool_name` contract without the forwarder emitting those fragments itself.
pub(crate) type ToolNameMap = Arc>>;
-/// Shared `call_id → (success, classified failure, elapsed_ms, output_chars)`
-/// side-channel. The crate's `AgentEvent::ToolCompleted` carries only `call_id`
-/// + `tool_name` (no success/error, duration, or output size), so
+/// Shared `call_id → (success, classified failure, elapsed_ms, output_chars,
+/// structured metadata)` side-channel. The crate's `AgentEvent::ToolCompleted`
+/// carries only `call_id` + `tool_name` (no success/error, duration, output
+/// size, or `ToolResult.metadata`), so
///
/// `ToolOutcomeCaptureMiddleware::after_tool` — which does see the `ToolResult`
-/// (including the executor-measured `elapsed_ms` and the rendered content) —
-/// classifies each outcome and writes it here; the bridge reads it when
-/// projecting the live `ToolCallCompleted` event, so a failed tool surfaces real
-/// `success: false` + a user-facing `failure`, and a completed tool surfaces its
-/// real duration + output size instead of `0`/`0` (#4467, item 4). Absent entry
-/// (event projected before the middleware ran) falls back to `(true, None, 0, 0)`.
+/// (including the executor-measured `elapsed_ms`, the rendered content, and
+/// its host-only `metadata`) — classifies each outcome and writes it here; the
+/// bridge reads it when projecting the live `ToolCallCompleted` event, so a
+/// failed tool surfaces real `success: false` + a user-facing `failure`, a
+/// completed tool surfaces its real duration + output size instead of `0`/`0`
+/// (#4467, item 4), and a tool that populated `ToolResult.metadata` with a
+/// `{"kind": ...}` object (e.g. web search) surfaces it as
+/// `ToolCallCompleted::structured`. Absent entry (event projected before the
+/// middleware ran) falls back to `(true, None, 0, 0, None)`.
pub(crate) type ToolFailureMap = Arc<
Mutex<
std::collections::HashMap<
@@ -59,6 +63,7 @@ pub(crate) type ToolFailureMap = Arc<
Option,
u64,
usize,
+ Option,
),
>,
>,
diff --git a/crates/openhuman-core/src/agent/tinyagents/observability/event_bridge.rs b/crates/openhuman-core/src/agent/tinyagents/observability/event_bridge.rs
index 0d275ce31ce..cabe8a25b4c 100644
--- a/crates/openhuman-core/src/agent/tinyagents/observability/event_bridge.rs
+++ b/crates/openhuman-core/src/agent/tinyagents/observability/event_bridge.rs
@@ -11,6 +11,7 @@ use tinyinference_llm::usage::Usage;
use crate::agent::progress::AgentProgress;
use crate::inference::provider::UsageInfo;
+use tinytools::humanize_tool_name;
use super::cap_pauser::{
IterationCursor, ProviderUsageCarry, SubagentScope, ToolFailureMap, ToolNameMap,
@@ -87,6 +88,14 @@ pub(crate) struct OpenhumanEventBridge {
/// `ToolStarted` and taken on `ToolCompleted` so the projected completion
/// event carries a real `elapsed_ms` (the crate event has no timing).
pub(super) tool_started_at: Mutex>,
+ /// The turn's registered tool sets, retained (cheap `Arc` clones — never
+ /// the tools themselves) so the bridge can resolve a live `&dyn Tool` by
+ /// name and call its own [`tinytools::Tool::display_label`] /
+ /// [`tinytools::Tool::display_detail`] instead of only ever guessing from
+ /// the bare tool name (issue: tool-call presentation). Empty for a bridge
+ /// built without a turn's tool sets (e.g. a bare unit-test bridge), in
+ /// which case every lookup falls back to [`humanize_tool_name`].
+ pub(super) tool_sets: Vec>>>,
pub(super) state: Mutex,
/// Ordered overflow buffer for progress events that hit backpressure
/// (channel `Full`). Once ANY event spills here, `draining` stays set and
@@ -123,12 +132,17 @@ impl OpenhumanEventBridge {
Arc::default(),
Arc::default(),
Arc::default(),
+ Vec::new(),
)
}
/// Build a bridge, optionally child-scoped, sharing `cursor` (iteration
/// attribution) and `tool_names` (tool-call name lookup for the streamed
- /// argument fragments) with the model adapter.
+ /// argument fragments) with the model adapter. `tool_sets` is the turn's
+ /// registered tool sets (cheap `Arc` clones), used to resolve a live
+ /// `&dyn Tool` for `display_label`/`display_detail` — pass `Vec::new()`
+ /// when none are available (e.g. tests).
+ #[allow(clippy::too_many_arguments)]
pub(crate) fn with_scope(
on_progress: Option>,
model: impl Into,
@@ -139,6 +153,7 @@ impl OpenhumanEventBridge {
tool_names: ToolNameMap,
failure_map: ToolFailureMap,
usage_carry: ProviderUsageCarry,
+ tool_sets: Vec>>>,
) -> Arc {
Arc::new(Self {
on_progress,
@@ -153,11 +168,52 @@ impl OpenhumanEventBridge {
recorded_iterations: Mutex::new(std::collections::HashSet::new()),
resolved_calls: Mutex::new(std::collections::HashMap::new()),
tool_started_at: Mutex::new(std::collections::HashMap::new()),
+ tool_sets,
state: Mutex::new(BridgeState::default()),
overflow: Arc::default(),
})
}
+ /// Resolve `tool_name` against the turn's registered tool sets and
+ /// compute the presentation pair from the tool's OWN
+ /// [`tinytools::Tool::display_label`] / [`tinytools::Tool::display_detail`]
+ /// using `args` (the real call arguments when known, `Null` at call-start
+ /// before they've arrived). Unknown tools (not found in any set — the
+ /// unknown-tool-call path never registers one) fall back to a humanized
+ /// name with no detail, matching the pre-existing behavior.
+ pub(super) fn resolve_display(
+ &self,
+ tool_name: &str,
+ args: &serde_json::Value,
+ ) -> (Option, Option) {
+ match self
+ .tool_sets
+ .iter()
+ .flat_map(|set| set.iter())
+ .find(|t| t.name() == tool_name)
+ {
+ Some(tool) => {
+ let label = tool.display_label(args);
+ let detail = tool.display_detail(args);
+ tracing::trace!(
+ tool_name,
+ label = ?label,
+ detail = ?detail,
+ "[tool-presentation] resolved display label/detail from registered tool"
+ );
+ (label, detail)
+ }
+ None => {
+ tracing::debug!(
+ tool_name,
+ "[tool-presentation] tool not found in turn's registered sets — \
+ falling back to humanized name"
+ );
+ (Some(humanize_tool_name(tool_name)), None)
+ }
+ }
+ }
+
/// Cumulative `(input_tokens, output_tokens, charged_usd)` observed so far.
#[cfg(test)]
pub(super) fn totals(&self) -> (u64, u64, f64) {
diff --git a/crates/openhuman-core/src/agent/tinyagents/observability/event_projection.rs b/crates/openhuman-core/src/agent/tinyagents/observability/event_projection.rs
index e2f0ca2077e..bbce65f365b 100644
--- a/crates/openhuman-core/src/agent/tinyagents/observability/event_projection.rs
+++ b/crates/openhuman-core/src/agent/tinyagents/observability/event_projection.rs
@@ -321,7 +321,7 @@ impl EventListener for OpenhumanEventBridge {
tool_name: requested_name.clone(),
arguments: arguments.clone(),
iteration,
- display_label: Some(label),
+ display_label: Some(label.clone()),
display_detail: Some("tool not available".to_string()),
});
self.send(AgentProgress::ToolCallCompleted {
@@ -334,6 +334,9 @@ impl EventListener for OpenhumanEventBridge {
elapsed_ms: 0,
iteration,
failure,
+ display_label: Some(label),
+ display_detail: Some("tool not available".to_string()),
+ structured: None,
});
}
Some(s) => {
@@ -344,7 +347,7 @@ impl EventListener for OpenhumanEventBridge {
tool_name: requested_name.clone(),
arguments: arguments.clone(),
iteration,
- display_label: Some(label),
+ display_label: Some(label.clone()),
display_detail: Some("tool not available".to_string()),
});
self.send(AgentProgress::SubagentToolCallCompleted {
@@ -359,6 +362,9 @@ impl EventListener for OpenhumanEventBridge {
elapsed_ms: 0,
iteration,
failure,
+ display_label: Some(label),
+ display_detail: Some("tool not available".to_string()),
+ structured: None,
});
}
}
@@ -445,6 +451,9 @@ impl EventListener for OpenhumanEventBridge {
elapsed_ms: *latency_ms,
iteration,
failure: None,
+ display_label: Some("Searching tools".to_string()),
+ display_detail: None,
+ structured: None,
});
}
Some(s) => {
@@ -470,6 +479,9 @@ impl EventListener for OpenhumanEventBridge {
elapsed_ms: *latency_ms,
iteration,
failure: None,
+ display_label: Some("Searching tools".to_string()),
+ display_detail: None,
+ structured: None,
});
}
}
@@ -489,14 +501,25 @@ impl EventListener for OpenhumanEventBridge {
.lock()
.unwrap_or_else(|p| p.into_inner())
.insert(call_id.as_str().to_string(), std::time::Instant::now());
+ // The harness start event carries no call input (`ToolStarted`
+ // has only `call_id`/`tool_name`), so the label/detail are
+ // computed against empty args here — a tool whose label
+ // doesn't depend on its arguments (the common case: a policy
+ // label, or a name-derived default) already reads correctly;
+ // one whose detail DOES depend on args (e.g. a search query)
+ // is recomputed with the real arguments on `ToolCallCompleted`
+ // below and forwarded on the wire as
+ // `tool_display_label`/`tool_display_detail` there too.
+ let (display_label, display_detail) =
+ self.resolve_display(tool_name, &serde_json::Value::Null);
match &self.scope {
None => self.send(AgentProgress::ToolCallStarted {
call_id: call_id.as_str().to_string(),
tool_name: tool_name.clone(),
arguments: serde_json::Value::Null,
iteration,
- display_label: Some(humanize_tool_name(tool_name)),
- display_detail: None,
+ display_label,
+ display_detail,
}),
Some(s) => self.send(AgentProgress::SubagentToolCallStarted {
agent_id: s.agent_id.clone(),
@@ -505,8 +528,8 @@ impl EventListener for OpenhumanEventBridge {
tool_name: tool_name.clone(),
arguments: serde_json::Value::Null,
iteration,
- display_label: Some(humanize_tool_name(tool_name)),
- display_detail: None,
+ display_label,
+ display_detail,
}),
}
}
@@ -546,7 +569,7 @@ impl EventListener for OpenhumanEventBridge {
.unwrap_or(0);
let elapsed_ms = outcome
.as_ref()
- .map(|(_, _, e, _)| *e)
+ .map(|(_, _, e, ..)| *e)
.filter(|e| *e > 0)
.unwrap_or(stamped_elapsed);
// Tool result text, captured by the harness when
@@ -559,13 +582,33 @@ impl EventListener for OpenhumanEventBridge {
};
let output_chars = outcome
.as_ref()
- .map(|(_, _, _, c)| *c)
+ .map(|(_, _, _, c, _)| *c)
.filter(|c| *c > 0)
.unwrap_or_else(|| output_text.chars().count());
+ // Structured, tool-specific result payload the middleware
+ // copied from `ToolResult.metadata` (e.g. web search results).
+ let structured = outcome.as_ref().and_then(|(.., s)| s.clone());
// Carry the classified failure onto whichever completion event
// this projects — main-agent OR sub-agent (#4459). Previously
// the sub-agent branch dropped it on the floor.
- let failure = outcome.and_then(|(_, f, _, _)| f);
+ let failure = outcome.and_then(|(_, f, ..)| f);
+ // Recompute the label/detail with the REAL call arguments
+ // (unlike `ToolCallStarted`, this event's `input` is the
+ // actual arguments the harness captured), so a tool whose
+ // detail depends on its args — a search query, a target
+ // email — surfaces it here even when the started event
+ // couldn't.
+ let args_for_display = input.clone().unwrap_or(serde_json::Value::Null);
+ let (display_label, display_detail) =
+ self.resolve_display(tool_name, &args_for_display);
+ tracing::debug!(
+ call_id = call_id.as_str(),
+ tool_name = tool_name.as_str(),
+ success,
+ elapsed_ms,
+ has_structured = structured.is_some(),
+ "[tool-presentation] projecting ToolCallCompleted with resolved label/detail"
+ );
match &self.scope {
None => self.send(AgentProgress::ToolCallCompleted {
call_id: call_id.as_str().to_string(),
@@ -577,6 +620,9 @@ impl EventListener for OpenhumanEventBridge {
elapsed_ms,
iteration,
failure,
+ display_label,
+ display_detail,
+ structured,
}),
Some(s) => self.send(AgentProgress::SubagentToolCallCompleted {
agent_id: s.agent_id.clone(),
@@ -590,6 +636,9 @@ impl EventListener for OpenhumanEventBridge {
elapsed_ms,
iteration,
failure,
+ display_label,
+ display_detail,
+ structured,
}),
}
}
diff --git a/crates/openhuman-core/src/agent/tinyagents/observability_tests.rs b/crates/openhuman-core/src/agent/tinyagents/observability_tests.rs
index 9e3f8eeb478..31aef3de149 100644
--- a/crates/openhuman-core/src/agent/tinyagents/observability_tests.rs
+++ b/crates/openhuman-core/src/agent/tinyagents/observability_tests.rs
@@ -1,6 +1,47 @@
use super::*;
use tinyagents_harness::events::EventSink;
+/// A tool whose `display_label`/`display_detail` depend on the call
+/// arguments — the shape a dynamic Composio/MCP/integration tool takes (e.g.
+/// [`crate::integrations::composio::action_tool::ComposioActionTool`]'s
+/// "Gmail send email"). Used to prove the bridge calls the tool's OWN
+/// presentation methods instead of always deriving a label from the bare
+/// tool name.
+struct FakeLabeledTool;
+
+#[async_trait::async_trait]
+impl tinytools::Tool for FakeLabeledTool {
+ fn name(&self) -> &str {
+ "fake_send_email"
+ }
+
+ fn description(&self) -> &str {
+ "sends an email (test double)"
+ }
+
+ fn parameters_schema(&self) -> serde_json::Value {
+ serde_json::json!({"type": "object"})
+ }
+
+ async fn execute(&self, _args: serde_json::Value) -> anyhow::Result {
+ Ok(tinytools::ToolResult::success("sent"))
+ }
+
+ fn display_label(&self, _args: &serde_json::Value) -> Option {
+ Some("Sending email".to_string())
+ }
+
+ fn display_detail(&self, args: &serde_json::Value) -> Option {
+ args.get("to").and_then(|v| v.as_str()).map(str::to_string)
+ }
+}
+
+fn fake_tool_sets() -> Vec>>> {
+ vec![Arc::new(vec![
+ Box::new(FakeLabeledTool) as Box
+ ])]
+}
+
#[tokio::test]
async fn bridge_forwards_tool_and_cost_progress() {
let (tx, mut rx) = tokio::sync::mpsc::channel(64);
@@ -66,6 +107,7 @@ async fn model_completed_projects_generation_with_content_and_provider() {
Arc::default(),
Arc::default(),
Arc::default(),
+ Vec::new(),
);
let sink = EventSink::new();
sink.subscribe(bridge.clone());
@@ -138,6 +180,7 @@ async fn subagent_model_completed_carries_task_attribution() {
Arc::default(),
Arc::default(),
Arc::default(),
+ Vec::new(),
);
let sink = EventSink::new();
sink.subscribe(bridge.clone());
@@ -331,3 +374,83 @@ async fn duplicate_usage_for_same_model_call_is_recorded_once() {
// `ToolStarted` arm above — it no longer special-cases a sentinel). The test
// referenced the deleted constant (a stale reference reintroduced by a merge)
// and asserted behaviour that no longer exists.
+
+/// #6XXX (tool-call presentation): `ToolCallStarted`/`ToolCallCompleted` must
+/// carry the tool's OWN `display_label`/`display_detail` when the bridge was
+/// built with the turn's tool sets, not a name-derived guess — proven with a
+/// fake tool whose label is a fixed phrase and whose detail comes from a
+/// `"to"` argument only known once the call completes.
+#[tokio::test]
+async fn tool_call_events_use_the_tool_s_own_display_label_and_detail() {
+ let (tx, mut rx) = tokio::sync::mpsc::channel(64);
+ let bridge = OpenhumanEventBridge::with_scope(
+ Some(tx),
+ "mock-model",
+ "managed",
+ 10,
+ None,
+ Arc::default(),
+ Arc::default(),
+ Arc::default(),
+ Arc::default(),
+ fake_tool_sets(),
+ );
+ let sink = EventSink::new();
+ sink.subscribe(bridge.clone());
+
+ sink.emit(AgentEvent::ModelStarted {
+ call_id: "c1".into(),
+ model: "mock-model".to_string(),
+ });
+ sink.emit(AgentEvent::ToolStarted {
+ call_id: "c1".into(),
+ tool_name: "fake_send_email".to_string(),
+ });
+ sink.emit(AgentEvent::ToolCompleted {
+ call_id: "c1".into(),
+ tool_name: "fake_send_email".to_string(),
+ started_at_ms: None,
+ input: Some(serde_json::json!({"to": "steven@example.com"})),
+ output: Some(serde_json::Value::String("sent".to_string())),
+ duration_ms: Some(5),
+ output_bytes: Some(4),
+ error: None,
+ metadata: None,
+ });
+
+ let mut started_label = None;
+ let mut completed = None;
+ while let Ok(p) = rx.try_recv() {
+ match p {
+ AgentProgress::ToolCallStarted {
+ display_label,
+ display_detail,
+ ..
+ } => started_label = Some((display_label, display_detail)),
+ AgentProgress::ToolCallCompleted {
+ display_label,
+ display_detail,
+ ..
+ } => completed = Some((display_label, display_detail)),
+ _ => {}
+ }
+ }
+
+ let (started_label, started_detail) = started_label.expect("ToolCallStarted projected");
+ assert_eq!(
+ started_label,
+ Some("Sending email".to_string()),
+ "the started label comes from the tool's own display_label, not a humanized name"
+ );
+ // No arguments exist yet at call-start, so the arg-derived detail is
+ // absent — this is recovered on the completed event below.
+ assert_eq!(started_detail, None);
+
+ let (completed_label, completed_detail) = completed.expect("ToolCallCompleted projected");
+ assert_eq!(completed_label, Some("Sending email".to_string()));
+ assert_eq!(
+ completed_detail,
+ Some("steven@example.com".to_string()),
+ "the completed detail is recomputed from the real call arguments"
+ );
+}
diff --git a/crates/openhuman-core/src/agent/tinyagents/turn_runner.rs b/crates/openhuman-core/src/agent/tinyagents/turn_runner.rs
index b0c222c87ff..ac7215dd3b8 100644
--- a/crates/openhuman-core/src/agent/tinyagents/turn_runner.rs
+++ b/crates/openhuman-core/src/agent/tinyagents/turn_runner.rs
@@ -236,6 +236,11 @@ async fn run_turn_via_tinyagents_inner(
// the exact same `Arc`-shared instances, so retain only the cheap Arc clone
// for a hosted invocation (never clone the tools themselves).
let hosted_tool_sets = hosted_root.as_ref().map(|_| tool_sets.clone());
+ // Retained for the event bridge (cheap `Arc` clones — never the tools
+ // themselves) so it can resolve a live `&dyn Tool` by name and call the
+ // tool's OWN `display_label`/`display_detail` instead of only ever
+ // guessing from the bare name (issue: tool-call presentation).
+ let bridge_tool_sets = tool_sets.clone();
// The turn's crate `ChatModel` set (`turn_models`) and the provider telemetry
// id are built by the caller via `build_turn_models` — the seam entry is
// crate-native and no longer names `Provider` (issue #4249, Phase 5). The
@@ -455,6 +460,7 @@ async fn run_turn_via_tinyagents_inner(
tool_names.clone(),
failure_map.clone(),
provider_usage_carry.clone(),
+ bridge_tool_sets,
);
events.subscribe(bridge.clone());
bridge
diff --git a/crates/openhuman-core/src/channels/proactive.rs b/crates/openhuman-core/src/channels/proactive.rs
index 6dc7de171d0..1b9f7441bec 100644
--- a/crates/openhuman-core/src/channels/proactive.rs
+++ b/crates/openhuman-core/src/channels/proactive.rs
@@ -219,6 +219,8 @@ impl EventHandler for ProactiveMessageSubscriber {
subagent: None,
tool_display_label: None,
tool_display_detail: None,
+ elapsed_ms: None,
+ structured: None,
usage: None,
// Proactive delivery is emitted outside the seq-stamping progress
// bridge; leave `seq` unset (older clients ignore it).
diff --git a/crates/openhuman-core/src/core/socketio.rs b/crates/openhuman-core/src/core/socketio.rs
index bab189d3e13..cc2913217d9 100644
--- a/crates/openhuman-core/src/core/socketio.rs
+++ b/crates/openhuman-core/src/core/socketio.rs
@@ -323,6 +323,24 @@ pub struct WebChannelEvent {
/// shown after [`Self::tool_display_label`].
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_display_detail: Option,
+ /// Milliseconds the tool call took to execute. Present on `tool_result` /
+ /// `subagent_tool_result`, mirroring `AgentProgress::ToolCallCompleted`'s
+ /// `elapsed_ms` / `SubagentToolCallCompleted`'s `elapsed_ms` — carried
+ /// as a plain top-level field (in addition to `subagent.elapsed_ms` for
+ /// the sub-agent case) so a frontend that only reads flat fields still
+ /// gets real timing instead of guessing from wall-clock deltas.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub elapsed_ms: Option,
+ /// Structured, tool-specific result payload copied from a tool's
+ /// `ToolResult::metadata` when it is a JSON object carrying a `"kind"`
+ /// discriminator, e.g.
+ /// `{"kind":"web_search","query":"...","provider":"...","results":[...]}`.
+ /// Present on `tool_result` / `subagent_tool_result` only for tools that
+ /// populate metadata of that shape (currently the web-search tools); the
+ /// model-facing `output` text is unaffected and stays byte-identical to
+ /// what the model itself saw.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub structured: Option,
/// Holistic token/cost/context usage for a completed turn (parent +
/// sub-agents), carried on `chat_done`. Lets the UI footer show session
/// tokens, USD cost, and real context-window utilisation, with a
diff --git a/crates/openhuman-core/src/platform/socket/medulla/envelope_tests.rs b/crates/openhuman-core/src/platform/socket/medulla/envelope_tests.rs
index 6371340f661..3f26378d9d7 100644
--- a/crates/openhuman-core/src/platform/socket/medulla/envelope_tests.rs
+++ b/crates/openhuman-core/src/platform/socket/medulla/envelope_tests.rs
@@ -132,6 +132,9 @@ fn tool_call_and_result_map_to_their_kinds() {
elapsed_ms: 5,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
};
match progress_to_event_kind(&completed) {
Some(HarnessEventKind::ToolResult(tr)) => {
diff --git a/crates/openhuman-core/src/search/tools/brave.rs b/crates/openhuman-core/src/search/tools/brave.rs
index 9cb5b9ed9bb..23408c99796 100644
--- a/crates/openhuman-core/src/search/tools/brave.rs
+++ b/crates/openhuman-core/src/search/tools/brave.rs
@@ -233,6 +233,28 @@ impl Tool for BraveWebSearchTool {
if options.prefer_markdown {
out.markdown_formatted = Some(render_web_markdown(&results, &query, count));
}
+ // Host-only structured payload for the chat UI's tool-call
+ // presentation — never rendered to the model, so `render_web_plain`'s
+ // text above (and the cache key that depends on it) is unaffected.
+ let structured_results: Vec> = results
+ .iter()
+ .map(|r| crate::search::tools::WebSearchResultRef {
+ title: if r.title.trim().is_empty() {
+ "Untitled"
+ } else {
+ r.title.trim()
+ },
+ url: r.url.as_str(),
+ published: r.age.as_deref(),
+ excerpt: Some(r.description.as_str()).filter(|d| !d.trim().is_empty()),
+ })
+ .collect();
+ out.metadata = Some(crate::search::tools::web_search_metadata(
+ &query,
+ "Brave",
+ &structured_results,
+ count,
+ ));
Ok(out)
}
}
@@ -406,7 +428,31 @@ impl Tool for BraveNewsSearchTool {
));
}
}
- Ok(ToolResult::success(lines.join("\n")))
+ let mut out = ToolResult::success(lines.join("\n"));
+ // Host-only structured payload — never rendered to the model, so the
+ // plain text above (and the cache key that depends on it) is
+ // unaffected.
+ let structured_results: Vec> = parsed
+ .results
+ .iter()
+ .map(|r| crate::search::tools::WebSearchResultRef {
+ title: if r.title.trim().is_empty() {
+ "Untitled"
+ } else {
+ r.title.trim()
+ },
+ url: r.url.as_str(),
+ published: r.age.as_deref(),
+ excerpt: Some(r.description.as_str()).filter(|d| !d.trim().is_empty()),
+ })
+ .collect();
+ out.metadata = Some(crate::search::tools::web_search_metadata(
+ &query,
+ "Brave",
+ &structured_results,
+ count,
+ ));
+ Ok(out)
}
}
diff --git a/crates/openhuman-core/src/search/tools/exa.rs b/crates/openhuman-core/src/search/tools/exa.rs
index f52fa97b4ce..0918264a6f4 100644
--- a/crates/openhuman-core/src/search/tools/exa.rs
+++ b/crates/openhuman-core/src/search/tools/exa.rs
@@ -1,10 +1,8 @@
//! Exa neural search integration -- direct API (BYOK, not backend-proxied).
//!
//! **Scope**: Agent + CLI/RPC.
-//!
//! **Endpoints**: `POST https://api.exa.ai/search`,
//! `POST https://api.exa.ai/findSimilar`, `POST https://api.exa.ai/contents`.
-//!
//! **Auth**: `x-api-key: `.
//!
//! When the user selects `exa` as their search engine and has saved their own
@@ -508,7 +506,25 @@ impl Tool for ExaSearchTool {
let limit = self.client.requested_results(&args);
let body = self.build_body(&args, &query);
let results = self.client.post_documents("search", body).await?;
- Ok(self.client.to_result(&results, &query, limit, &options))
+ let mut result = self.client.to_result(&results, &query, limit, &options);
+ let excerpts: Vec> = results.iter().map(ExaResultItem::excerpt).collect();
+ let structured: Vec> = results
+ .iter()
+ .zip(&excerpts)
+ .map(|(r, e)| super::WebSearchResultRef {
+ title: r.display_title(),
+ url: &r.url,
+ published: r.published_date.as_deref(),
+ excerpt: e.as_deref(),
+ })
+ .collect();
+ result.metadata = Some(super::web_search_metadata(
+ &query,
+ "Exa",
+ &structured,
+ limit,
+ ));
+ Ok(result)
}
}
diff --git a/crates/openhuman-core/src/search/tools/mod.rs b/crates/openhuman-core/src/search/tools/mod.rs
index e90588d8d25..ecd49e60c3a 100644
--- a/crates/openhuman-core/src/search/tools/mod.rs
+++ b/crates/openhuman-core/src/search/tools/mod.rs
@@ -39,3 +39,67 @@ pub use web_search::WebSearchTool;
// Crate-internal: the `tools.web_search` RPC reuses the same provider
// resolution so both managed-search surfaces attribute a call identically.
pub(crate) use web_search::resolve_managed_provider;
+
+/// Maximum characters kept from a web-search result's excerpt when it is
+/// copied into [`ToolResult::metadata`][tinytools::ToolResult] for the UI
+/// (issue: tool-call presentation). Deliberately smaller than the ~500-char
+/// budget the model-facing text renders with — this metadata is for a compact
+/// result card, not the full context the model reads.
+pub(crate) const WEB_SEARCH_METADATA_EXCERPT_CHARS: usize = 300;
+
+/// One search result, borrowed from whatever provider-specific struct the
+/// caller already has, for building the structured
+/// `{"kind":"web_search",...}` metadata every model-facing web-search tool
+/// attaches to its [`tinytools::ToolResult::metadata`] (issue: tool-call
+/// presentation). Kept separate from the model-facing rendering so a change
+/// here can never perturb the byte-identical prompt text the provider's cache
+/// keys on.
+pub(crate) struct WebSearchResultRef<'a> {
+ pub title: &'a str,
+ pub url: &'a str,
+ pub published: Option<&'a str>,
+ pub excerpt: Option<&'a str>,
+}
+
+/// Build the structured web-search metadata payload:
+/// `{"kind":"web_search","query":...,"provider":...,"results":[{"title":...,
+/// "url":...,"published":...?,"excerpt":...?}]}`. `max_results` caps how many
+/// of `results` are copied in, matching whatever cap the tool's own
+/// model-facing rendering already applies so the structured payload never
+/// claims more results exist than the model was shown.
+///
+/// This is metadata (host-only, never rendered to the model) — see
+/// [`tinytools::ToolResult::metadata`]'s own docs on that boundary.
+pub(crate) fn web_search_metadata(
+ query: &str,
+ provider: &str,
+ results: &[WebSearchResultRef<'_>],
+ max_results: usize,
+) -> serde_json::Value {
+ let results_json: Vec = results
+ .iter()
+ .take(max_results)
+ .map(|r| {
+ let mut obj = serde_json::json!({
+ "title": r.title,
+ "url": r.url,
+ });
+ if let Some(published) = r.published.map(str::trim).filter(|s| !s.is_empty()) {
+ obj["published"] = serde_json::json!(published);
+ }
+ if let Some(excerpt) = r.excerpt.map(str::trim).filter(|s| !s.is_empty()) {
+ obj["excerpt"] = serde_json::json!(crate::util::truncate_with_ellipsis(
+ excerpt,
+ WEB_SEARCH_METADATA_EXCERPT_CHARS
+ ));
+ }
+ obj
+ })
+ .collect();
+ serde_json::json!({
+ "kind": "web_search",
+ "query": query,
+ "provider": provider,
+ "results": results_json,
+ })
+}
diff --git a/crates/openhuman-core/src/search/tools/querit.rs b/crates/openhuman-core/src/search/tools/querit.rs
index acf2d110d7a..83bb7b96b6a 100644
--- a/crates/openhuman-core/src/search/tools/querit.rs
+++ b/crates/openhuman-core/src/search/tools/querit.rs
@@ -515,6 +515,37 @@ impl Tool for QueritSearchTool {
result.markdown_formatted =
Some(self.render_results_markdown(&search_resp.results.result, query));
}
+ // Host-only structured payload for the chat UI's tool-call
+ // presentation — never rendered to the model, so `render_results_plain`'s
+ // text above (and the cache key that depends on it) is unaffected.
+ let snippets: Vec> = search_resp
+ .results
+ .result
+ .iter()
+ .map(QueritResultItem::snippet_text)
+ .collect();
+ let structured_results: Vec> = search_resp
+ .results
+ .result
+ .iter()
+ .zip(snippets.iter())
+ .map(|(item, snippet)| crate::search::tools::WebSearchResultRef {
+ title: item
+ .title
+ .as_deref()
+ .filter(|t| !t.trim().is_empty())
+ .unwrap_or("Untitled"),
+ url: item.url.as_str(),
+ published: item.page_age.as_deref(),
+ excerpt: snippet.as_deref(),
+ })
+ .collect();
+ result.metadata = Some(crate::search::tools::web_search_metadata(
+ query,
+ "Querit",
+ &structured_results,
+ self.max_results,
+ ));
Ok(result)
}
}
diff --git a/crates/openhuman-core/src/search/tools/tavily/search_tool.rs b/crates/openhuman-core/src/search/tools/tavily/search_tool.rs
index 070d4dc3cec..2b474216d08 100644
--- a/crates/openhuman-core/src/search/tools/tavily/search_tool.rs
+++ b/crates/openhuman-core/src/search/tools/tavily/search_tool.rs
@@ -212,6 +212,30 @@ impl Tool for TavilySearchTool {
if options.prefer_markdown {
result.markdown_formatted = Some(markdown);
}
+ // Host-only structured payload for the chat UI's tool-call
+ // presentation — never rendered to the model, so the plain/markdown
+ // text above (and the cache key that depends on it) is unaffected.
+ let structured_results: Vec> = parsed
+ .results
+ .iter()
+ .map(|r| crate::search::tools::WebSearchResultRef {
+ title: r
+ .title
+ .as_deref()
+ .map(str::trim)
+ .filter(|t| !t.is_empty())
+ .unwrap_or("Untitled"),
+ url: r.url.as_str(),
+ published: None,
+ excerpt: r.content.as_deref(),
+ })
+ .collect();
+ result.metadata = Some(crate::search::tools::web_search_metadata(
+ &query,
+ "Tavily",
+ &structured_results,
+ limit,
+ ));
Ok(result)
}
}
diff --git a/crates/openhuman-core/src/search/tools/web_search.rs b/crates/openhuman-core/src/search/tools/web_search.rs
index 25a6742ef0e..4e0fb81c637 100644
--- a/crates/openhuman-core/src/search/tools/web_search.rs
+++ b/crates/openhuman-core/src/search/tools/web_search.rs
@@ -9,7 +9,9 @@
//! to `MANAGED_DEFAULT_PROVIDER`, for UI display. `with_direct_search` can
//! swap in a `SeltzSearchTool` that bypasses the proxy; only tests use it.
-use super::{SearchResponse, SearchResultItem, SeltzSearchTool};
+use super::{
+ web_search_metadata, SearchResponse, SearchResultItem, SeltzSearchTool, WebSearchResultRef,
+};
use crate::config::Config;
use crate::integrations::IntegrationClient;
use async_trait::async_trait;
@@ -412,6 +414,26 @@ impl Tool for WebSearchTool {
result.markdown_formatted =
Some(self.render_results_markdown(&resp.results, &query, provider));
}
+ // Host-only structured payload for the chat UI's tool-call
+ // presentation (issue: tool-call presentation) — never rendered to
+ // the model, so `parse_parallel_results`'s text above (and the cache
+ // key that depends on it) is unaffected.
+ let structured_results: Vec> = resp
+ .results
+ .iter()
+ .map(|r| WebSearchResultRef {
+ title: &r.title,
+ url: &r.url,
+ published: r.publish_date.as_deref(),
+ excerpt: r.excerpts.first().map(String::as_str),
+ })
+ .collect();
+ result.metadata = Some(web_search_metadata(
+ &query,
+ provider,
+ &structured_results,
+ self.max_results,
+ ));
Ok(result)
}
}
diff --git a/crates/openhuman-core/src/threads/ops/usage.rs b/crates/openhuman-core/src/threads/ops/usage.rs
index ca2554b4184..1ad3802e58b 100644
--- a/crates/openhuman-core/src/threads/ops/usage.rs
+++ b/crates/openhuman-core/src/threads/ops/usage.rs
@@ -92,6 +92,17 @@ pub(super) fn transcript_spend(transcript: &SessionTranscript) -> TranscriptSpen
let Some(usage) = message.turn_usage.as_ref() else {
continue;
};
+ // A text-dialect tool round's issuing row carries a provenance-only
+ // record (its calls, zero spend); the turn's spend is on its final row.
+ // It is not a turn that spent, and must not become the "last" one.
+ if !usage.tool_calls.is_empty()
+ && usage.usage.input == 0
+ && usage.usage.output == 0
+ && usage.usage.cached_input == 0
+ && usage.usage.cost_usd == 0.0
+ {
+ continue;
+ }
spend.input_tokens = spend.input_tokens.saturating_add(usage.usage.input);
spend.output_tokens = spend.output_tokens.saturating_add(usage.usage.output);
spend.cached_input_tokens = spend
diff --git a/crates/openhuman-core/src/threads/ops/usage_tests.rs b/crates/openhuman-core/src/threads/ops/usage_tests.rs
index 3da5c9cc05b..a281b96d9c3 100644
--- a/crates/openhuman-core/src/threads/ops/usage_tests.rs
+++ b/crates/openhuman-core/src/threads/ops/usage_tests.rs
@@ -306,3 +306,52 @@ fn reports_no_usage_for_an_unknown_or_spendless_thread() {
assert_eq!(spend.root.turns, 0, "but it recorded no spend");
assert_eq!(spend.root.input_tokens, 0);
}
+
+/// A text-dialect tool round's issuing row carries a provenance-only record —
+/// its calls, zero spend — beside the turn's real record on the final row.
+/// It is not a turn that spent and must not take over the last-turn view.
+#[test]
+fn provenance_only_tool_round_records_are_not_counted_as_turns() {
+ let tmp = tempfile::tempdir().expect("tempdir");
+ let thread = "thread-text-dialect";
+ let path = tmp
+ .path()
+ .join("session_raw")
+ .join("1790000001_orchestrator_text.jsonl");
+ std::fs::create_dir_all(path.parent().unwrap()).expect("create session_raw");
+
+ let mut issuing = TranscriptMessage::assistant("");
+ issuing.turn_usage = Some(TurnUsage {
+ tool_calls: vec![tinyagents_session::transcript::TranscriptToolCall {
+ id: "call-1".into(),
+ name: "web_search_tool".into(),
+ arguments: "{}".into(),
+ extra_content: None,
+ }],
+ ..turn_usage(0, 0, 0)
+ });
+ let rows = vec![
+ TranscriptMessage::new("user", "q"),
+ issuing,
+ TranscriptMessage::new(
+ "user",
+ "[Tool results]\n\nok\n \n",
+ ),
+ TranscriptMessage::assistant("a"),
+ ];
+ append_transcript_turn(
+ &path,
+ &[],
+ &rows,
+ &meta("orchestrator", "root", Some(thread)),
+ Some(&turn_usage(5_000, 50, 1_000)),
+ Some("req-0"),
+ )
+ .expect("append turn");
+
+ let spend = thread_spend(tmp.path(), thread);
+
+ assert_eq!(spend.root.turns, 1);
+ assert_eq!(spend.root.input_tokens, 5_000);
+ assert_eq!(spend.root.last_input_tokens, 5_000);
+}
diff --git a/crates/openhuman-core/src/threads/transcript_view/mod.rs b/crates/openhuman-core/src/threads/transcript_view/mod.rs
index d94ae0ad7e4..3ac807f9376 100644
--- a/crates/openhuman-core/src/threads/transcript_view/mod.rs
+++ b/crates/openhuman-core/src/threads/transcript_view/mod.rs
@@ -105,6 +105,9 @@ fn parse_cursor(cursor: Option<&str>) -> usize {
#[cfg(test)]
#[path = "transcript_view_tests.rs"]
mod tests;
+#[cfg(test)]
+#[path = "transcript_view_tool_round_tests.rs"]
+mod tool_round_tests;
#[cfg(test)]
#[path = "transcript_ordering_tests.rs"]
diff --git a/crates/openhuman-core/src/threads/transcript_view/project.rs b/crates/openhuman-core/src/threads/transcript_view/project.rs
index 3aa9924a3dc..aaf1601e30e 100644
--- a/crates/openhuman-core/src/threads/transcript_view/project.rs
+++ b/crates/openhuman-core/src/threads/transcript_view/project.rs
@@ -10,6 +10,9 @@ use std::collections::{HashSet, VecDeque};
use std::path::{Path, PathBuf};
use tinyagents_session::transcript::{self, CompactionMarker, DisplayMessage, DisplayRecord};
+use tinytools_agent::dialect::{parse_replayed_results, ToolResultEntry};
+
+use crate::agent::messages::TOOL_RESULT_FAILURES_METADATA_KEY;
use super::resolve;
use super::subagents;
@@ -209,6 +212,12 @@ impl Projector {
log::debug!("{LOG_PREFIX} sanitize: dropped system line from projection");
}
"user" => {
+ // A text dialect folds a round's results into one user turn;
+ // it is tool output, never the user's words.
+ if let Some(results) = parse_replayed_results(&msg.message.content) {
+ project_text_tool_results(msg, results, &mut self.items, &mut self.pending);
+ return;
+ }
// A legacy turn without request ids still restarts the step
// count at its prompt.
self.step = 0;
@@ -310,6 +319,19 @@ impl Projector {
for (call_id, name, arguments) in tool_calls {
let args = parse_tool_args(&arguments);
+ // Repair legacy rows which put aggregate calls on the final
+ // answer after their result had already projected as an orphan.
+ if let Some(DisplayItem::ToolCall {
+ name: settled_name,
+ args: settled_args,
+ ..
+ }) = settled_orphan_mut(&mut self.items, &call_id)
+ {
+ log::debug!("{LOG_PREFIX} call {call_id} recorded after its result — merged");
+ *settled_name = name;
+ *settled_args = args;
+ continue;
+ }
if !call_id.is_empty() {
self.seen_call_ids.insert(call_id.clone());
}
@@ -438,6 +460,83 @@ fn unwrap_tool_result(raw: &str) -> (String, Option) {
)
}
+/// Pair each result of a text-dialect `[Tool results]` row with its pending
+/// call. Failure status comes from the ids the session codec recorded on the
+/// row ([`TOOL_RESULT_FAILURES_METADATA_KEY`]); a result with no pending call
+/// surfaces as an orphan row, as for a native `tool` line.
+fn project_text_tool_results(
+ msg: &DisplayMessage,
+ results: Vec,
+ items: &mut Vec,
+ pending: &mut VecDeque<(String, usize)>,
+) {
+ let failed: Vec<&str> = msg
+ .message
+ .extra_metadata
+ .as_ref()
+ .and_then(|meta| meta.get(TOOL_RESULT_FAILURES_METADATA_KEY))
+ .and_then(serde_json::Value::as_array)
+ .map(|ids| ids.iter().filter_map(serde_json::Value::as_str).collect())
+ .unwrap_or_default();
+ log::debug!(
+ "{LOG_PREFIX} text-dialect results row results={} failed={} pending={}",
+ results.len(),
+ failed.len(),
+ pending.len()
+ );
+ for result in results {
+ let (status, failure) = if failed.contains(&result.tool_call_id.as_str()) {
+ (
+ ToolCallStatus::Error,
+ Some(ToolCallFailure { detail: None }),
+ )
+ } else {
+ (ToolCallStatus::Success, None)
+ };
+ if let Some(idx) = take_pending_by_id(pending, &result.tool_call_id) {
+ if let Some(DisplayItem::ToolCall {
+ result: slot,
+ status: status_slot,
+ failure: failure_slot,
+ ..
+ }) = items.get_mut(idx)
+ {
+ *slot = Some(result.content);
+ *status_slot = status;
+ *failure_slot = failure;
+ continue;
+ }
+ }
+ items.push(DisplayItem::ToolCall {
+ call_id: result.tool_call_id,
+ name: "tool".to_string(),
+ args: None,
+ result: Some(result.content),
+ status,
+ failure,
+ });
+ }
+}
+
+/// The already-settled orphan row for `call_id` in the current turn — a result
+/// that projected before any call named it.
+fn settled_orphan_mut<'a>(
+ items: &'a mut [DisplayItem],
+ call_id: &str,
+) -> Option<&'a mut DisplayItem> {
+ let turn_start = items
+ .iter()
+ .rposition(|item| matches!(item, DisplayItem::TurnBoundary { .. }))
+ .map_or(0, |idx| idx + 1);
+ items[turn_start..].iter_mut().find(|item| {
+ matches!(
+ item,
+ DisplayItem::ToolCall { call_id: id, name, result: Some(_), .. }
+ if id == call_id && name == "tool"
+ )
+ })
+}
+
/// Remove and return the pending entry whose call id matches `id`, if any.
fn take_pending_by_id(pending: &mut VecDeque<(String, usize)>, id: &str) -> Option {
let pos = pending.iter().position(|(cid, _)| cid == id)?;
diff --git a/crates/openhuman-core/src/threads/transcript_view/transcript_view_tool_round_tests.rs b/crates/openhuman-core/src/threads/transcript_view/transcript_view_tool_round_tests.rs
new file mode 100644
index 00000000000..1c2376f3bd2
--- /dev/null
+++ b/crates/openhuman-core/src/threads/transcript_view/transcript_view_tool_round_tests.rs
@@ -0,0 +1,300 @@
+//! Tool-round projection tests: text-dialect rounds persisted through the real
+//! session codec and writer, and transcripts written before calls rode their
+//! issuing row.
+
+use super::project::{project_records, project_thread};
+use super::types::{DisplayItem, ToolCallStatus};
+use crate::agent::messages::ChatMessage;
+use tempfile::TempDir;
+use tinyagents_session::transcript::{self, read_transcript_display};
+
+/// Write a raw JSONL transcript (meta header + `body` lines) for `thread_id`.
+fn write_raw(workspace: &std::path::Path, stem: &str, thread_id: &str, body: &[&str]) {
+ let path = transcript::resolve_keyed_transcript_path(workspace, stem).expect("resolve");
+ let mut buf = format!(
+ r#"{{"_meta":{{"version":1,"agent":"orchestrator","dispatcher":"xml","created":"2026-09-24T00:00:00Z","updated":"2026-09-24T00:00:10Z","turn_count":1,"input_tokens":0,"output_tokens":0,"cached_input_tokens":0,"charged_amount_usd":0.0,"thread_id":"{thread_id}"}}}}"#
+ );
+ buf.push('\n');
+ for line in body {
+ buf.push_str(line);
+ buf.push('\n');
+ }
+ std::fs::write(&path, buf).expect("write raw transcript");
+}
+
+/// A text-dialect (`xml`/`python`/`pformat`) tool turn, persisted through the
+/// real runtime codec and writer, must attach each call to the assistant row
+/// that issued it and pair it with its `[Tool results]` entry — so the derived
+/// transcript reports settled calls as success/error, not "running".
+///
+/// Regression: the codec put every tool outcome of the turn on the turn-level
+/// usage record, which the writer attaches to the turn's *final* assistant row.
+/// The calls then landed after their own results, the results rendered as a
+/// user message, and every reloaded call projected as `running` (the UI showed
+/// them as cancelled).
+#[test]
+fn text_dialect_tool_turn_projects_calls_on_their_issuing_row_as_settled() {
+ use crate::agent::messages::{ConversationMessage, ToolResultMessage};
+ use crate::agent::session_host::OpenHumanTranscriptCodec;
+ use crate::agent::tinyagents::host::OpenHumanRunContext;
+ use crate::inference::provider::ToolCall;
+ use tinyagents_runtime::{ResumeMode, TranscriptCodec, TranscriptTurnOptions};
+ use tinyinference_llm::message::Message;
+
+ let dir = TempDir::new().unwrap();
+
+ // What the session driver persists for a text dialect: the conversation
+ // rendered through the dialect's replay form.
+ let conversation = vec![
+ ConversationMessage::AssistantToolCalls {
+ text: None,
+ tool_calls: vec![
+ ToolCall {
+ id: "call_web_search_1".into(),
+ name: "web_search_tool".into(),
+ arguments: r#"{"query":"rust async traits"}"#.into(),
+ extra_content: None,
+ },
+ ToolCall {
+ id: "call_file_read_1".into(),
+ name: "file_read".into(),
+ arguments: r#"{"path":"README.md"}"#.into(),
+ extra_content: None,
+ },
+ ],
+ reasoning_content: None,
+ extra_metadata: None,
+ },
+ ConversationMessage::ToolResults(vec![
+ ToolResultMessage {
+ tool_call_id: "call_web_search_1".into(),
+ content: "Search results for: rust async traits".into(),
+ },
+ ToolResultMessage {
+ tool_call_id: "call_file_read_1".into(),
+ content: "unknown tool `file_read`".into(),
+ },
+ ]),
+ ConversationMessage::Chat(ChatMessage::assistant("Here is what I found.")),
+ ];
+ let rendered = crate::agent::message_convert::provider_messages_from_conversation(
+ &tinytools_agent::dialect::XmlDialect,
+ &conversation,
+ );
+ let mut next = vec![Message::user("search the web and read the README")];
+ next.extend(crate::agent::message_convert::history_to_messages(
+ &rendered,
+ ));
+
+ let context = OpenHumanRunContext::new();
+ {
+ let mut sidecar = context.session_sidecar.lock().unwrap();
+ sidecar.model_calls = 2;
+ sidecar.input_tokens = 40;
+ sidecar.output_tokens = 12;
+ sidecar.resolved_route = Some(tinyinference_llm::model::ResolvedModelRoute {
+ provider: "e2e".into(),
+ model: "e2e-mock-model".into(),
+ route: "e2e".into(),
+ });
+ for (id, name, arguments, success, content) in [
+ (
+ "call_web_search_1",
+ "web_search_tool",
+ serde_json::json!({"query": "rust async traits"}),
+ true,
+ "Search results for: rust async traits",
+ ),
+ (
+ "call_file_read_1",
+ "file_read",
+ serde_json::json!({"path": "README.md"}),
+ false,
+ "unknown tool `file_read`",
+ ),
+ ] {
+ sidecar
+ .tool_outcomes
+ .push(crate::agent::tinyagents::ToolCallOutcome {
+ call_id: id.into(),
+ name: name.into(),
+ arguments,
+ success,
+ content: content.into(),
+ duration_ms: 1,
+ });
+ }
+ }
+ let options = TranscriptTurnOptions {
+ request_id: Some("req-xml".into()),
+ thread_id: Some("thr_xml".into()),
+ stream: false,
+ resume: ResumeMode::Never,
+ context,
+ };
+ let rows = OpenHumanTranscriptCodec
+ .reconcile(&[], &[], &next, &options)
+ .unwrap();
+ let usage = OpenHumanTranscriptCodec.turn_usage(&options).unwrap();
+
+ let meta = transcript::TranscriptMeta {
+ session_id: None,
+ parent_session_id: None,
+ agent_name: "orchestrator".into(),
+ agent_id: Some("orchestrator".into()),
+ agent_type: Some("root".into()),
+ dispatcher: "xml".into(),
+ provider: None,
+ model: None,
+ created: "2026-09-24T00:00:00Z".into(),
+ updated: "2026-09-24T00:00:00Z".into(),
+ turn_count: 1,
+ input_tokens: 0,
+ output_tokens: 0,
+ cached_input_tokens: 0,
+ charged_amount_usd: 0.0,
+ thread_id: Some("thr_xml".into()),
+ task_id: None,
+ };
+ let path = transcript::resolve_keyed_transcript_path(dir.path(), "xml_orchestrator").unwrap();
+ transcript::append_transcript_turn(&path, &[], &rows, &meta, usage.as_ref(), Some("req-xml"))
+ .unwrap();
+
+ // The durable rows: the calls ride the issuing row, not the final answer.
+ let persisted = transcript::read_transcript(&path).unwrap();
+ let assistants: Vec<_> = persisted
+ .messages
+ .iter()
+ .filter(|m| m.role == "assistant")
+ .collect();
+ assert_eq!(assistants.len(), 2);
+ let issued: Vec = assistants[0]
+ .turn_usage
+ .as_ref()
+ .map(|tu| tu.tool_calls.iter().map(|c| c.id.clone()).collect())
+ .unwrap_or_default();
+ assert_eq!(
+ issued,
+ vec![
+ "call_web_search_1".to_string(),
+ "call_file_read_1".to_string()
+ ],
+ "the issuing assistant row carries its calls"
+ );
+ assert!(
+ assistants[1]
+ .turn_usage
+ .as_ref()
+ .is_some_and(|tu| tu.tool_calls.is_empty() && tu.usage.input == 40),
+ "the final answer carries the turn's usage but none of its calls"
+ );
+
+ // The projection: calls settled with their results, no raw results bubble.
+ let display = read_transcript_display(&path).unwrap();
+ let items = project_records(&display.records);
+ let calls: Vec<_> = items
+ .iter()
+ .filter_map(|item| match item {
+ DisplayItem::ToolCall {
+ call_id,
+ name,
+ result,
+ status,
+ ..
+ } => Some((call_id.clone(), name.clone(), result.clone(), *status)),
+ _ => None,
+ })
+ .collect();
+ assert_eq!(
+ calls.len(),
+ 2,
+ "one item per call, no duplicates: {items:?}"
+ );
+ assert_eq!(calls[0].0, "call_web_search_1");
+ assert_eq!(calls[0].1, "web_search_tool");
+ assert_eq!(
+ calls[0].2.as_deref(),
+ Some("Search results for: rust async traits")
+ );
+ assert_eq!(calls[0].3, ToolCallStatus::Success);
+ assert_eq!(calls[1].0, "call_file_read_1");
+ assert_eq!(calls[1].1, "file_read");
+ assert_eq!(calls[1].2.as_deref(), Some("unknown tool `file_read`"));
+ assert_eq!(calls[1].3, ToolCallStatus::Error);
+ assert!(
+ !items.iter().any(|item| matches!(
+ item,
+ DisplayItem::UserMessage { content, .. } if content.starts_with("[Tool results]")
+ )),
+ "a tool-results turn is not a user message: {items:?}"
+ );
+ let first_call = items
+ .iter()
+ .position(|i| matches!(i, DisplayItem::ToolCall { .. }))
+ .unwrap();
+ let final_answer = items
+ .iter()
+ .position(|i| {
+ matches!(i, DisplayItem::AssistantMessage { content, .. } if content == "Here is what I found.")
+ })
+ .unwrap();
+ assert!(
+ first_call < final_answer,
+ "calls precede the answer they fed"
+ );
+}
+
+/// Transcripts already written while the codec filed a turn's calls on its
+/// final row (calls recorded *after* their own results) still project each call
+/// once, settled, with its real name.
+#[test]
+fn calls_recorded_after_their_results_project_as_settled() {
+ let dir = TempDir::new().unwrap();
+ let thread = "thr_late_calls";
+ write_raw(
+ dir.path(),
+ "late_orchestrator",
+ thread,
+ &[
+ r#"{"role":"user","content":"search and read","request_id":"R"}"#,
+ r#"{"role":"assistant","content":"","request_id":"R"}"#,
+ r#"{"role":"user","content":"[Tool results]\n\nhits\n \n\nunknown tool\n \n","request_id":"R"}"#,
+ r#"{"role":"assistant","content":"Here is what I found.","provider":"e2e","model":"m","usage":{"input":0,"output":0,"cached_input":0,"context_window":0,"cost_usd":0.0},"ts":"2026-09-24T00:49:35Z","iteration":2,"tool_calls":[{"id":"call_web_search_1","name":"web_search_tool","arguments":"{\"query\":\"q\"}"},{"id":"call_file_read_1","name":"file_read","arguments":"{\"path\":\"p\"}"}],"request_id":"R"}"#,
+ ],
+ );
+
+ let items = project_thread(dir.path(), thread)
+ .expect("transcript")
+ .items;
+ let calls: Vec<_> = items
+ .iter()
+ .filter_map(|item| match item {
+ DisplayItem::ToolCall {
+ call_id,
+ name,
+ args,
+ status,
+ ..
+ } => Some((call_id.clone(), name.clone(), args.is_some(), *status)),
+ _ => None,
+ })
+ .collect();
+ assert_eq!(
+ calls,
+ vec![
+ (
+ "call_web_search_1".to_string(),
+ "web_search_tool".to_string(),
+ true,
+ ToolCallStatus::Success
+ ),
+ (
+ "call_file_read_1".to_string(),
+ "file_read".to_string(),
+ true,
+ ToolCallStatus::Success
+ ),
+ ],
+ "{items:?}"
+ );
+}
diff --git a/crates/openhuman-core/src/threads/turn_state/mirror_finish_and_subagent_args_tests.rs b/crates/openhuman-core/src/threads/turn_state/mirror_finish_and_subagent_args_tests.rs
index 9aadcbb18ad..d74c3d68904 100644
--- a/crates/openhuman-core/src/threads/turn_state/mirror_finish_and_subagent_args_tests.rs
+++ b/crates/openhuman-core/src/threads/turn_state/mirror_finish_and_subagent_args_tests.rs
@@ -61,6 +61,9 @@ fn subagent_transcript_persists_interleaved_prose_and_tools() {
elapsed_ms: 12,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
});
let activity = m.snapshot().tool_timeline[0]
@@ -421,6 +424,9 @@ fn tinyagents_path_backfills_arguments_from_the_completion_event() {
elapsed_ms: 12,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
});
let activity = m.snapshot().tool_timeline[0]
@@ -473,6 +479,9 @@ fn completion_arguments_do_not_overwrite_arguments_captured_at_start() {
elapsed_ms: 12,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
});
let activity = m.snapshot().tool_timeline[0]
diff --git a/crates/openhuman-core/src/threads/turn_state/mirror_observe_tests.rs b/crates/openhuman-core/src/threads/turn_state/mirror_observe_tests.rs
index 3e917c5b50e..83780b9482a 100644
--- a/crates/openhuman-core/src/threads/turn_state/mirror_observe_tests.rs
+++ b/crates/openhuman-core/src/threads/turn_state/mirror_observe_tests.rs
@@ -121,6 +121,9 @@ fn tool_call_start_and_complete_track_timeline() {
elapsed_ms: 50,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
});
let s = m.snapshot();
assert_eq!(s.tool_timeline[0].status, ToolTimelineStatus::Success);
@@ -150,6 +153,9 @@ fn tool_call_completed_persists_capped_output() {
elapsed_ms: 50,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
});
let s = m.snapshot();
assert_eq!(s.tool_timeline[0].output.as_deref(), Some("hello world"));
@@ -175,6 +181,9 @@ fn tool_call_completed_persists_capped_output() {
elapsed_ms: 50,
iteration: 2,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
});
let s = m.snapshot();
let persisted = s.tool_timeline[1].output.as_deref().unwrap();
@@ -406,6 +415,9 @@ fn tool_call_started_reuses_args_delta_placeholder_for_same_call_id() {
elapsed_ms: 5,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
});
assert_eq!(m.snapshot().tool_timeline.len(), 1);
assert_eq!(
diff --git a/crates/openhuman-core/src/tools/ops_tests.rs b/crates/openhuman-core/src/tools/ops_tests.rs
index 7feb5510eb8..5d874ba3bef 100644
--- a/crates/openhuman-core/src/tools/ops_tests.rs
+++ b/crates/openhuman-core/src/tools/ops_tests.rs
@@ -436,6 +436,8 @@ const ALWAYS_PRESENT_MEMORY_TOOLS: &[&str] = &["update_memory_md", "memory_store
#[path = "ops_tests_capability_gating_tests.rs"]
mod capability_gating_tests;
+#[path = "ops_tests_catalog_fixture_tests.rs"]
+mod catalog_fixture_tests;
#[path = "ops_tests_default_registry_tests.rs"]
mod default_registry_tests;
#[path = "ops_tests_domain_family_tests.rs"]
diff --git a/crates/openhuman-core/src/tools/ops_tests_catalog_fixture_tests.rs b/crates/openhuman-core/src/tools/ops_tests_catalog_fixture_tests.rs
new file mode 100644
index 00000000000..e93e0099725
--- /dev/null
+++ b/crates/openhuman-core/src/tools/ops_tests_catalog_fixture_tests.rs
@@ -0,0 +1,148 @@
+//! Drift guard between the core's registered tool catalog and the frontend's
+//! own copy of that name list (issue: tool-call presentation).
+//!
+//! `app/src/features/conversations/tools/` renders a fallback label/icon for
+//! any tool name it recognizes even before the server-computed
+//! `display_label`/`display_detail` arrive (e.g. on a cold reconnect that
+//! replays a persisted timeline). That fallback table is only ever as
+//! accurate as the day someone last updated it by hand, so this test builds
+//! the REAL registered catalog on every core test run and fails loudly the
+//! moment it disagrees with the frontend's copy, naming exactly what was
+//! added or removed and how to regenerate.
+use super::*;
+use std::path::PathBuf;
+
+/// Path to the frontend's copy of the tool-name list, relative to this
+/// crate's manifest directory (`crates/openhuman-core`).
+fn fixture_path() -> PathBuf {
+ PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .join("../../app/src/features/conversations/tools/__fixtures__/coreToolNames.json")
+}
+
+/// The full model-facing tool catalog this build can register, sorted and
+/// deduplicated.
+///
+/// Built from [`all_tools`] (which thinly wraps [`all_tools_with_runtime`]
+/// with the native runtime adapter) under a config that widens every toggle
+/// this test controls — the browser tool enabled, in addition to whatever
+/// `Config::default()` already turns on — so the registered set is as close
+/// to maximal as a config alone can make it. What this canNOT widen:
+///
+/// * **Composio per-connection action tools** (`ComposioActionTool`, dynamic
+/// slugs like `GMAIL_SEND_EMAIL`) are never part of this static list.
+/// `all_composio_agent_tools` only ever registers its five fixed dispatcher
+/// tools (`composio_list_toolkits`, `composio_list_connections`,
+/// `composio_authorize`, `composio_connect`, `composio_list_tools`,
+/// `composio_execute`) and gates even those on a signed-in session, which
+/// this test's config does not have — so this build contributes none of
+/// them, static or dynamic, and the fixture should never carry a
+/// `COMPOSIO_*`/upper-snake action slug.
+/// * **BYOK search engines** (Exa, Tavily, Querit, Brave, ...) and other
+/// API-key-gated tools that require a live key in config are absent here;
+/// only the managed `web_search_tool` (or whichever tool the enabled
+/// feature set + config resolves to) is registered.
+/// * **Cargo feature gates**: this test runs under this crate's default
+/// features (`cargo test -p openhuman`), matching the contributor build
+/// `AGENTS.md` documents as authoritative for the test lane. A tool
+/// compiled out under a non-default feature set (see
+/// `scripts/ci/product-features.txt` for the shipped product's gates)
+/// will not appear here even though it exists in the source tree; this is
+/// intentional; add a comment at the call site (not in the fixture) when
+/// a name conditionally disappears under a feature combination CI covers.
+///
+/// On top of the domain registry this adds the two harness-intrinsic bridge
+/// tool names, `tool_search` and `tool_call`
+/// (`tinyagents_harness::tool::discover::{TOOL_SEARCH_NAME, TOOL_CALL_NAME}`):
+/// neither is ever a registered [`tinytools::Tool`] — the agent loop answers
+/// both itself once a turn has deferred tools (see that module's doc comment)
+/// — but both are model-visible tool names the frontend's tool-call
+/// presentation must recognize exactly like any other.
+fn full_tool_catalog_names() -> Vec {
+ let tmp = TempDir::new().unwrap();
+ let security = Arc::new(SecurityPolicy::default());
+ let mut cfg = test_config(&tmp);
+ cfg.browser.enabled = true;
+ let browser = cfg.browser.clone();
+ let http = cfg.http_request.clone();
+
+ let tools = all_tools(
+ Arc::new(cfg.clone()),
+ &security,
+ AuditLogger::disabled(),
+ &browser,
+ &http,
+ tmp.path(),
+ &HashMap::new(),
+ &cfg,
+ );
+
+ let mut names: Vec = tools.iter().map(|t| t.name().to_string()).collect();
+ names.push(tinyagents_harness::tool::discover::TOOL_SEARCH_NAME.to_string());
+ names.push(tinyagents_harness::tool::discover::TOOL_CALL_NAME.to_string());
+ names.sort();
+ names.dedup();
+ // Defensive: a Composio per-connection action tool would be an
+ // upper-snake slug (e.g. `GMAIL_SEND_EMAIL`) and must never reach this
+ // static fixture — see the doc comment above for why none should be
+ // registered here in the first place.
+ for name in &names {
+ assert!(
+ !(name.chars().any(|c| c.is_ascii_uppercase()) && name.contains('_')),
+ "catalog contains what looks like a dynamic Composio action slug \
+ ({name}); those must be excluded from the static fixture"
+ );
+ }
+ names
+}
+
+const REGENERATE_COMMAND: &str = "UPDATE_TOOL_CATALOG=1 cargo test -p openhuman --lib \
+ tools::ops::tests::catalog_fixture_tests::tool_catalog_matches_frontend_fixture";
+
+/// Regenerates the fixture when `UPDATE_TOOL_CATALOG=1`, otherwise fails with
+/// exactly what was added/removed relative to it.
+#[test]
+fn tool_catalog_matches_frontend_fixture() {
+ let names = full_tool_catalog_names();
+ let path = fixture_path();
+
+ if std::env::var("UPDATE_TOOL_CATALOG").as_deref() == Ok("1") {
+ let json = serde_json::to_string_pretty(&names).expect("serialize tool catalog");
+ if let Some(parent) = path.parent() {
+ std::fs::create_dir_all(parent).expect("create fixture directory");
+ }
+ std::fs::write(&path, format!("{json}\n")).expect("write tool catalog fixture");
+ eprintln!(
+ "[tool-catalog] rewrote {} with {} names",
+ path.display(),
+ names.len()
+ );
+ return;
+ }
+
+ let existing = std::fs::read_to_string(&path).unwrap_or_else(|e| {
+ panic!(
+ "missing tool-catalog fixture at {}: {e}\nGenerate it with:\n {REGENERATE_COMMAND}",
+ path.display()
+ )
+ });
+ let mut expected: Vec = serde_json::from_str(&existing).unwrap_or_else(|e| {
+ panic!(
+ "fixture at {} is not a JSON array of strings: {e}",
+ path.display()
+ )
+ });
+ expected.sort();
+ expected.dedup();
+
+ if names != expected {
+ let added: Vec<&String> = names.iter().filter(|n| !expected.contains(n)).collect();
+ let removed: Vec<&String> = expected.iter().filter(|n| !names.contains(n)).collect();
+ panic!(
+ "core tool catalog drifted from the frontend fixture at {}.\n\
+ added: {added:?}\n\
+ removed: {removed:?}\n\n\
+ Regenerate with:\n {REGENERATE_COMMAND}",
+ path.display()
+ );
+ }
+}
diff --git a/crates/openhuman-core/src/web_chat/presentation.rs b/crates/openhuman-core/src/web_chat/presentation.rs
index 41aef9be92f..fe746e10a5a 100644
--- a/crates/openhuman-core/src/web_chat/presentation.rs
+++ b/crates/openhuman-core/src/web_chat/presentation.rs
@@ -183,6 +183,8 @@ pub(crate) async fn deliver_response(
subagent: None,
tool_display_label: None,
tool_display_detail: None,
+ elapsed_ms: None,
+ structured: None,
citations: if i == 0 && !citations.is_empty() {
Some(serde_json::json!(citations))
} else {
@@ -224,6 +226,8 @@ pub(crate) async fn deliver_response(
subagent: None,
tool_display_label: None,
tool_display_detail: None,
+ elapsed_ms: None,
+ structured: None,
citations: if citations.is_empty() {
None
} else {
@@ -302,6 +306,8 @@ fn publish_chat_done(
subagent: None,
tool_display_label: None,
tool_display_detail: None,
+ elapsed_ms: None,
+ structured: None,
citations: if citations.is_empty() {
None
} else {
diff --git a/crates/openhuman-core/src/web_chat/progress_bridge.rs b/crates/openhuman-core/src/web_chat/progress_bridge.rs
index f2fcf9ddb34..49b6e644f7b 100644
--- a/crates/openhuman-core/src/web_chat/progress_bridge.rs
+++ b/crates/openhuman-core/src/web_chat/progress_bridge.rs
@@ -650,10 +650,13 @@ pub(crate) fn spawn_progress_bridge(
success,
output_chars,
output,
+ arguments,
elapsed_ms,
iteration,
failure,
- ..
+ display_label,
+ display_detail,
+ structured,
} => {
// Serialize the classified failure (if any) for the UI + ledger.
let failure_json = failure.as_ref().and_then(|f| serde_json::to_value(f).ok());
@@ -673,6 +676,17 @@ pub(crate) fn spawn_progress_bridge(
}),
},
);
+ log::debug!(
+ "[web_channel][bridge] tool_result round={} tool={} call_id={} \
+ success={} elapsed_ms={} has_structured={} request_id={}",
+ iteration,
+ tool_name,
+ call_id,
+ success,
+ elapsed_ms,
+ structured.is_some(),
+ request_id
+ );
publish_seq_stamped(
&mut emit_seq,
WebChannelEvent {
@@ -687,10 +701,23 @@ pub(crate) fn spawn_progress_bridge(
// `subagent_tool_result` path. Frontends that only
// need size/timing read the ledger telemetry instead.
output: Some(cap_wire_output(output)),
+ // The call arguments the harness captured at
+ // completion (`ToolCallStarted.arguments` is
+ // always `Null` on this path). Omitted when the
+ // harness ran with payload capture off.
+ args: arguments.filter(|v| !v.is_null()),
success: Some(success),
round: Some(iteration),
tool_call_id: Some(call_id),
failure: failure_json,
+ elapsed_ms: Some(elapsed_ms),
+ structured,
+ // Recomputed from the real arguments (unlike the
+ // started event's args-free computation), so a
+ // completed row can pick up a detail that only
+ // became knowable once the arguments existed.
+ tool_display_label: display_label,
+ tool_display_detail: display_detail,
..Default::default()
},
);
@@ -1130,10 +1157,13 @@ pub(crate) fn spawn_progress_bridge(
success,
output_chars,
output,
+ arguments,
elapsed_ms,
iteration,
failure,
- ..
+ display_label,
+ display_detail,
+ structured,
} => {
// Serialize the classified failure (if any) so a failed
// sub-agent tool row carries its "why + next" copy on the
@@ -1173,6 +1203,11 @@ pub(crate) fn spawn_progress_bridge(
// bounded size for the wire (#4007); `output_chars` +
// `elapsed_ms` still ride along in `subagent` below.
output: Some(cap_wire_output(output)),
+ args: arguments.filter(|v| !v.is_null()),
+ elapsed_ms: Some(elapsed_ms),
+ structured,
+ tool_display_label: display_label,
+ tool_display_detail: display_detail,
failure: failure_json,
subagent: Some(SubagentProgressDetail {
child_iteration: Some(iteration),
diff --git a/crates/openhuman-core/src/web_chat/progress_bridge_tests.rs b/crates/openhuman-core/src/web_chat/progress_bridge_tests.rs
index 252e6dc8cfe..0c216a04b08 100644
--- a/crates/openhuman-core/src/web_chat/progress_bridge_tests.rs
+++ b/crates/openhuman-core/src/web_chat/progress_bridge_tests.rs
@@ -126,6 +126,9 @@ async fn tool_call_completed_forwards_real_output_on_tool_result() {
elapsed_ms: 42,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
})
.await
.expect("send progress");
@@ -367,6 +370,9 @@ async fn stamps_monotonic_seq_on_emitted_events() {
elapsed_ms: 5,
iteration: 1,
failure: None,
+ display_label: None,
+ display_detail: None,
+ structured: None,
})
.await
.unwrap();
diff --git a/gitbooks/developing/architecture/frontend.md b/gitbooks/developing/architecture/frontend.md
index 4794fbd85ee..b21c11cf04c 100644
--- a/gitbooks/developing/architecture/frontend.md
+++ b/gitbooks/developing/architecture/frontend.md
@@ -387,6 +387,43 @@ iteration, the delegation prompt excerpt, or final status. The thread timeline
remains the authoritative detailed view; sub-mascots are only the glanceable
orchestration layer around the main mascot.
+### Tool-call presentation
+
+Every surface that names a tool call (chat cards, the processing panel, the
+status line, the mascot) resolves it through one registry,
+`app/src/features/conversations/tools/toolPresentation.ts`
+(`describeToolCall`). It returns the icon, a translated phrase in two tenses
+("Reading file" while running, "Read file" once settled), the target chip, and
+which rich body the call expands into. The data lives in `toolSpecs.ts` (exact
+names, collapsed tools that switch on an argument, prefix families, named
+agents) and `toolPhrases.ts` (phrases, served as
+`conversations.tools..active|done`). Composio action slugs
+(`GMAIL_SEND_EMAIL`) resolve through the toolkit catalog in
+`components/composio/toolkitMeta.tsx` to "Used Gmail · Send email" with the
+app's logo. The server's `tool_display_label` is used only for tools the
+registry cannot describe.
+
+Rendering uses assistant-ui's elements, vendored under
+`app/src/components/assistant-ui/elements/` (tool-call, tool-timeline,
+web-search, terminal-block, code-diff, web-preview) with the `tw-shimmer`
+utility. `ChatToolGroup` wraps a run of calls in the tool timeline;
+`AssistantUiToolCallCard` renders each call. The adapters in
+`tools/ToolBodies.tsx` only map tool data onto those elements.
+
+The core's `tool_result` socket event carries `args`, `elapsed_ms`, the
+recomputed `tool_display_label` / `tool_display_detail`, and `structured`
+(the tool's `ToolResult.metadata`; web searches send
+`{ kind: "web_search", query, provider, results: [...] }`).
+`parseWebSearchResult.ts` prefers that payload and falls back to parsing the
+text rendering for older turns.
+
+`tools/__fixtures__/coreToolNames.json` lists every tool the core registers.
+The Rust test `tools/ops_tests_catalog_fixture_tests.rs` keeps it in sync
+(`UPDATE_TOOL_CATALOG=1` regenerates it) and
+`toolPresentation.catalog.test.ts` fails if any listed tool falls through to
+the generic fallback, so a new core tool cannot reach the chat unlabelled.
+`/dev/tools` (dev builds only) renders every state and the whole catalog.
+
---
## Pages & Routing
diff --git a/vendor/tinyagents b/vendor/tinyagents
index fcf7e884c40..1763e8b26b4 160000
--- a/vendor/tinyagents
+++ b/vendor/tinyagents
@@ -1 +1 @@
-Subproject commit fcf7e884c407479721e03f08aa78a5166e44dd52
+Subproject commit 1763e8b26b4981c3bd13bb62a6c805ad8fbbcce3