From 7dca7c19a1a98edcadada42d9e02dd31122e000b Mon Sep 17 00:00:00 2001 From: Curtis Man Date: Thu, 30 Oct 2025 08:42:09 -0700 Subject: [PATCH] Grammar partial fix --- .../actionGrammar/src/grammarMatcher.ts | 30 ++++++++++-------- .../agents/player/src/agent/playerGrammar.agr | 31 +++++++++++++++---- ts/packages/shell/test/testHelper.ts | 30 ++++++++++++------ 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/ts/packages/actionGrammar/src/grammarMatcher.ts b/ts/packages/actionGrammar/src/grammarMatcher.ts index a9b6b56f9..4be8bcdbe 100644 --- a/ts/packages/actionGrammar/src/grammarMatcher.ts +++ b/ts/packages/actionGrammar/src/grammarMatcher.ts @@ -256,11 +256,9 @@ function nextNonSeparatorIndex(request: string, index: number) { return match === null ? index : index + match[0].length; } -function finalizeMatch( - state: MatchState, - request: string, - results: GrammarMatchResult[], -) { +// Finalize the state to make capture the last wildcard if any +// and make sure there are any trailing un-matched non-separator characters. +function finalizeState(state: MatchState, request: string) { if (state.pendingWildcard !== undefined) { const value = getWildcardStr( request, @@ -268,7 +266,7 @@ function finalizeMatch( request.length, ); if (value === undefined) { - return; + return false; } state.index = request.length; addValueWithId(state, state.pendingWildcard.valueId, value, true); @@ -282,13 +280,24 @@ function finalizeMatch( nonSepIndex, )}`, ); - return; + return false; } debugMatch( `Consume trailing separators at ${state.index} to ${request.length}}`, ); } + return true; +} + +function finalizeMatch( + state: MatchState, + request: string, + results: GrammarMatchResult[], +) { + if (!finalizeState(state, request)) { + return; + } debugMatch( `Matched at end of input. Matched ids: ${JSON.stringify(state.valueIds)}, values: ${JSON.stringify(state.values)}'`, ); @@ -640,12 +649,7 @@ function partialMatchRules( `Start state ${state.name}{${state.partIndex}}: @${state.index}`, ); if (!matchState(state, request, pending)) { - const nonSepIndex = nextNonSeparatorIndex(request, state.index); - if (nonSepIndex !== request.length) { - // There are not matched non-separator characters left - debugCompletion( - ` Rejecting completion at ${state.name} with non-separator text at ${nonSepIndex}`, - ); + if (!finalizeState(state, request)) { continue; } const nextPart = state.rule.parts[state.partIndex]; diff --git a/ts/packages/agents/player/src/agent/playerGrammar.agr b/ts/packages/agents/player/src/agent/playerGrammar.agr index 721eb2796..cf63d4790 100644 --- a/ts/packages/agents/player/src/agent/playerGrammar.agr +++ b/ts/packages/agents/player/src/agent/playerGrammar.agr @@ -6,7 +6,7 @@ | @ = pause ((the)? music)? -> { actionName: "pause" } @ = resume ((the)? music)? -> { actionName: "resume" } -@ = +@ = | @ = play (the)? $(n:) ()? -> { actionName: "playFromCurrentTrackList", @@ -26,16 +26,35 @@ trackNumber: $(n) } } -@ = one | track | song | cut + +@ = one | cut | +@ = track | song + @ = - one -> 1 + $(x:number) +| one -> 1 | two -> 2 | three -> 3 | four -> 4 -@ = + + +@ = first -> 1 | second -> 2 | third -> 3 | troisiemme -> 3 -| fourth -> 4 - \ No newline at end of file +| fourth -> 4 + +@ = ((the)? )? $(trackName:) + | $(trackName:) +@ = play $(trackName:) -> { actionName: "playTrack", parameters: { trackName: $(trackName) } } + | play $(trackName:) by $(artist:) -> { actionName: "playTrack", parameters: { trackName: $(trackName), artists: [$(artist)] } } + | play $(trackName:) from (the)? album $(albumName:) -> { actionName: "playTrack", parameters: { trackName: $(trackName), albumName: $(albumName) } } + | play $(trackName:) by $(artist:) from (the)? album $(albumName:) -> { actionName: "playTrack", parameters: { trackName: $(trackName), artists: [$(artist)], albumName: $(albumName) } } + +// TODO: wire up entity names +@ = $(x:string) +@ = $(x:string) +@ = $(x:string) + + diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index a78f82a79..66e6263ce 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -227,6 +227,10 @@ export async function sendUserRequestFast(prompt: string, page: Page) { page.keyboard.down("Enter"); } +async function getAgentMessageLocators(page: Page): Promise { + return page.locator(".chat-message-agent .chat-message-content").all(); +} + /** * Submits a user request to the system via the chat input box and then waits for the first available response * NOTE: If your expected response changes or you invoke multi-action flow you should be calling @@ -241,9 +245,7 @@ export async function sendUserRequestAndWaitForResponse( prompt: string, page: Page, ): Promise { - const locators: Locator[] = await page - .locator(".chat-message-agent .chat-message-content") - .all(); + const locators = await getAgentMessageLocators(page); // send the user request await sendUserRequest(prompt, page); @@ -264,9 +266,7 @@ export async function sendUserRequestAndWaitForCompletion( prompt: string, page: Page, ): Promise { - const locators: Locator[] = await page - .locator(".chat-message-agent .chat-message-content") - .all(); + const locators = await getAgentMessageLocators(page); // send the user request await sendUserRequest(prompt, page); @@ -415,8 +415,17 @@ export async function runTestCallback( } } +export async function getAllAgentMessages(page: Page): Promise { + const locators = await getAgentMessageLocators(page); + return ( + await Promise.all( + locators.map((locator) => locator.innerText()).reverse(), + ) + ).filter((msg) => msg.length > 0); +} + /** - * Encapsulates the supplied method within a startup and shutdown of teh + * Encapsulates the supplied method within a startup and shutdown of the * shell. Test code executes between them. */ export async function testUserRequest( @@ -434,11 +443,14 @@ export async function testUserRequest( userRequests[i], mainWindow, ); - // verify expected result expect( msg, - `Chat agent didn't respond with the expected message. Request: '${userRequests[i]}', Response: '${expectedResponses[i]}'`, + [ + `Chat agent didn't respond with the expected message. Request: '${userRequests[i]}', Response: '${expectedResponses[i]}'`, + `All response so far:`, + ...(await getAllAgentMessages(mainWindow)), + ].join("\n"), ).toBe(expectedResponses[i]); } });