feat(queue): deliver queued messages from the server
Messages queued while a session is busy used to live in the browser tab and were sent by that tab once the session went idle, so closing the tab (or losing the connection) stranded them. The web server now owns the queue: it persists to <data-dir>/message-queue.json, watches session.status on the global event hub, re-verifies idleness against OpenCode before sending, and delivers the head of the queue via prompt_async (or /command for slash commands) with the model, agent, variant, attachments, and agent mention captured at queue time. Failed sends stay queued and retry with backoff; a user abort holds delivery briefly; every change is broadcast so all clients see one queue. The shared UI store becomes a projection of the server queue outside VS Code (hydrate on connect, apply broadcasts, optimistic mutations settled on the server's copy, one-time upload of locally queued messages from older builds). Edit / send-now take the full message back from the server. A UI-driven auto-review run asks the server to hold that session's queue. VS Code keeps its local queue and foreground auto-send. Claude-Session: https://claude.ai/code/session_01HB9wdLQoZX2vfyDjwv6Rso
This commit is contained in:
@@ -2003,6 +2003,8 @@ export const dict = {
|
||||
'chat.queuedMessage.send': 'senden',
|
||||
'chat.queuedMessage.removeAria': 'Aus der Warteschlange entfernen',
|
||||
'chat.queuedMessage.reorderAria': 'Ziehen, um neu anzuordnen',
|
||||
'chat.queuedMessage.toast.queueFailed': 'Die Nachricht konnte nicht in die Warteschlange gestellt werden. Sie ist wieder im Eingabefeld.',
|
||||
'chat.queuedMessage.toast.takeFailed': 'Die Nachricht aus der Warteschlange konnte nicht geladen werden. Bitte erneut versuchen.',
|
||||
'chat.container.returnToParent.aria': 'Zurück zur übergeordneten Sitzung',
|
||||
'chat.container.returnToParent.titleNamed': 'Zurück zu: {title}',
|
||||
'chat.container.returnToParent.title': 'Zurück zur übergeordneten Sitzung',
|
||||
|
||||
@@ -2208,6 +2208,8 @@ export const dict = {
|
||||
'chat.queuedMessage.send': 'send',
|
||||
'chat.queuedMessage.removeAria': 'Remove from queue',
|
||||
'chat.queuedMessage.reorderAria': 'Drag to reorder',
|
||||
'chat.queuedMessage.toast.queueFailed': 'Couldn\'t queue the message. It\'s back in the composer.',
|
||||
'chat.queuedMessage.toast.takeFailed': 'Couldn\'t load the queued message. Please try again.',
|
||||
'chat.container.returnToParent.aria': 'Return to parent session',
|
||||
'chat.container.returnToParent.titleNamed': 'Return to: {title}',
|
||||
'chat.container.returnToParent.title': 'Return to parent session',
|
||||
|
||||
@@ -2186,6 +2186,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.queuedMessage.send": "send",
|
||||
"chat.queuedMessage.removeAria": "Eliminar de la cola",
|
||||
"chat.queuedMessage.reorderAria": "Arrastra para reordenar",
|
||||
"chat.queuedMessage.toast.queueFailed": "No se pudo poner el mensaje en cola. Vuelve a estar en el editor.",
|
||||
"chat.queuedMessage.toast.takeFailed": "No se pudo cargar el mensaje en cola. Inténtalo de nuevo.",
|
||||
"chat.container.returnToParent.aria": "Volver a la sesión principal",
|
||||
"chat.container.returnToParent.titleNamed": "Volver a: {title}",
|
||||
"chat.container.returnToParent.title": "Volver a la sesión principal",
|
||||
|
||||
@@ -1938,6 +1938,8 @@ export const dict = {
|
||||
'chat.queuedMessage.send': 'envoyer',
|
||||
'chat.queuedMessage.removeAria': 'Supprimer de la file d\'attente',
|
||||
'chat.queuedMessage.reorderAria': 'Glisser pour réorganiser',
|
||||
'chat.queuedMessage.toast.queueFailed': 'Impossible de mettre le message en file d\'attente. Il est de retour dans l\'éditeur.',
|
||||
'chat.queuedMessage.toast.takeFailed': 'Impossible de charger le message en file d\'attente. Veuillez réessayer.',
|
||||
'chat.container.returnToParent.aria': 'Retour à la session parents',
|
||||
'chat.container.returnToParent.titleNamed': 'Retourner à : {title}',
|
||||
'chat.container.returnToParent.title': 'Retour à la session parents',
|
||||
|
||||
@@ -2204,6 +2204,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.queuedMessage.send': '送信',
|
||||
'chat.queuedMessage.removeAria': 'キューから削除',
|
||||
'chat.queuedMessage.reorderAria': 'ドラッグして並び替え',
|
||||
'chat.queuedMessage.toast.queueFailed': 'メッセージをキューに追加できませんでした。入力欄に戻しました。',
|
||||
'chat.queuedMessage.toast.takeFailed': 'キューのメッセージを読み込めませんでした。もう一度お試しください。',
|
||||
'chat.container.returnToParent.aria': '親セッションに戻る',
|
||||
'chat.container.returnToParent.titleNamed': '戻る: {title}',
|
||||
'chat.container.returnToParent.title': '親セッションに戻る',
|
||||
|
||||
@@ -2210,6 +2210,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.queuedMessage.send': 'send',
|
||||
'chat.queuedMessage.removeAria': '큐에서 제거',
|
||||
'chat.queuedMessage.reorderAria': '드래그하여 순서 변경',
|
||||
'chat.queuedMessage.toast.queueFailed': '메시지를 대기열에 추가하지 못했습니다. 입력창으로 되돌렸습니다.',
|
||||
'chat.queuedMessage.toast.takeFailed': '대기열의 메시지를 불러오지 못했습니다. 다시 시도해 주세요.',
|
||||
'chat.container.returnToParent.aria': '상위 세션으로 돌아가기',
|
||||
'chat.container.returnToParent.titleNamed': '돌아가기: {title}',
|
||||
'chat.container.returnToParent.title': '상위 세션으로 돌아가기',
|
||||
|
||||
@@ -863,6 +863,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.queuedMessage.send': 'send',
|
||||
'chat.queuedMessage.removeAria': 'Usuń z kolejki',
|
||||
'chat.queuedMessage.reorderAria': 'Przeciągnij, aby zmienić kolejność',
|
||||
'chat.queuedMessage.toast.queueFailed': 'Nie udało się dodać wiadomości do kolejki. Wróciła do pola wpisywania.',
|
||||
'chat.queuedMessage.toast.takeFailed': 'Nie udało się wczytać wiadomości z kolejki. Spróbuj ponownie.',
|
||||
'chat.container.returnToParent.aria': 'Powrót do sesji nadrzędnej',
|
||||
'chat.container.returnToParent.titleNamed': 'Powrót do: {title}',
|
||||
'chat.container.returnToParent.title': 'Powrót do sesji nadrzędnej',
|
||||
|
||||
@@ -2186,6 +2186,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.queuedMessage.send": "send",
|
||||
"chat.queuedMessage.removeAria": "Excluir da fila",
|
||||
"chat.queuedMessage.reorderAria": "Arraste para reordenar",
|
||||
"chat.queuedMessage.toast.queueFailed": "Não foi possível enfileirar a mensagem. Ela voltou para o campo de texto.",
|
||||
"chat.queuedMessage.toast.takeFailed": "Não foi possível carregar a mensagem da fila. Tente novamente.",
|
||||
"chat.container.returnToParent.aria": "Voltar para a sessão principal",
|
||||
"chat.container.returnToParent.titleNamed": "Voltar para: {title}",
|
||||
"chat.container.returnToParent.title": "Voltar para a sessão principal",
|
||||
|
||||
@@ -2167,6 +2167,8 @@ export const dict = {
|
||||
'chat.queuedMessage.send': 'gönder',
|
||||
'chat.queuedMessage.removeAria': 'Kuyruktan kaldır',
|
||||
'chat.queuedMessage.reorderAria': 'Yeniden sıralamak için sürükle',
|
||||
'chat.queuedMessage.toast.queueFailed': 'Mesaj kuyruğa eklenemedi. Yazma alanına geri kondu.',
|
||||
'chat.queuedMessage.toast.takeFailed': 'Kuyruktaki mesaj yüklenemedi. Lütfen tekrar deneyin.',
|
||||
'chat.container.returnToParent.aria': 'Üst session\'a dön',
|
||||
'chat.container.returnToParent.titleNamed': 'Şuraya dön: {title}',
|
||||
'chat.container.returnToParent.title': 'Üst session\'a dön',
|
||||
|
||||
@@ -2186,6 +2186,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.queuedMessage.send": "send",
|
||||
"chat.queuedMessage.removeAria": "Видалити з черги",
|
||||
"chat.queuedMessage.reorderAria": "Перетягніть, щоб змінити порядок",
|
||||
"chat.queuedMessage.toast.queueFailed": "Не вдалося додати повідомлення в чергу. Воно повернулося в поле вводу.",
|
||||
"chat.queuedMessage.toast.takeFailed": "Не вдалося завантажити повідомлення з черги. Спробуйте ще раз.",
|
||||
"chat.container.returnToParent.aria": "Повернутися до батьківської сесії",
|
||||
"chat.container.returnToParent.titleNamed": "Повернутися до: {title}",
|
||||
"chat.container.returnToParent.title": "Повернутися до батьківської сесії",
|
||||
|
||||
@@ -2174,6 +2174,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.queuedMessage.send': 'send',
|
||||
'chat.queuedMessage.removeAria': '从队列移除',
|
||||
'chat.queuedMessage.reorderAria': '拖动以重新排序',
|
||||
'chat.queuedMessage.toast.queueFailed': '无法将消息加入队列,已放回输入框。',
|
||||
'chat.queuedMessage.toast.takeFailed': '无法加载队列中的消息,请重试。',
|
||||
'chat.container.returnToParent.aria': '返回父会话',
|
||||
'chat.container.returnToParent.titleNamed': '返回到:{title}',
|
||||
'chat.container.returnToParent.title': '返回父会话',
|
||||
|
||||
@@ -2178,6 +2178,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.queuedMessage.send': 'send',
|
||||
'chat.queuedMessage.removeAria': '從佇列移除',
|
||||
'chat.queuedMessage.reorderAria': '拖曳以重新排序',
|
||||
'chat.queuedMessage.toast.queueFailed': '無法將訊息加入佇列,已放回輸入框。',
|
||||
'chat.queuedMessage.toast.takeFailed': '無法載入佇列中的訊息,請再試一次。',
|
||||
'chat.container.returnToParent.aria': '返回父會話',
|
||||
'chat.container.returnToParent.titleNamed': '返回到:{title}',
|
||||
'chat.container.returnToParent.title': '返回父會話',
|
||||
|
||||
Reference in New Issue
Block a user