feat: add track-aware tool targeting and new agent tools, lifted the requirement of selection a MIDI region to use AI Agent

Introduces a shared `toolTargeting.ts` module that resolves the active MIDI
region/track from the user's current selection, enabling tools to operate on
the correct target without requiring explicit track IDs in most cases.

Adds two new read-only tools — `list_all_tracks` and
`get_user_selected_music_range_and_track` — so the agent can inspect
available tracks and the current selection context before acting.

Also refactors `AddNotesTool` to auto-create MIDI regions when no region
exists, refactors `RemoveNotesTool`, `ReadMusicTool`, and
`ReadChordProgressionTool` to use the new targeting helpers, adds a
`buildToolHistoryContent` hook to `BaseTool` for cleaner chat history
display, and updates system prompts and tests throughout.
This commit is contained in:
Xiaohan-Tian
2026-06-04 19:29:37 -07:00
parent 5c5a07b839
commit 8ee7ddac77
27 changed files with 1812 additions and 855 deletions
@@ -0,0 +1,31 @@
import { BaseTool } from './BaseTool';
import type { ToolParameter, ToolResult } from './BaseTool';
import {
resolveSelectedMusicRangeContext,
resolveSelectedTrackContext,
} from './toolTargeting';
export class GetUserSelectedMusicRangeAndTrackTool extends BaseTool {
readonly name = 'get_user_selected_music_range_and_track';
readonly description =
'Get the current selected music range and the current selected regular track, if one is selected. Use this when selection context matters. When you are editing notes on the currently selected track, you do not need to pass track_id or track_name to note-editing tools.';
readonly parameters: Record<string, ToolParameter> = {};
async execute(_params: Record<string, unknown>): Promise<ToolResult> {
try {
const selectedMusicRange = resolveSelectedMusicRangeContext();
const selectedTrack = resolveSelectedTrackContext();
const selectedTrackSection = selectedTrack.hasSelectedTrack
? `track_id: ${selectedTrack.trackId}\ntrack_name: ${selectedTrack.trackName}`
: 'No selected track.';
return this.createSuccessResult(
`Current Selected Music Range:\n${selectedMusicRange.section}\n\nCurrent Selected Track:\n${selectedTrackSection}`,
);
} catch (error) {
return this.createErrorResult(`Failed to read current selected music range and track: ${error}`);
}
}
}