feat(remote-control): 优化 Web 展示、状态同步与桥接控制流程 (#288)

Co-authored-by: chengzifeng <chengzifeng@meituan.com>
This commit is contained in:
Cheng Zi Feng
2026-04-17 16:21:27 +08:00
committed by GitHub
parent b5c299f5d2
commit 72a2093cd6
64 changed files with 4138 additions and 312 deletions

View File

@@ -0,0 +1,17 @@
import { describe, expect, test } from 'bun:test'
import { isBridgeSafeCommand } from '../commands.js'
import clear from '../commands/clear/index.js'
import plan from '../commands/plan/index.js'
import proactive from '../commands/proactive.js'
describe('isBridgeSafeCommand', () => {
test('allows bridge-safe local-jsx commands', () => {
expect(isBridgeSafeCommand(plan)).toBe(true)
expect(isBridgeSafeCommand(proactive)).toBe(true)
})
test('continues allowing explicit local bridge-safe commands', () => {
expect(isBridgeSafeCommand(clear)).toBe(true)
})
})

View File

@@ -0,0 +1,121 @@
import { beforeEach, describe, expect, mock, test } from 'bun:test'
import { createAbortController } from '../utils/abortController'
import { QueryGuard } from '../utils/QueryGuard'
import { handlePromptSubmit } from '../utils/handlePromptSubmit'
import { getCommandQueue, resetCommandQueue } from '../utils/messageQueueManager'
function createBaseParams() {
const queryGuard = new QueryGuard()
queryGuard.reserve()
return {
queryGuard,
helpers: {
setCursorOffset: mock((_offset: number) => {}),
clearBuffer: mock(() => {}),
resetHistory: mock(() => {}),
},
onInputChange: mock((_value: string) => {}),
setPastedContents: mock((_value: unknown) => {}),
setToolJSX: mock((_value: unknown) => {}),
getToolUseContext: mock(() => {
throw new Error('getToolUseContext should not be called in queued path')
}),
messages: [],
mainLoopModel: 'claude-sonnet-4-6',
ideSelection: undefined,
querySource: 'repl_main_thread' as any,
commands: [],
setUserInputOnProcessing: mock((_prompt?: string) => {}),
setAbortController: mock((_abortController: AbortController | null) => {}),
onQuery: mock(
async () => undefined,
) as unknown as (
...args: unknown[]
) => Promise<void>,
setAppState: mock((_updater: unknown) => {}),
}
}
describe('handlePromptSubmit', () => {
beforeEach(() => {
resetCommandQueue()
})
test('aborts the current turn when only cancel-interrupt tools are running', async () => {
const params = createBaseParams()
const abortController = createAbortController()
await handlePromptSubmit({
...params,
input: 'hello',
mode: 'prompt',
pastedContents: {},
abortController,
streamMode: 'normal' as any,
hasInterruptibleToolInProgress: true,
isExternalLoading: false,
})
expect(abortController.signal.aborted).toBe(true)
expect(abortController.signal.reason).toBe('interrupt')
expect(getCommandQueue()).toHaveLength(1)
expect(getCommandQueue()[0]).toMatchObject({
value: 'hello',
preExpansionValue: 'hello',
mode: 'prompt',
})
expect(params.onInputChange).toHaveBeenCalledWith('')
})
test('queues the input without aborting when a blocking tool is running', async () => {
const params = createBaseParams()
const abortController = createAbortController()
await handlePromptSubmit({
...params,
input: 'hello',
mode: 'prompt',
pastedContents: {},
abortController,
streamMode: 'normal' as any,
hasInterruptibleToolInProgress: false,
isExternalLoading: false,
})
expect(abortController.signal.aborted).toBe(false)
expect(getCommandQueue()).toHaveLength(1)
expect(getCommandQueue()[0]).toMatchObject({
value: 'hello',
preExpansionValue: 'hello',
mode: 'prompt',
})
})
test('preserves bridgeOrigin when a remote slash command is queued during external loading', async () => {
const params = createBaseParams()
const abortController = createAbortController()
await handlePromptSubmit({
...params,
input: '/proactive',
mode: 'prompt',
pastedContents: {},
abortController,
streamMode: 'normal' as any,
hasInterruptibleToolInProgress: true,
isExternalLoading: true,
skipSlashCommands: true,
bridgeOrigin: true,
})
expect(getCommandQueue()).toHaveLength(1)
expect(getCommandQueue()[0]).toMatchObject({
value: '/proactive',
preExpansionValue: '/proactive',
mode: 'prompt',
skipSlashCommands: true,
bridgeOrigin: true,
})
})
})