Fix plan command support in web bridge (#1018)
* fix: support plan command in web bridge * fix: preserve queued bridge messages * fix: avoid duplicate queued plan messages * fix: preserve plan command semantics --------- Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
@@ -28,6 +28,7 @@ const bridgeCommands = computed(() => [
|
||||
{ name: 'status', args: '', description: t('chat.slashCommands.status') },
|
||||
{ name: 'abort', args: '', description: t('chat.slashCommands.abort') },
|
||||
{ name: 'queue', args: t('chat.slashCommandArgs.message'), description: t('chat.slashCommands.queue') },
|
||||
{ name: 'plan', args: t('chat.slashCommandArgs.text'), description: t('chat.slashCommands.plan') },
|
||||
{ name: 'clear', args: '', description: t('chat.slashCommands.clear') },
|
||||
{ name: 'clear', args: '--history', insertText: 'clear --history', description: t('chat.slashCommands.clearHistory') },
|
||||
{ name: 'title', args: t('chat.slashCommandArgs.title'), description: t('chat.slashCommands.title') },
|
||||
|
||||
@@ -1307,7 +1307,7 @@ async function handleSessionModelCustomSubmit() {
|
||||
{{ t('chat.clarifyDismiss') }}
|
||||
</NButton>
|
||||
</div>
|
||||
<div v-else class="clarify-actions">
|
||||
<div v-else class="clarify-actions clarify-actions-open">
|
||||
<div class="clarify-input-row">
|
||||
<NInput
|
||||
v-model:value="clarifyResponse"
|
||||
@@ -2168,12 +2168,24 @@ async function handleSessionModelCustomSubmit() {
|
||||
.clarify-input-row {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
.n-input {
|
||||
flex: 1;
|
||||
:deep(.n-input) {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.n-button) {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.clarify-actions-open {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.approval-bar {
|
||||
@@ -2212,9 +2224,18 @@ async function handleSessionModelCustomSubmit() {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.clarify-actions-open {
|
||||
display: flex;
|
||||
grid-template-columns: none;
|
||||
}
|
||||
|
||||
.clarify-actions :deep(.n-button) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.clarify-actions-open :deep(.n-button) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
@@ -2233,6 +2254,15 @@ async function handleSessionModelCustomSubmit() {
|
||||
.clarify-actions {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.clarify-input-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.clarify-actions-open :deep(.n-button) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rainbow-glow {
|
||||
|
||||
@@ -218,6 +218,7 @@ export default {
|
||||
status: 'Sitzungsstatus und Warteschlange anzeigen',
|
||||
abort: 'Aktiven Bridge-Lauf stoppen',
|
||||
queue: 'Nachricht hinter dem aktiven Lauf einreihen',
|
||||
plan: 'Markdown-Implementierungsplan schreiben',
|
||||
clear: 'Aktuelle Anzeige leeren',
|
||||
clearHistory: 'Gespeicherten Nachrichtenverlauf dieser Sitzung löschen',
|
||||
title: 'Diese Sitzung umbenennen',
|
||||
|
||||
@@ -219,6 +219,7 @@ export default {
|
||||
status: 'Show session status and queue',
|
||||
abort: 'Stop the active bridge run',
|
||||
queue: 'Queue a message behind the active run',
|
||||
plan: 'Write a markdown implementation plan',
|
||||
clear: 'Clear the current display',
|
||||
clearHistory: 'Delete this session’s stored message history',
|
||||
title: 'Rename this session',
|
||||
|
||||
@@ -218,6 +218,7 @@ export default {
|
||||
status: 'Mostrar estado de sesión y cola',
|
||||
abort: 'Detener la ejecución activa de Bridge',
|
||||
queue: 'Poner un mensaje en cola tras la ejecución activa',
|
||||
plan: 'Escribir un plan de implementación en Markdown',
|
||||
clear: 'Limpiar la vista actual',
|
||||
clearHistory: 'Eliminar el historial de mensajes guardado de esta sesión',
|
||||
title: 'Renombrar esta sesión',
|
||||
|
||||
@@ -218,6 +218,7 @@ export default {
|
||||
status: 'Afficher l’état de la session et la file',
|
||||
abort: 'Arrêter l’exécution Bridge active',
|
||||
queue: 'Mettre un message en file après l’exécution active',
|
||||
plan: 'Rédiger un plan d’implémentation Markdown',
|
||||
clear: 'Effacer l’affichage actuel',
|
||||
clearHistory: 'Supprimer l’historique des messages enregistrés de cette session',
|
||||
title: 'Renommer cette session',
|
||||
|
||||
@@ -218,6 +218,7 @@ export default {
|
||||
status: 'セッション状態とキューを表示',
|
||||
abort: '実行中の Bridge を停止',
|
||||
queue: '実行中の処理の後ろにメッセージをキュー追加',
|
||||
plan: 'Markdown の実装計画を作成',
|
||||
clear: '現在の表示をクリア',
|
||||
clearHistory: 'このセッションの保存済みメッセージ履歴を削除',
|
||||
title: 'このセッション名を変更',
|
||||
|
||||
@@ -218,6 +218,7 @@ export default {
|
||||
status: '세션 상태와 대기열 표시',
|
||||
abort: '활성 Bridge 실행 중지',
|
||||
queue: '활성 실행 뒤에 메시지 대기열 추가',
|
||||
plan: 'Markdown 구현 계획 작성',
|
||||
clear: '현재 표시 내용 지우기',
|
||||
clearHistory: '이 세션의 저장된 메시지 기록 삭제',
|
||||
title: '이 세션 이름 변경',
|
||||
|
||||
@@ -218,6 +218,7 @@ export default {
|
||||
status: 'Mostrar status da sessão e fila',
|
||||
abort: 'Parar a execução ativa do Bridge',
|
||||
queue: 'Enfileirar uma mensagem após a execução ativa',
|
||||
plan: 'Escrever um plano de implementação em Markdown',
|
||||
clear: 'Limpar a visualização atual',
|
||||
clearHistory: 'Excluir o histórico de mensagens salvo desta sessão',
|
||||
title: 'Renomear esta sessão',
|
||||
|
||||
@@ -218,6 +218,7 @@ export default {
|
||||
status: '查看會話狀態和佇列',
|
||||
abort: '停止目前 Bridge 執行',
|
||||
queue: '將訊息加入目前執行後的佇列',
|
||||
plan: '產生一份 Markdown 實作計畫',
|
||||
clear: '清空目前顯示內容',
|
||||
clearHistory: '刪除目前會話已儲存的訊息歷史',
|
||||
title: '重新命名目前會話',
|
||||
|
||||
@@ -219,6 +219,7 @@ export default {
|
||||
status: '查看会话状态和队列',
|
||||
abort: '停止当前 Bridge 运行',
|
||||
queue: '把消息加入当前运行后的队列',
|
||||
plan: '生成一份 Markdown 实施计划',
|
||||
clear: '清空当前显示内容',
|
||||
clearHistory: '删除当前会话已入库的消息历史',
|
||||
title: '重命名当前会话',
|
||||
|
||||
@@ -967,12 +967,14 @@ export const useChatStore = defineStore('chat', () => {
|
||||
const timestamp = typeof peer?.timestamp === 'number' && Number.isFinite(peer.timestamp)
|
||||
? Math.round(peer.timestamp * 1000)
|
||||
: Date.now()
|
||||
const role = peer?.role === 'command' ? 'command' : 'user'
|
||||
return [{
|
||||
id: messageId,
|
||||
role: 'user' as const,
|
||||
role,
|
||||
content,
|
||||
timestamp,
|
||||
queued: true,
|
||||
systemType: role === 'command' ? 'command' as const : undefined,
|
||||
}]
|
||||
})
|
||||
}
|
||||
@@ -1028,11 +1030,12 @@ export const useChatStore = defineStore('chat', () => {
|
||||
enqueueUserMessage(sessionId, {
|
||||
...(existing || {}),
|
||||
id: messageId,
|
||||
role: 'user',
|
||||
role: peer?.role === 'command' ? 'command' : 'user',
|
||||
content,
|
||||
timestamp: existing?.timestamp || timestamp,
|
||||
attachments: existing?.attachments,
|
||||
queued: true,
|
||||
systemType: peer?.role === 'command' ? 'command' : existing?.systemType,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1093,6 +1096,22 @@ export const useChatStore = defineStore('chat', () => {
|
||||
pendingClarifies.value = new Map(pendingClarifies.value)
|
||||
}
|
||||
|
||||
function clearPendingInteractions(sessionId: string) {
|
||||
let changed = false
|
||||
if (pendingApprovals.value.has(sessionId)) {
|
||||
pendingApprovals.value.delete(sessionId)
|
||||
changed = true
|
||||
}
|
||||
if (pendingClarifies.value.has(sessionId)) {
|
||||
pendingClarifies.value.delete(sessionId)
|
||||
changed = true
|
||||
}
|
||||
if (changed) {
|
||||
pendingApprovals.value = new Map(pendingApprovals.value)
|
||||
pendingClarifies.value = new Map(pendingClarifies.value)
|
||||
}
|
||||
}
|
||||
|
||||
function respondToClarify(response: string) {
|
||||
const pending = activePendingClarify.value
|
||||
if (!pending) return
|
||||
@@ -1169,8 +1188,9 @@ export const useChatStore = defineStore('chat', () => {
|
||||
: false
|
||||
const isBridgeSlashCommand = content.trim().startsWith('/')
|
||||
const isBridgeCompressCommand = isBridgeSlashCommand && /^\/compress(?:\s|$)/i.test(content.trim())
|
||||
const isBridgePlanCommand = isBridgeSlashCommand && /^\/plan(?:\s|$)/i.test(content.trim())
|
||||
const wasLiveBeforeSend = isSessionLive(sid)
|
||||
const shouldQueue = wasLiveBeforeSend && !isBridgeSlashCommand
|
||||
const shouldQueue = wasLiveBeforeSend && (!isBridgeSlashCommand || isBridgePlanCommand)
|
||||
|
||||
const userMsg: Message = {
|
||||
id: uid(),
|
||||
@@ -1449,6 +1469,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
|
||||
case 'abort.completed': {
|
||||
setAbortState({ aborting: false, synced: (evt as any).synced ?? false })
|
||||
clearPendingInteractions(sid)
|
||||
if ((evt as any).queue_length > 0) {
|
||||
queueLengths.value.set(sid, (evt as any).queue_length)
|
||||
setAbortState(null)
|
||||
@@ -1798,7 +1819,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
{ onReconnectResume: applyReconnectResume },
|
||||
)
|
||||
|
||||
if (!isBridgeSlashCommand || isBridgeCompressCommand) {
|
||||
if (!isBridgeSlashCommand || isBridgeCompressCommand || isBridgePlanCommand) {
|
||||
streamStates.value.set(sid, ctrl)
|
||||
}
|
||||
} catch (err: any) {
|
||||
@@ -1920,6 +1941,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
|
||||
case 'abort.completed': {
|
||||
setAbortState({ aborting: false, synced: (evt as any).synced ?? false })
|
||||
clearPendingInteractions(sid)
|
||||
if ((evt as any).queue_length > 0) {
|
||||
queueLengths.value.set(sid, (evt as any).queue_length)
|
||||
setAbortState(null)
|
||||
@@ -2286,10 +2308,11 @@ export const useChatStore = defineStore('chat', () => {
|
||||
|
||||
const message: Message = {
|
||||
id: messageId || uid(),
|
||||
role: 'user',
|
||||
role: peer?.role === 'command' ? 'command' : 'user',
|
||||
content,
|
||||
timestamp,
|
||||
queued: !!peer?.queued,
|
||||
systemType: peer?.role === 'command' ? 'command' : undefined,
|
||||
}
|
||||
if (peer?.queued) {
|
||||
enqueueUserMessage(sid, message)
|
||||
@@ -2307,6 +2330,7 @@ export const useChatStore = defineStore('chat', () => {
|
||||
const sid = activeSessionId.value
|
||||
if (!sid) return
|
||||
if (isAborting.value) return
|
||||
clearPendingInteractions(sid)
|
||||
const ctrl = streamStates.value.get(sid)
|
||||
if (ctrl) {
|
||||
setAbortState({ aborting: true, synced: null })
|
||||
|
||||
Reference in New Issue
Block a user