Welcome back! In Command Registry & Configuration, we created the "Menu Entry" for our rewind command. The system now knows the command exists.
But knowing a command exists is different from running it.
Imagine you hire a maintenance worker to fix a specific pipe in your office building. Usually, they stay in the basement (the "sandbox"). They do their job in isolation so they don't accidentally break anything in the main lobby.
However, the rewind command is special. It needs to show a popup window to the user so they can select which message to rewind to. To do this, it needs to reach outside its isolated basement and interact with the main building.
The Use Case:
We need to give our isolated rewind code a way to press a button on the main application's UI to show a "Message Selector" screen.
To solve this, we use the Tool Context Interface (specifically called ToolUseContext).
Think of the context as a Universal Key Ring or a Control Panel that the main system hands to the command right before it starts working.
In our specific case, the context holds a key called openMessageSelector.
Let's look at the actual code in rewind.ts. When the system runs our command, it passes two things: arguments (what the user typed) and the context.
First, we need to accept the context in our function definition.
import type { ToolUseContext } from '../../Tool.js'
// The 'context' argument is our toolbox
export async function call(
_args: string,
context: ToolUseContext,
): Promise<LocalCommandResult> {
Explanation: We define the call function. Notice the second argument: context. This is the variable that holds our "Universal Keys."
Now that we are holding the context, we check if the specific tool we need exists, and then we use it.
// Check if the 'universal key' exists
if (context.openMessageSelector) {
// Use the key to open the main UI window
context.openMessageSelector()
}
Explanation: The rewind command doesn't know how to draw the window. It just knows that if it calls context.openMessageSelector(), the main application will do the heavy lifting and show the UI to the user.
After we open the selector, our command is effectively done with its immediate task.
// Tell the system we are done and to 'skip' adding text to chat
return { type: 'skip' }
}
Explanation: We return a result telling the system that the command finished successfully. We will cover this return format in Local Command Result.
How does the context actually get to the command? It is "injected" by the main system.
rewind.context object containing all the tools and helper functions the app supports.rewind command.Here is a diagram illustrating this hand-off:
Behind the scenes, the code that calls our command looks something like this (simplified):
// This exists in the main system core (not in rewind.ts)
const context: ToolUseContext = {
// We define the function here
openMessageSelector: () => {
uiLayer.showCheckpoints(); // Real UI logic
}
};
// We pass this object to the command
await rewindCommand.call(args, context);
Explanation: The openMessageSelector function is defined in the main system where it has full access to the UI. It is then wrapped inside the context object and shipped off to the rewind command. This creates a bridge between the isolated command and the core system.
In this chapter, we learned that commands don't run in a vacuum. They are given a Tool Context Interface which acts as a bridge to the outside world.
openMessageSelector) to trigger actions in the main application.
What's Next?
We have defined the command (Chapter 1) and given it the tools it needs (Chapter 2). But who actually calls the call function? How is that coordinated?
Next, we will look at the Command Execution Handler, the engine that drives this entire process.
Next Chapter: Command Execution Handler
Generated by Code IQ