Merge /goal desktop visibility fix

This commit is contained in:
程序员阿江(Relakkes) 2026-05-16 00:10:10 +08:00
commit c75ff0bf2d
2 changed files with 94 additions and 2 deletions

View File

@ -1,5 +1,9 @@
import { describe, expect, it } from 'bun:test'
import { translateCliMessage } from '../ws/handler.js'
import {
createCurrentTurnLocalCommandForwarder,
translateCliMessage,
} from '../ws/handler.js'
import { parseSlashCommand } from '../../utils/slashCommandParsing.js'
describe('WebSocket memory events', () => {
it('forwards CLI memory_saved system messages to the desktop client', () => {
@ -208,6 +212,37 @@ describe('WebSocket goal command events', () => {
{ type: 'content_delta', text: 'Goal: active' },
])
})
it('allows the current slash command lifecycle through the pre-turn mute gate', () => {
const shouldForward = createCurrentTurnLocalCommandForwarder(
parseSlashCommand('/goal ship the smoke test'),
)
expect(shouldForward({
type: 'system',
subtype: 'init',
})).toBe(false)
expect(shouldForward({
type: 'system',
subtype: 'local_command',
content: '<command-name>/cost</command-name>\n<command-args></command-args>',
})).toBe(false)
expect(shouldForward({
type: 'system',
subtype: 'local_command',
content: '<command-name>/goal</command-name>\n<command-args>ship the smoke test</command-args>',
})).toBe(true)
expect(shouldForward({
type: 'system',
subtype: 'local_command',
content: '<local-command-stdout>Goal created.\nGoal: active\nObjective: ship the smoke test</local-command-stdout>',
})).toBe(true)
expect(shouldForward({
type: 'system',
subtype: 'local_command_output',
content: 'late unrelated output',
})).toBe(false)
})
})
describe('WebSocket stream event translation', () => {

View File

@ -333,9 +333,16 @@ async function handleUserMessage(
// Keep output muted until the current user turn is enqueued to avoid forwarding
// any pre-turn SDK chatter as fresh chat history.
let userMessageSent = false
const shouldForwardCurrentTurnLocalCommand =
createCurrentTurnLocalCommandForwarder(desktopSlashCommand)
rebindSessionOutput(sessionId, ws, {
shouldForward: (cliMsg) => userMessageSent || (cliMsg.type === 'result' && cliMsg.is_error),
shouldForward: (cliMsg) => {
if (userMessageSent || (cliMsg.type === 'result' && cliMsg.is_error)) {
return true
}
return shouldForwardCurrentTurnLocalCommand(cliMsg)
},
})
const sent = conversationService.sendMessage(
@ -1324,6 +1331,56 @@ function getDesktopSlashCommand(content: string): ReturnType<typeof parseSlashCo
return parsed
}
export function createCurrentTurnLocalCommandForwarder(
command: ReturnType<typeof parseSlashCommand>,
): (cliMsg: any) => boolean {
let awaitingCurrentTurnLocalCommandOutput = false
return (cliMsg: any) => {
if (command && isMatchingCurrentTurnLocalCommand(cliMsg, command)) {
awaitingCurrentTurnLocalCommandOutput = true
return true
}
if (
awaitingCurrentTurnLocalCommandOutput &&
isLocalCommandOutputMessage(cliMsg)
) {
awaitingCurrentTurnLocalCommandOutput = false
return true
}
return false
}
}
function isMatchingCurrentTurnLocalCommand(
cliMsg: any,
command: NonNullable<ReturnType<typeof parseSlashCommand>>,
): boolean {
if (cliMsg?.type !== 'system' || cliMsg?.subtype !== 'local_command') {
return false
}
const localCommand = extractLocalCommand(cliMsg.content ?? cliMsg.message)
if (!localCommand) return false
return (
localCommand.name === command.commandName &&
localCommand.args.trim() === command.args.trim()
)
}
function isLocalCommandOutputMessage(cliMsg: any): boolean {
if (
cliMsg?.type !== 'system' ||
(cliMsg?.subtype !== 'local_command' &&
cliMsg?.subtype !== 'local_command_output')
) {
return false
}
return extractLocalCommandOutput(
cliMsg.content ?? cliMsg.message,
{ allowUntagged: cliMsg.subtype === 'local_command_output' },
) !== null
}
function extractLocalCommandOutput(
content: unknown,
options: { allowUntagged?: boolean } = {},