mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 16:02:55 +00:00
Fix F3: use known-tag predicate for explicit-turn decisions
isHarnessInjectedUserTurnText matches any multi-word kebab tag, which is safe for non-destructive UI classification but wrong for state-transition decisions: a real prompt starting with a custom <my-element> paste looked like machinery in resolvePrompt/hasExplicitUserPrompt. Combined with the post-interrupt working-suppression in agent-hooks/server.ts, that left the agent visibly done after Ctrl+C. Switch both explicit-turn callsites to the known-tag isKnownHarnessInjectedUserTurnText predicate; known harness tags and Grok <user_query> behave unchanged. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -923,6 +923,49 @@ describe('shared agent-hook-listener', () => {
|
||||
expect(event!.hasExplicitPrompt).toBe(false)
|
||||
})
|
||||
|
||||
it('treats a custom-element paste as an explicit user turn, not machinery', () => {
|
||||
normalizeHookPayload(
|
||||
state,
|
||||
'claude',
|
||||
{ paneKey: PANE_KEY, payload: { hook_event_name: 'UserPromptSubmit', prompt: 'fix login' } },
|
||||
'production'
|
||||
)
|
||||
// Why: a real prompt starting with an unknown kebab tag (<my-custom-element>)
|
||||
// is the user's turn — it must reset the cached prompt and count as explicit,
|
||||
// so interrupt recovery does not leave the pane visibly done.
|
||||
const event = normalizeHookPayload(
|
||||
state,
|
||||
'claude',
|
||||
{
|
||||
paneKey: PANE_KEY,
|
||||
payload: {
|
||||
hook_event_name: 'UserPromptSubmit',
|
||||
prompt: '<my-custom-element> render this component'
|
||||
}
|
||||
},
|
||||
'production'
|
||||
)
|
||||
expect(event).not.toBeNull()
|
||||
expect(event!.payload.prompt).toBe('<my-custom-element> render this component')
|
||||
expect(event!.hasExplicitPrompt).toBe(true)
|
||||
})
|
||||
|
||||
it('treats a Grok user_query prompt as an explicit user turn', () => {
|
||||
const event = normalizeHookPayload(
|
||||
state,
|
||||
'grok',
|
||||
{
|
||||
paneKey: PANE_KEY,
|
||||
payload: { hookEventName: 'user_prompt_submit', prompt: '<user_query>fix the bug</user_query>' }
|
||||
},
|
||||
'production'
|
||||
)
|
||||
expect(event).not.toBeNull()
|
||||
// Grok wraps the real typed prompt; the envelope is stripped but it stays explicit.
|
||||
expect(event!.payload.prompt).toBe('fix the bug')
|
||||
expect(event!.hasExplicitPrompt).toBe(true)
|
||||
})
|
||||
|
||||
it('isolates caches between listener instances', () => {
|
||||
const a = createHookListenerState()
|
||||
const b = createHookListenerState()
|
||||
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
type AgentProviderSessionMetadata
|
||||
} from './agent-session-resume'
|
||||
import { parsePaneKey } from './stable-pane-id'
|
||||
import { isHarnessInjectedUserTurnText } from './harness-injected-user-turns'
|
||||
import { isKnownHarnessInjectedUserTurnText } from './harness-injected-user-turns'
|
||||
import {
|
||||
buildGrokChatHistoryPathCandidates,
|
||||
findGrokChatHistoryBySessionId,
|
||||
@@ -451,8 +451,10 @@ function resolvePrompt(
|
||||
): string {
|
||||
// Why: harness-injected turns (task notifications, system reminders) fire
|
||||
// UserPromptSubmit but are not the user's ask — keep the cached real prompt
|
||||
// instead of surfacing raw machinery tags in status labels.
|
||||
if (isHarnessInjectedUserTurnText(promptText)) {
|
||||
// instead of surfacing raw machinery tags in status labels. Match only known
|
||||
// harness tags: a real prompt pasting a custom `<my-element>` must reset the
|
||||
// turn, not be mistaken for machinery and leave the pane on a stale prompt.
|
||||
if (isKnownHarnessInjectedUserTurnText(promptText)) {
|
||||
return state.lastPromptByPaneKey.get(paneKey) ?? ''
|
||||
}
|
||||
if (options?.resetOnNewTurn) {
|
||||
@@ -2266,7 +2268,10 @@ function hasExplicitUserPrompt(
|
||||
}
|
||||
// Why: harness-injected machinery turns are not proof of a user submit —
|
||||
// they must not count for prompt-sent telemetry or permission stickiness.
|
||||
if (isHarnessInjectedUserTurnText(extractedPrompt.text)) {
|
||||
// Match only known harness tags: a real prompt starting with a custom
|
||||
// `<my-element>` is an explicit user turn and must survive interrupt recovery
|
||||
// (a false "not explicit" leaves the agent visibly done after Ctrl+C).
|
||||
if (isKnownHarnessInjectedUserTurnText(extractedPrompt.text)) {
|
||||
return false
|
||||
}
|
||||
// Why: bare `message` fields often contain permission or status copy. They
|
||||
|
||||
Reference in New Issue
Block a user