Welcome to the final chapter of our TaskStopTool tutorial!
In the previous chapter, Task Execution Logic, we built the engine of our tool. We wrote the code that actually communicates with the system to kill a process and returns a raw data object (JSON) confirming the success.
However, raw data looks like this:
{
"message": "Successfully stopped task: 123",
"task_id": "123",
"command": "/usr/bin/node /very/long/path/to/script.js --flag1 --flag2..."
}
While this is great for the AI to read, itβs messy for a human user.
This chapter is about UI Rendering. We are going to build the "Dashboard" that takes that raw data and turns it into a clean, readable status message on your screen.
Use Case: Imagine you stop a background process that was started with a command that is 500 characters long (lots of file paths and configuration flags).
If our tool simply prints the whole command again to say it stopped, your screen will be flooded with text. You don't care about the flags anymore; you just want to know: "Is it dead?"
We need a display layer that:
We use a library called React (standard for web UI) combined with Ink (which lets React render text inside a command-line terminal).
Just like the call() function was the entry point for logic, renderToolResultMessage is the entry point for visuals. The system hands this function the output from the tool and asks, "How should I draw this?"
This is the art of cutting text short. If a command is 10 lines long, we might only show the first 2 lines and add "..." at the end. This keeps the user's terminal tidy.
Sometimes, you do want to see the details. Our UI needs a "Verbose Mode" switch.
Let's look at UI.tsx. We will build this in two parts: the logic to shorten text, and the component to display it.
First, we set some rules. We don't want the text to be wider than a standard screen or taller than two lines.
// --- File: UI.tsx ---
const MAX_COMMAND_DISPLAY_LINES = 2
const MAX_COMMAND_DISPLAY_CHARS = 160
// We import a helper to measure text width accurately
import { stringWidth } from '../../ink/stringWidth.js'
Explanation: These constants act as our "budget" for screen real estate. We won't spend more than 160 characters or 2 lines on a status update.
We need a function that takes a long string and chops it down to fit our budget.
function truncateCommand(command: string): string {
const lines = command.split('\n')
let truncated = command
// Rule 1: Limit to 2 lines
if (lines.length > MAX_COMMAND_DISPLAY_LINES) {
truncated = lines.slice(0, MAX_COMMAND_DISPLAY_LINES).join('\n')
}
// Rule 2: Limit to 160 characters
if (stringWidth(truncated) > MAX_COMMAND_DISPLAY_CHARS) {
// A helper that cuts the string without breaking words awkwardly
truncated = truncateToWidthNoEllipsis(truncated, MAX_COMMAND_DISPLAY_CHARS)
}
return truncated.trim()
}
Explanation:
split('\n'): Breaks the text into an array of lines.slice(0, 2): Keeps only the first 2 lines.truncateToWidth...: Ensures the horizontal length doesn't wrap uglily.
Now we write the main function that returns the visual elements. It receives the output from our tool (Chapter 4) and a verbose setting.
import { Text } from '../../ink.js'
import { MessageResponse } from '../../components/MessageResponse.js'
export function renderToolResultMessage(output, _, { verbose }) {
// 1. Get the command string from the tool output
const rawCommand = output.command ?? ''
// 2. Decide: Full text or Short text?
const command = verbose ? rawCommand : truncateCommand(rawCommand)
// 3. Add a visual indicator
const suffix = command !== rawCommand ? 'β¦ Β· stopped' : ' Β· stopped'
// 4. Render the UI
return (
<MessageResponse>
<Text>{command}{suffix}</Text>
</MessageResponse>
)
}
Explanation:
verbose ? raw : truncated: This is a toggle. If the user asked for verbose output, we skip the truncation logic.suffix: We append " Β· stopped" so the user knows the state. If we cut text out, we add an ellipsis (β¦) so they know there is missing info.<Text>: This is an Ink component that prints styled text to the terminal.How does the data get from the engine to the screen?
We have now completed the entire circle of tool creation.
If you stop a simple task, the user sees:
npm run dev Β· stopped
If you stop a massive, complex task, the user sees:
java -jar server.jar --config=/etc/configs/production... Β· stopped
Because of your work in this chapter, the user gets exactly the information they needβno more, no less.
Congratulations! You have built a fully functional, safe, and user-friendly AI tool from scratch.
You learned how to:
You are now ready to add this tool to the robot's registry and let it help you manage your tasks!
Generated by Code IQ