Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.9.18] - 2026-07-31

### Added

- ⌨️ **Approve or reject tool requests from the keyboard.** Tool approval shortcuts are now available in Keyboard settings, so you can keep a chat moving without reaching for the mouse.

### Fixed

- 🧰 **Tool requests continue more smoothly.** When a chat asks for permission to use tools, Computer now keeps the request in order, picks up where it left off after your choice, and handles rejected tools more cleanly.
- 💬 **Chat shortcuts stay out of the way.** Shortcuts that only apply to an active tool request now leave the rest of the app alone when there is nothing to approve or reject.
- 💬 **Open WebUI chat titles are more reliable.** New chats started through Open WebUI now get cleaner titles from the first message.

## [0.9.17] - 2026-07-31

### Changed
Expand Down
8 changes: 7 additions & 1 deletion cptr/frontend/src/lib/components/Settings/Keyboard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
nextTab: $t('keyboard.nextTab'),
prevTab: $t('keyboard.prevTab'),
quickOpen: $t('keyboard.quickOpen'),
searchAll: $t('keyboard.searchAll'),
openSettings: $t('keyboard.openSettings'),
toggleSplit: $t('keyboard.toggleSplit'),
toggleSidebar: $t('keyboard.toggleSidebar')
toggleSidebar: $t('keyboard.toggleSidebar'),
approveToolCall: $t('keyboard.approveToolCall'),
rejectToolCall: $t('keyboard.rejectToolCall'),
voiceMemo: $t('keyboard.voiceMemo')
});

/** Translated action labels for display. */
Expand All @@ -40,6 +44,8 @@
openSettings: $t('keyboard.action.openSettings'),
toggleSplit: $t('keyboard.action.toggleSplit'),
toggleSidebar: $t('keyboard.action.toggleSidebar'),
approveToolCall: $t('keyboard.action.approveToolCall'),
rejectToolCall: $t('keyboard.action.rejectToolCall'),
voiceMemo: $t('keyboard.action.voiceMemo')
});

Expand Down
31 changes: 27 additions & 4 deletions cptr/frontend/src/lib/components/chat/ChatPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
streamingChatTabs,
registerStreamingChat,
unregisterStreamingChat,
updateChatStatuses
updateChatStatuses,
registerToolApprovalShortcutHandler
} from '$lib/stores/chat';
import { socketStore } from '$lib/stores/socket.svelte';
import { onMount, onDestroy, tick } from 'svelte';
Expand Down Expand Up @@ -763,7 +764,7 @@
}
refreshCommandSessions();
commandSessionsTimer = setInterval(refreshCommandSessions, 5000);
window.addEventListener('cptr:inspect-command-session', handleInspectCommandSession);
window.addEventListener('computer:inspectCommandSession', handleInspectCommandSession);

const offChat = socketStore.on('events:chat', handleSocketEvent);
const offConnect = socketStore.on('connect', handleReconnect);
Expand All @@ -779,7 +780,7 @@
unbindSocketListeners = null;
if (commandSessionsTimer) clearInterval(commandSessionsTimer);
commandSessionsTimer = null;
window.removeEventListener('cptr:inspect-command-session', handleInspectCommandSession);
window.removeEventListener('computer:inspectCommandSession', handleInspectCommandSession);
if (landingRefreshTimer) clearTimeout(landingRefreshTimer);
if (taskClearTimer) clearTimeout(taskClearTimer);
// Don't clear streamingChatTabs here -- the global listener in
Expand Down Expand Up @@ -821,6 +822,11 @@
return () => unregisterStreamingChat(chatId!, tabId!);
});

$effect(() => {
if (!active) return;
return registerToolApprovalShortcutHandler(handleApproveToolCallShortcut);
});

// ── Persist read state separately from visibility tracking ──

$effect(() => {
Expand Down Expand Up @@ -1265,7 +1271,8 @@
item.type === 'function_call' && item.call_id === callId && item.status === 'pending'
);
if (call) {
call.status = approved ? 'running' : 'rejected';
call.status = approved ? 'queued' : 'rejected';
if (approved) call.approved = true;
allMessages = [...allMessages]; // trigger reactivity
}
}
Expand All @@ -1277,6 +1284,22 @@
});
}

function handleApproveToolCallShortcut(approved: boolean) {
for (const { msg } of [...activePath].reverse()) {
const item = msg.output?.find(
(output: any) =>
output.type === 'function_call' &&
output.name !== 'ask_user' &&
output.status === 'pending'
);
if (!item) continue;

handleApprove(msg.id, item.call_id, approved);
return true;
}
return false;
}

function handleAskUserAnswer(
messageId: string,
callId: string,
Expand Down
6 changes: 6 additions & 0 deletions cptr/frontend/src/lib/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,13 @@
"keyboard.nextTab": "Zum nächsten Tab wechseln",
"keyboard.prevTab": "Zum vorherigen Tab wechseln",
"keyboard.quickOpen": "Dateien schnell suchen und öffnen",
"keyboard.searchAll": "Chats und Dateien durchsuchen",
"keyboard.openSettings": "Einstellungen öffnen",
"keyboard.toggleSplit": "Geteilte Ansicht umschalten",
"keyboard.toggleSidebar": "Seitenleiste ein-/ausblenden",
"keyboard.approveToolCall": "Ausstehenden Tool-Aufruf genehmigen",
"keyboard.rejectToolCall": "Ausstehenden Tool-Aufruf ablehnen",
"keyboard.voiceMemo": "Sprachnotiz-Rekorder öffnen",
"changelog.whatsNew": "Neuigkeiten",
"changelog.releaseNotes": "Versionshinweise",
"changelog.loadError": "Versionshinweise konnten nicht geladen werden.",
Expand Down Expand Up @@ -826,6 +830,8 @@
"keyboard.action.openSettings": "Einstellungen öffnen",
"keyboard.action.toggleSplit": "Teilung umschalten",
"keyboard.action.toggleSidebar": "Seitenleiste umschalten",
"keyboard.action.approveToolCall": "Tool-Aufruf genehmigen",
"keyboard.action.rejectToolCall": "Tool-Aufruf ablehnen",
"keyboard.action.voiceMemo": "Sprachnotiz",
"keyboard.conflict": "Auch belegt von {{action}}",
"chat.history.justNow": "Gerade eben",
Expand Down
6 changes: 6 additions & 0 deletions cptr/frontend/src/lib/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -540,9 +540,13 @@
"keyboard.nextTab": "Switch to the next tab",
"keyboard.prevTab": "Switch to the previous tab",
"keyboard.quickOpen": "Search and open files quickly",
"keyboard.searchAll": "Search across chats and files",
"keyboard.openSettings": "Open the settings panel",
"keyboard.toggleSplit": "Toggle split editor view",
"keyboard.toggleSidebar": "Show or hide the sidebar",
"keyboard.approveToolCall": "Approve the pending tool call",
"keyboard.rejectToolCall": "Reject the pending tool call",
"keyboard.voiceMemo": "Open voice memo recorder",
"changelog.whatsNew": "What's New",
"changelog.releaseNotes": "Release Notes",
"changelog.loadError": "Couldn't load release notes.",
Expand Down Expand Up @@ -863,6 +867,8 @@
"keyboard.action.openSettings": "Open Settings",
"keyboard.action.toggleSplit": "Toggle Split",
"keyboard.action.toggleSidebar": "Toggle Sidebar",
"keyboard.action.approveToolCall": "Approve Tool Call",
"keyboard.action.rejectToolCall": "Reject Tool Call",
"keyboard.action.voiceMemo": "Voice Memo",
"keyboard.conflict": "Also bound to {{action}}",
"chat.history.justNow": "Just now",
Expand Down
6 changes: 6 additions & 0 deletions cptr/frontend/src/lib/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,13 @@
"keyboard.nextTab": "Cambiar a la siguiente pestaña",
"keyboard.prevTab": "Cambiar a la pestaña anterior",
"keyboard.quickOpen": "Buscar y abrir archivos rápidamente",
"keyboard.searchAll": "Buscar en chats y archivos",
"keyboard.openSettings": "Abrir el panel de ajustes",
"keyboard.toggleSplit": "Alternar vista dividida del editor",
"keyboard.toggleSidebar": "Mostrar u ocultar la barra lateral",
"keyboard.approveToolCall": "Aprobar la llamada de herramienta pendiente",
"keyboard.rejectToolCall": "Rechazar la llamada de herramienta pendiente",
"keyboard.voiceMemo": "Abrir grabadora de nota de voz",
"changelog.whatsNew": "Novedades",
"changelog.releaseNotes": "Notas de la versión",
"changelog.loadError": "No se pudieron cargar las notas de la versión.",
Expand Down Expand Up @@ -826,6 +830,8 @@
"keyboard.action.openSettings": "Abrir ajustes",
"keyboard.action.toggleSplit": "Alternar división",
"keyboard.action.toggleSidebar": "Alternar barra lateral",
"keyboard.action.approveToolCall": "Aprobar herramienta",
"keyboard.action.rejectToolCall": "Rechazar herramienta",
"keyboard.action.voiceMemo": "Nota de voz",
"keyboard.conflict": "También asignado a {{action}}",
"chat.history.justNow": "Ahora mismo",
Expand Down
6 changes: 6 additions & 0 deletions cptr/frontend/src/lib/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,13 @@
"keyboard.nextTab": "Passer à l'onglet suivant",
"keyboard.prevTab": "Passer à l'onglet précédent",
"keyboard.quickOpen": "Rechercher et ouvrir des fichiers rapidement",
"keyboard.searchAll": "Rechercher dans les chats et fichiers",
"keyboard.openSettings": "Ouvrir le panneau des paramètres",
"keyboard.toggleSplit": "Basculer la vue divisée de l'éditeur",
"keyboard.toggleSidebar": "Afficher ou masquer la barre latérale",
"keyboard.approveToolCall": "Approuver l'appel d'outil en attente",
"keyboard.rejectToolCall": "Rejeter l'appel d'outil en attente",
"keyboard.voiceMemo": "Ouvrir l'enregistreur de note vocale",
"changelog.whatsNew": "Nouveautés",
"changelog.releaseNotes": "Notes de version",
"changelog.loadError": "Impossible de charger les notes de version.",
Expand Down Expand Up @@ -826,6 +830,8 @@
"keyboard.action.openSettings": "Ouvrir les paramètres",
"keyboard.action.toggleSplit": "Basculer la division",
"keyboard.action.toggleSidebar": "Basculer la barre latérale",
"keyboard.action.approveToolCall": "Approuver l'outil",
"keyboard.action.rejectToolCall": "Rejeter l'outil",
"keyboard.action.voiceMemo": "Note vocale",
"keyboard.conflict": "Également lié à {{action}}",
"chat.history.justNow": "À l'instant",
Expand Down
6 changes: 6 additions & 0 deletions cptr/frontend/src/lib/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,13 @@
"keyboard.nextTab": "次のタブに切り替える",
"keyboard.prevTab": "前のタブに切り替える",
"keyboard.quickOpen": "ファイルをすばやく検索して開く",
"keyboard.searchAll": "チャットとファイルを検索",
"keyboard.openSettings": "設定パネルを開く",
"keyboard.toggleSplit": "エディタの分割表示を切替",
"keyboard.toggleSidebar": "サイドバーの表示/非表示",
"keyboard.approveToolCall": "保留中のツール呼び出しを承認",
"keyboard.rejectToolCall": "保留中のツール呼び出しを拒否",
"keyboard.voiceMemo": "音声メモ録音を開く",
"changelog.whatsNew": "新機能",
"changelog.releaseNotes": "リリースノート",
"changelog.loadError": "リリースノートを読み込めませんでした。",
Expand Down Expand Up @@ -826,6 +830,8 @@
"keyboard.action.openSettings": "設定を開く",
"keyboard.action.toggleSplit": "分割を切替",
"keyboard.action.toggleSidebar": "サイドバーを切替",
"keyboard.action.approveToolCall": "ツールを承認",
"keyboard.action.rejectToolCall": "ツールを拒否",
"keyboard.action.voiceMemo": "音声メモ",
"keyboard.conflict": "{{action}}にも割り当て済み",
"chat.history.justNow": "たった今",
Expand Down
6 changes: 6 additions & 0 deletions cptr/frontend/src/lib/i18n/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,13 @@
"keyboard.nextTab": "다음 탭으로 전환",
"keyboard.prevTab": "이전 탭으로 전환",
"keyboard.quickOpen": "파일을 빠르게 검색하고 열기",
"keyboard.searchAll": "채팅과 파일 검색",
"keyboard.openSettings": "설정 패널 열기",
"keyboard.toggleSplit": "편집기 분할 보기 전환",
"keyboard.toggleSidebar": "사이드바 표시/숨기기",
"keyboard.approveToolCall": "대기 중인 도구 호출 승인",
"keyboard.rejectToolCall": "대기 중인 도구 호출 거부",
"keyboard.voiceMemo": "음성 메모 녹음기 열기",
"changelog.whatsNew": "새로운 기능",
"changelog.releaseNotes": "릴리스 노트",
"changelog.loadError": "릴리스 노트를 불러올 수 없습니다.",
Expand Down Expand Up @@ -826,6 +830,8 @@
"keyboard.action.openSettings": "설정 열기",
"keyboard.action.toggleSplit": "분할 전환",
"keyboard.action.toggleSidebar": "사이드바 전환",
"keyboard.action.approveToolCall": "도구 승인",
"keyboard.action.rejectToolCall": "도구 거부",
"keyboard.action.voiceMemo": "음성 메모",
"keyboard.conflict": "{{action}}에도 할당됨",
"chat.history.justNow": "방금",
Expand Down
6 changes: 6 additions & 0 deletions cptr/frontend/src/lib/i18n/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,13 @@
"keyboard.nextTab": "Mudar para a próxima aba",
"keyboard.prevTab": "Mudar para a aba anterior",
"keyboard.quickOpen": "Pesquisar e abrir arquivos rapidamente",
"keyboard.searchAll": "Pesquisar em chats e arquivos",
"keyboard.openSettings": "Abrir o painel de configurações",
"keyboard.toggleSplit": "Alternar visualização dividida do editor",
"keyboard.toggleSidebar": "Mostrar ou ocultar a barra lateral",
"keyboard.approveToolCall": "Aprovar a chamada de ferramenta pendente",
"keyboard.rejectToolCall": "Rejeitar a chamada de ferramenta pendente",
"keyboard.voiceMemo": "Abrir gravador de nota de voz",
"changelog.whatsNew": "Novidades",
"changelog.releaseNotes": "Notas da versão",
"changelog.loadError": "Não foi possível carregar as notas da versão.",
Expand Down Expand Up @@ -826,6 +830,8 @@
"keyboard.action.openSettings": "Abrir configurações",
"keyboard.action.toggleSplit": "Alternar divisão",
"keyboard.action.toggleSidebar": "Alternar barra lateral",
"keyboard.action.approveToolCall": "Aprovar ferramenta",
"keyboard.action.rejectToolCall": "Rejeitar ferramenta",
"keyboard.action.voiceMemo": "Nota de voz",
"keyboard.conflict": "Também atribuído a {{action}}",
"chat.history.justNow": "Agora",
Expand Down
6 changes: 6 additions & 0 deletions cptr/frontend/src/lib/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,13 @@
"keyboard.nextTab": "Перейти к следующей вкладке",
"keyboard.prevTab": "Перейти к предыдущей вкладке",
"keyboard.quickOpen": "Быстрый поиск и открытие файлов",
"keyboard.searchAll": "Поиск в чатах и файлах",
"keyboard.openSettings": "Открыть панель настроек",
"keyboard.toggleSplit": "Переключить разделённый вид редактора",
"keyboard.toggleSidebar": "Показать или скрыть боковую панель",
"keyboard.approveToolCall": "Одобрить ожидающий вызов инструмента",
"keyboard.rejectToolCall": "Отклонить ожидающий вызов инструмента",
"keyboard.voiceMemo": "Открыть запись голосовой заметки",
"changelog.whatsNew": "Что нового",
"changelog.releaseNotes": "Примечания к выпуску",
"changelog.loadError": "Не удалось загрузить примечания к выпуску.",
Expand Down Expand Up @@ -826,6 +830,8 @@
"keyboard.action.openSettings": "Открыть настройки",
"keyboard.action.toggleSplit": "Переключить разделение",
"keyboard.action.toggleSidebar": "Переключить боковую панель",
"keyboard.action.approveToolCall": "Одобрить инструмент",
"keyboard.action.rejectToolCall": "Отклонить инструмент",
"keyboard.action.voiceMemo": "Голосовая заметка",
"keyboard.conflict": "Также назначено на {{action}}",
"chat.history.justNow": "Только что",
Expand Down
6 changes: 6 additions & 0 deletions cptr/frontend/src/lib/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,13 @@
"keyboard.nextTab": "切换到下一个标签页",
"keyboard.prevTab": "切换到上一个标签页",
"keyboard.quickOpen": "快速搜索并打开文件",
"keyboard.searchAll": "搜索聊天和文件",
"keyboard.openSettings": "打开设置面板",
"keyboard.toggleSplit": "切换编辑器分割视图",
"keyboard.toggleSidebar": "显示或隐藏侧边栏",
"keyboard.approveToolCall": "批准待处理的工具调用",
"keyboard.rejectToolCall": "拒绝待处理的工具调用",
"keyboard.voiceMemo": "打开语音备忘录录制器",
"changelog.whatsNew": "新功能",
"changelog.releaseNotes": "发行说明",
"changelog.loadError": "无法加载发行说明。",
Expand Down Expand Up @@ -826,6 +830,8 @@
"keyboard.action.openSettings": "打开设置",
"keyboard.action.toggleSplit": "切换分割",
"keyboard.action.toggleSidebar": "切换侧边栏",
"keyboard.action.approveToolCall": "批准工具调用",
"keyboard.action.rejectToolCall": "拒绝工具调用",
"keyboard.action.voiceMemo": "语音备忘录",
"keyboard.conflict": "同时绑定到 {{action}}",
"chat.history.justNow": "刚刚",
Expand Down
6 changes: 6 additions & 0 deletions cptr/frontend/src/lib/i18n/locales/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,13 @@
"keyboard.nextTab": "切換到下一個分頁",
"keyboard.prevTab": "切換到上一個分頁",
"keyboard.quickOpen": "快速搜尋並開啟檔案",
"keyboard.searchAll": "搜尋聊天和檔案",
"keyboard.openSettings": "開啟設定面板",
"keyboard.toggleSplit": "切換編輯器分割檢視",
"keyboard.toggleSidebar": "顯示或隱藏側邊欄",
"keyboard.approveToolCall": "核准待處理的工具呼叫",
"keyboard.rejectToolCall": "拒絕待處理的工具呼叫",
"keyboard.voiceMemo": "開啟語音備忘錄錄製器",
"changelog.whatsNew": "最新消息",
"changelog.releaseNotes": "發行說明",
"changelog.loadError": "無法載入發行說明。",
Expand Down Expand Up @@ -826,6 +830,8 @@
"keyboard.action.openSettings": "開啟設定",
"keyboard.action.toggleSplit": "切換分割",
"keyboard.action.toggleSidebar": "切換側邊欄",
"keyboard.action.approveToolCall": "核准工具呼叫",
"keyboard.action.rejectToolCall": "拒絕工具呼叫",
"keyboard.action.voiceMemo": "語音備忘錄",
"keyboard.conflict": "同時繫結到 {{action}}",
"chat.history.justNow": "剛才",
Expand Down
21 changes: 21 additions & 0 deletions cptr/frontend/src/lib/stores/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,27 @@ export function unregisterStreamingChat(chatId: string, tabId: string) {
if (tabIds.size === 0) chatToTabs.delete(chatId);
}

type ToolApprovalShortcutHandler = (approved: boolean) => boolean;

let toolApprovalShortcutHandler: ToolApprovalShortcutHandler | null = null;

export function registerToolApprovalShortcutHandler(
handler: ToolApprovalShortcutHandler
): () => void {
toolApprovalShortcutHandler = handler;
return () => {
if (toolApprovalShortcutHandler === handler) toolApprovalShortcutHandler = null;
};
}

export function approveActiveToolCallShortcut(): boolean {
return toolApprovalShortcutHandler?.(true) ?? false;
}

export function rejectActiveToolCallShortcut(): boolean {
return toolApprovalShortcutHandler?.(false) ?? false;
}

/**
* Bind a global socket listener that clears streamingChatTabs when a
* chat's "done" event arrives -- even if the ChatPanel is not mounted.
Expand Down
Loading
Loading