Welcome to the final chapter of our tutorial series!
In the previous chapter, Remote Session Visualization, we learned how to show high-level animations for complex remote sessions. We made the system feel "alive" with rainbows and smooth counters.
But when an AI Agent is running locally on your machine, it isn't just a vague "processing" state. It is performing specific actions: reading files, running commands, or searching documentation.
AI Agents communicate with the system using Structured Data (usually JSON).
When an agent wants to read a file, it generates a "Tool Call" that looks like this:
{
"toolName": "readFile",
"input": {
"path": "/src/components/Button.tsx"
}
}
If we showed this raw JSON in our Task Detail Dialogs, the user would be overwhelmed. It looks like debug code, not a user interface.
The Tool Activity Renderer acts as a Translator. It takes that raw, technical JSON and converts it into a friendly, human-readable sentence.
{ "toolName": "readFile", "input": "..." }Reading file (src/components/Button.tsx)You are watching an agent fix a bug.
{ "tool": "grep", ... }, { "tool": "cat", ... }.This is the raw data coming from the agent. It contains two main things:
run_terminal_cmd).{ command: "ls -la" }).
Every tool in our system has a blueprint. This blueprint doesn't just contain code; it contains Metadata about how it should be displayed. It knows its own userFacingName (e.g., "Running command").
Before we display input data, we must ensure it is valid. We use schemas (like Zod) to check that the data is structured correctly before we try to print it.
Let's visualize how a raw event becomes a UI element.
The magic happens in a function called renderToolActivity. Let's walk through it step-by-step.
The function receives the activity data. First, it looks through our list of available tools to see if it recognizes the toolName.
// renderToolActivity.tsx
export function renderToolActivity(activity, tools, theme) {
// 1. Find the tool definition by name
const tool = findToolByName(tools, activity.toolName);
// If we don't know this tool, just show the raw name
if (!tool) {
return activity.toolName;
}
// ... continue logic
}
We use the tool's strict inputSchema to parse the incoming data. This ensures we don't crash the UI if the agent sends garbage data.
try {
// 2. Safely parse the input data
const parsed = tool.inputSchema.safeParse(activity.input);
// If parsing fails, default to empty object
const parsedInput = parsed.success ? parsed.data : {};
// ... continue logic
Now we ask the tool: "How do you want to be introduced?" This calls a function defined on the tool itself, passing in the input data so the name can be dynamic.
// 3. Get the friendly name (e.g., "Reading file")
const userFacingName = tool.userFacingName(parsedInput);
// Fallback if no name is defined
if (!userFacingName) {
return activity.toolName;
}
Finally, we want to show the details (arguments). For example, if the action is "Reading file", the detail is the filename.
// 4. Format the arguments (e.g., "index.ts")
const toolArgs = tool.renderToolUseMessage(parsedInput, {
theme,
verbose: false
});
// 5. Combine them: Name + (Args)
if (toolArgs) {
return (
<Text>
{userFacingName}({toolArgs})
</Text>
);
}
If anything goes wrong during this process (e.g., the schema validation crashes), we have a safety net. We simply return the raw tool name so the user sees something.
return userFacingName;
} catch {
// If anything explodes, fallback to the ID
return activity.toolName;
}
}
Let's say we have a tool defined for editing files.
Input Data (Raw Activity):
const activity = {
toolName: "edit_file",
input: {
path: "server.js",
changes: "..."
}
};
Tool Definition Logic (Internal):
userFacingName: Returns "Editing file"renderToolUseMessage: Returns the path variable ("server.js")
Resulting UI Output:
The renderToolActivity function combines these to render:
Editing file(server.js)
This text is then displayed in the Task Detail Dialog we built in Chapter 2, usually inside the scrolling list of "Thoughts" or "Activity."
The Tool Activity Renderer is the final piece of the puzzle.
By combining these five concepts, you have built a robust, user-friendly Command Line Interface that can handle complex AI agents and background processes without confusing the user.
Congratulations! You have completed the Task Management System tutorial.
Generated by Code IQ