Compare commits

..
Author SHA1 Message Date
bincxz 8cbcb73b61 fix(ssh): retain vnode directory cwd fallback 2026-09-18 11:49:37 +08:00
bincxz d3eb3007bb fix(ssh): reject lsof diagnostics as terminal directories 2026-09-18 11:45:25 +08:00
陈大猫 c16f0c43d4 Merge pull request #3401 from OOKAMISAMA/main
feat(tab): Add configurable terminal tab double-click behavior
2026-09-15 22:52:12 +08:00
OOKAMI 4df94736d0 Update TopTabs.test.ts 2026-09-15 17:55:59 +08:00
OOKAMI 91c3defe66 Add configurable terminal tab double-click behavior
Introduce a new TerminalTabDoubleClickBehavior setting (duplicate | copy | disabled) with default 'duplicate'. Add model field, normalization, default, and sync key (tabDoubleClickBehavior). UI: TerminalBehaviorSettings adds a Select; AppView and TopTabs pass the setting through; TopTabItems implements createTopTabSessionDoubleClickHandler to perform duplicate/copy with a safe fallback. Add i18n strings, settings search entry, and update tests to cover behavior and serialization. Improves user control over double-click actions on session tabs.
2026-09-15 16:56:55 +08:00
陈大猫 faf74ae9be Merge pull request #3395 from binaricat/ai/issue-3394-34924104389
fix(ai): replay signed Anthropic thinking blocks in Catty follow-up turns
2026-09-15 13:47:20 +08:00
陈大猫 cbace2f2ac Merge pull request #3389 from lengyuqu/codebuddy
feat: upgrade CodeBuddy SDK integration
2026-09-15 13:46:41 +08:00
netcatty-bot a52726aef4 fix(#3394): automated AI fix 2026-09-15 03:36:18 +00:00
lengyuqu 605dc94b13 fix: clarify CodeBuddy session persistence scope 2026-09-14 17:02:06 +08:00
lengyuqu 9e74618f2e feat: upgrade CodeBuddy SDK integration 2026-09-14 16:38:33 +08:00
github-actions[bot]andgithub-actions[bot] b46910d46e chore: update star history charts [skip ci] (#3383)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-14 00:11:12 +00:00
陈大猫 d8ea77c28b Merge pull request #3373 from binaricat/codex/3135-custom-provider-headers
feat(ai): configure custom provider HTTP headers securely
2026-09-13 16:37:09 +08:00
陈大猫 843c81ca44 Merge pull request #3376 from binaricat/ai/issue-3375-34697848012
fix(sftp): expand tree folders in place on double click
2026-09-13 15:44:39 +08:00
bincxz a95eb62cb1 fix(sftp): isolate tree arrow double clicks and test real events 2026-09-13 15:32:32 +08:00
netcatty-bot f24b5f4202 fix(#3375): automated AI fix 2026-09-12 14:14:36 +00:00
48 changed files with 853 additions and 111 deletions

No files matched your search

+1
View File
@@ -504,6 +504,7 @@ function AppViewInner({ domains }: AppViewProps) {
onRenameSession={startSessionRename}
onCopySession={copySessionWithCurrentShell}
onDuplicateSession={duplicateSessionWithCurrentShell}
tabDoubleClickBehavior={terminalSettings.tabDoubleClickBehavior}
onCopySessionToNewWindow={copySessionToNewWindowWithCurrentShell}
onEditHost={handleEditHostFromOverlay}
onRenameWorkspace={startWorkspaceRename}
+7 -2
View File
@@ -8,6 +8,7 @@ export const enAiMessages: Messages = {
'ai.chat.compactingStep': 'Trimming context for next step…',
'ai.chat.compactionRetry': 'Request was too large. Compacting context and retrying…',
'ai.chat.compactionBanner': 'Context compacted: {before}K → {after}K tokens',
'ai.chat.contextCompacted': 'Context compacted',
'ai.chat.contextUsage': 'Context usage: {used} / {max} tokens',
'ai.chat.activity.title': 'Agent activity',
'ai.chat.activity.plan': 'Plan',
@@ -234,20 +235,24 @@ export const enAiMessages: Messages = {
'ai.codebuddy.envVars': 'Environment variables',
'ai.codebuddy.envVars.placeholder': 'CODEBUDDY_API_KEY=...\nCODEBUDDY_AUTH_TOKEN=...\nOTHER_VAR=...',
'ai.codebuddy.envVars.hint': 'One KEY=VALUE per line, passed to the CodeBuddy agent. Set CODEBUDDY_API_KEY or CODEBUDDY_AUTH_TOKEN here for authentication. Stored locally in plaintext.',
'ai.codebuddy.advancedSection': 'Advanced options (SDK 0.3.230)',
'ai.codebuddy.advancedSection': 'Advanced options (SDK 0.3.258)',
'ai.codebuddy.effort': 'Reasoning Effort',
'ai.codebuddy.effort.default': 'Default',
'ai.codebuddy.effort.minimal': 'Minimal',
'ai.codebuddy.effort.low': 'Low',
'ai.codebuddy.effort.medium': 'Medium',
'ai.codebuddy.effort.high': 'High',
'ai.codebuddy.effort.xhigh': 'XHigh',
'ai.codebuddy.effort.hint': 'Controls model reasoning depth. Use Low for simple commands to save tokens, High/XHigh for complex diagnostics.',
'ai.codebuddy.effort.max': 'Max',
'ai.codebuddy.effort.hint': 'Controls model reasoning depth. Use Minimal/Low for simple commands to save tokens, Max/High for complex diagnostics.',
'ai.codebuddy.maxTurns': 'Max Turns',
'ai.codebuddy.maxTurns.hint': 'Limits the maximum conversation turns per request to prevent runaway loops. Leave empty for default.',
'ai.codebuddy.maxBudget': 'Max Budget (USD)',
'ai.codebuddy.maxBudget.hint': 'Maximum spend (USD) per request. Stops automatically when exceeded. Leave empty for no limit.',
'ai.codebuddy.sandbox': 'Sandbox Mode',
'ai.codebuddy.sandbox.hint': 'Execute tool calls in a sandbox, restricting filesystem and network access.',
'ai.codebuddy.persistSession': 'Session Persistence',
'ai.codebuddy.persistSession.hint': "Keep CodeBuddy's own session transcript on disk so CodeBuddy can resume it later. Turn off to disable CodeBuddy session storage; Netcatty may still save chat history and external session metadata. File checkpointing is skipped. Requires CodeBuddy CLI 2.125.1 or newer.",
'ai.codebuddy.fileCheckpointing': 'File Checkpointing',
'ai.codebuddy.fileCheckpointing.hint': 'Enable file operation checkpoints so AI file modifications can be rolled back.',
'ai.codebuddy.elicitation.title': 'CodeBuddy needs your input',
+5
View File
@@ -712,6 +712,11 @@ Highlight the focused split pane:
'settings.terminal.behavior.dynamicTabTitle.off': 'Disabled',
'settings.terminal.behavior.dynamicTabTitle.agent': 'Agents only',
'settings.terminal.behavior.dynamicTabTitle.all': 'All sessions',
'settings.terminal.behavior.tabDoubleClick': 'Tab double-click behavior',
'settings.terminal.behavior.tabDoubleClick.desc': 'Choose the action when double-clicking a terminal tab.',
'settings.terminal.behavior.tabDoubleClick.duplicate': 'Duplicate session (new connection)',
'settings.terminal.behavior.tabDoubleClick.copy': 'Copy tab (reuse connection)',
'settings.terminal.behavior.tabDoubleClick.disabled': 'Do nothing',
'settings.terminal.behavior.oscNotifications': 'OSC desktop notifications',
'settings.terminal.behavior.oscNotifications.desc':
'Show a system notification when a program emits OSC 9, OSC 777 notify, or kitty OSC 99. Used by Codex and other agents for turn-complete and approval alerts.',
+7 -2
View File
@@ -8,6 +8,7 @@ export const esAiMessages: Messages = {
'ai.chat.compactingStep': 'Recortando el contexto para el siguiente paso…',
'ai.chat.compactionRetry': 'La petición era demasiado grande. Compactando el contexto y reintentando…',
'ai.chat.compactionBanner': 'Contexto compactado: {before}K → {after}K tokens',
'ai.chat.contextCompacted': 'Contexto compactado',
'ai.chat.contextUsage': 'Uso de contexto: {used} / {max} tokens',
'ai.chat.activity.title': 'Actividad del agente',
'ai.chat.activity.plan': 'Plan',
@@ -225,20 +226,24 @@ export const esAiMessages: Messages = {
'ai.codebuddy.envVars': 'Variables de entorno',
'ai.codebuddy.envVars.placeholder': 'CODEBUDDY_API_KEY=...\nCODEBUDDY_AUTH_TOKEN=...\nOTHER_VAR=...',
'ai.codebuddy.envVars.hint': 'Una KEY=VALUE por línea, pasada al agente de CodeBuddy. Define CODEBUDDY_API_KEY o CODEBUDDY_AUTH_TOKEN aquí para la autenticación. Se almacena localmente en texto plano.',
'ai.codebuddy.advancedSection': 'Opciones avanzadas (SDK 0.3.230)',
'ai.codebuddy.advancedSection': 'Opciones avanzadas (SDK 0.3.258)',
'ai.codebuddy.effort': 'Esfuerzo de razonamiento',
'ai.codebuddy.effort.default': 'Predeterminado',
'ai.codebuddy.effort.minimal': 'Mínimo',
'ai.codebuddy.effort.low': 'Bajo',
'ai.codebuddy.effort.medium': 'Medio',
'ai.codebuddy.effort.high': 'Alto',
'ai.codebuddy.effort.xhigh': 'XHigh',
'ai.codebuddy.effort.hint': 'Controla la profundidad de razonamiento del modelo. Usa Bajo para comandos simples y ahorrar tokens, y Alto/XHigh para diagnósticos complejos.',
'ai.codebuddy.effort.max': 'Máximo',
'ai.codebuddy.effort.hint': 'Controla la profundidad de razonamiento del modelo. Usa Mínimo/Bajo para comandos simples y ahorrar tokens, y Máximo/Alto para diagnósticos complejos.',
'ai.codebuddy.maxTurns': 'Máximo de turnos',
'ai.codebuddy.maxTurns.hint': 'Limita el máximo de turnos de conversación por petición para evitar bucles descontrolados. Déjalo vacío para usar el predeterminado.',
'ai.codebuddy.maxBudget': 'Presupuesto máximo (USD)',
'ai.codebuddy.maxBudget.hint': 'Gasto máximo (USD) por petición. Se detiene automáticamente al superarse. Déjalo vacío para no tener límite.',
'ai.codebuddy.sandbox': 'Modo sandbox',
'ai.codebuddy.sandbox.hint': 'Ejecuta llamadas a herramientas en un sandbox, restringiendo el acceso al sistema de archivos y a la red.',
'ai.codebuddy.persistSession': 'Persistencia de sesión',
'ai.codebuddy.persistSession.hint': 'Guarda en disco la transcripción de la sesión propia de CodeBuddy para que CodeBuddy pueda reanudarla más tarde. Desactívalo para deshabilitar el almacenamiento de sesiones de CodeBuddy; Netcatty aún puede guardar el historial del chat y los metadatos de la sesión externa. Se omiten los puntos de control de archivos. Requiere CodeBuddy CLI 2.125.1 o superior.',
'ai.codebuddy.fileCheckpointing': 'Puntos de control de archivos',
'ai.codebuddy.fileCheckpointing.hint': 'Habilita puntos de control de operaciones de archivos para poder revertir las modificaciones de IA a los archivos.',
'ai.codebuddy.elicitation.title': 'CodeBuddy necesita tu aporte',
+5
View File
@@ -627,6 +627,11 @@ Resalta el panel dividido enfocado:
'settings.terminal.behavior.dynamicTabTitle.off': 'Desactivado',
'settings.terminal.behavior.dynamicTabTitle.agent': 'Solo agentes',
'settings.terminal.behavior.dynamicTabTitle.all': 'Todas las sesiones',
'settings.terminal.behavior.tabDoubleClick': 'Comportamiento de doble clic en pestaña',
'settings.terminal.behavior.tabDoubleClick.desc': 'Elige la acción al hacer doble clic en una pestaña de terminal.',
'settings.terminal.behavior.tabDoubleClick.duplicate': 'Duplicar sesión (nueva conexión)',
'settings.terminal.behavior.tabDoubleClick.copy': 'Copiar pestaña (reutilizar conexión)',
'settings.terminal.behavior.tabDoubleClick.disabled': 'No hacer nada',
'settings.terminal.behavior.oscNotifications': 'Notificaciones de escritorio OSC',
'settings.terminal.behavior.oscNotifications.desc':
'Muestra una notificación del sistema cuando un programa emite OSC 9, OSC 777 notify o kitty OSC 99. Lo usan Codex y otros agentes para avisos de turno completado y de aprobación.',
+21
View File
@@ -5,6 +5,7 @@ export const ruAiMessages: Messages = {
// AI Settings
'ai.agentSettings': 'Настройки агента',
'ai.chat.preparing': 'Подготовка…',
'ai.chat.contextCompacted': 'Контекст сжат',
'ai.title': 'AI',
'ai.description': 'Настройка AI-провайдеров, агентов и параметров безопасности',
'ai.providers': 'Провайдеры',
@@ -206,6 +207,26 @@ export const ruAiMessages: Messages = {
'ai.codebuddy.envVars': 'Переменные окружения',
'ai.codebuddy.envVars.placeholder': 'CODEBUDDY_API_KEY=...\nCODEBUDDY_AUTH_TOKEN=...\nOTHER_VAR=...',
'ai.codebuddy.envVars.hint': 'По одной записи KEY=VALUE на строку, передаются агенту CodeBuddy. Укажите CODEBUDDY_API_KEY или CODEBUDDY_AUTH_TOKEN для аутентификации. Хранятся локально в открытом виде.',
'ai.codebuddy.advancedSection': 'Расширенные параметры (SDK 0.3.258)',
'ai.codebuddy.effort': 'Усилие рассуждения',
'ai.codebuddy.effort.default': 'По умолчанию',
'ai.codebuddy.effort.minimal': 'Minimal',
'ai.codebuddy.effort.low': 'Low',
'ai.codebuddy.effort.medium': 'Medium',
'ai.codebuddy.effort.high': 'High',
'ai.codebuddy.effort.xhigh': 'XHigh',
'ai.codebuddy.effort.max': 'Max',
'ai.codebuddy.effort.hint': 'Управляет глубиной рассуждения модели. Для простых команд используйте Minimal/Low, чтобы экономить токены, а для сложной диагностики — Max/High.',
'ai.codebuddy.maxTurns': 'Максимум шагов',
'ai.codebuddy.maxTurns.hint': 'Ограничивает максимальное число шагов диалога за запрос, предотвращая зацикливание. Оставьте пустым для значения по умолчанию.',
'ai.codebuddy.maxBudget': 'Максимальный бюджет (USD)',
'ai.codebuddy.maxBudget.hint': 'Максимальные расходы (USD) за запрос. При превышении выполнение останавливается автоматически. Оставьте пустым, чтобы не ограничивать.',
'ai.codebuddy.sandbox': 'Режим песочницы',
'ai.codebuddy.sandbox.hint': 'Выполняет вызовы инструментов в песочнице, ограничивая доступ к файловой системе и сети.',
'ai.codebuddy.persistSession': 'Сохранение сессии',
'ai.codebuddy.persistSession.hint': 'Сохраняет на диск собственную историю сессии CodeBuddy, чтобы CodeBuddy мог возобновить её позже. Отключите, чтобы запретить хранение сессии CodeBuddy; Netcatty всё ещё может сохранять историю чата и метаданные внешней сессии. Контрольные точки файлов пропускаются. Требуется CodeBuddy CLI 2.125.1 или новее.',
'ai.codebuddy.fileCheckpointing': 'Контрольные точки файлов',
'ai.codebuddy.fileCheckpointing.hint': 'Включает контрольные точки файловых операций, чтобы изменения файлов ИИ можно было откатить.',
'ai.codebuddy.elicitation.title': 'CodeBuddy требуется ваш ответ',
'ai.codebuddy.elicitation.description': 'Проверьте запрос, чтобы продолжить текущий ход.',
'ai.codebuddy.elicitation.select': 'Выберите вариант',
+5
View File
@@ -669,6 +669,11 @@ export const ruCoreMessages: Messages = {
'settings.terminal.behavior.dynamicTabTitle.off': 'Отключено',
'settings.terminal.behavior.dynamicTabTitle.agent': 'Только агенты',
'settings.terminal.behavior.dynamicTabTitle.all': 'Все сеансы',
'settings.terminal.behavior.tabDoubleClick': 'Действие по двойному щелчку вкладки',
'settings.terminal.behavior.tabDoubleClick.desc': 'Выберите действие при двойном щелчке вкладки терминала.',
'settings.terminal.behavior.tabDoubleClick.duplicate': 'Дублировать сеанс (новое подключение)',
'settings.terminal.behavior.tabDoubleClick.copy': 'Копировать вкладку (использовать подключение)',
'settings.terminal.behavior.tabDoubleClick.disabled': 'Ничего не делать',
'settings.terminal.behavior.oscNotifications': 'Системные уведомления OSC',
'settings.terminal.behavior.oscNotifications.desc':
'Показывать системное уведомление, когда программа отправляет OSC 9, OSC 777 notify или kitty OSC 99. Так Codex и другие агенты сообщают о завершении хода и запросе подтверждения.',
+7 -2
View File
@@ -8,6 +8,7 @@ export const zhCNAiMessages: Messages = {
'ai.chat.compactingStep': '正在为下一步整理上下文…',
'ai.chat.compactionRetry': '请求过大,正在压缩上下文并重试…',
'ai.chat.compactionBanner': '上下文已压缩:{before}K → {after}K tokens',
'ai.chat.contextCompacted': '上下文已压缩',
'ai.chat.contextUsage': '上下文使用:{used} / {max} tokens',
'ai.chat.activity.title': 'Agent 活动',
'ai.chat.activity.plan': '计划',
@@ -234,20 +235,24 @@ export const zhCNAiMessages: Messages = {
'ai.codebuddy.envVars': '环境变量',
'ai.codebuddy.envVars.placeholder': 'CODEBUDDY_API_KEY=...\nCODEBUDDY_AUTH_TOKEN=...\nOTHER_VAR=...',
'ai.codebuddy.envVars.hint': '每行一个 KEY=VALUE,传给 CodeBuddy agent。可在此设置 CODEBUDDY_API_KEY 或 CODEBUDDY_AUTH_TOKEN 完成认证。明文存在本地。',
'ai.codebuddy.advancedSection': '高级选项(SDK 0.3.230)',
'ai.codebuddy.advancedSection': '高级选项(SDK 0.3.258)',
'ai.codebuddy.effort': '推理力度',
'ai.codebuddy.effort.default': '默认',
'ai.codebuddy.effort.minimal': 'Minimal',
'ai.codebuddy.effort.low': 'Low',
'ai.codebuddy.effort.medium': 'Medium',
'ai.codebuddy.effort.high': 'High',
'ai.codebuddy.effort.xhigh': 'XHigh',
'ai.codebuddy.effort.hint': '控制模型推理深度。简单命令用 Low 节省 token,复杂诊断用 High/XHigh。',
'ai.codebuddy.effort.max': 'Max',
'ai.codebuddy.effort.hint': '控制模型推理深度。简单命令用 Minimal/Low 节省 token,复杂诊断用 Max/High。',
'ai.codebuddy.maxTurns': '最大轮次',
'ai.codebuddy.maxTurns.hint': '限制每次请求的最大对话轮次,防止 AI 失控循环。留空使用默认值。',
'ai.codebuddy.maxBudget': '预算上限 (USD)',
'ai.codebuddy.maxBudget.hint': '每次请求的最大花费(美元)。超出后自动停止。留空不限制。',
'ai.codebuddy.sandbox': '沙箱模式',
'ai.codebuddy.sandbox.hint': '在沙箱中执行工具调用,限制文件系统和网络访问。',
'ai.codebuddy.persistSession': '会话持久化',
'ai.codebuddy.persistSession.hint': '将 CodeBuddy 自身的会话记录保存到磁盘,以便 CodeBuddy 后续恢复会话。关闭后将停用 CodeBuddy 的会话存储;Netcatty 仍可能保存聊天记录和外部会话元数据。文件检查点会跳过。需要 CodeBuddy CLI 2.125.1 或更高版本。',
'ai.codebuddy.fileCheckpointing': '文件检查点',
'ai.codebuddy.fileCheckpointing.hint': '启用文件操作检查点,AI 修改文件后可回滚。',
'ai.codebuddy.elicitation.title': 'CodeBuddy 需要你的输入',
@@ -347,6 +347,11 @@ export const zhCNTerminalMessages: Messages = {
'settings.terminal.behavior.dynamicTabTitle.off': '关闭',
'settings.terminal.behavior.dynamicTabTitle.agent': '仅 Agent',
'settings.terminal.behavior.dynamicTabTitle.all': '全部会话',
'settings.terminal.behavior.tabDoubleClick': '标签页双击行为',
'settings.terminal.behavior.tabDoubleClick.desc': '选择双击顶部终端标签页时执行的操作。',
'settings.terminal.behavior.tabDoubleClick.duplicate': '复制会话(新建连接)',
'settings.terminal.behavior.tabDoubleClick.copy': '复制标签页(复用连接)',
'settings.terminal.behavior.tabDoubleClick.disabled': '无操作',
'settings.terminal.behavior.oscNotifications': 'OSC 桌面通知',
'settings.terminal.behavior.oscNotifications.desc':
'当程序发出 OSC 9、OSC 777 notify 或 kitty OSC 99 时弹出系统通知。Codex 等 Agent 用它提示回合完成或等待审批。',
+7 -2
View File
@@ -8,6 +8,7 @@ export const zhTWAiMessages: Messages = {
'ai.chat.compactingStep': '正在為下一步整理上下文…',
'ai.chat.compactionRetry': '請求過大,正在壓縮上下文並重試…',
'ai.chat.compactionBanner': '上下文已壓縮:{before}K → {after}K tokens',
'ai.chat.contextCompacted': '上下文已壓縮',
'ai.chat.contextUsage': '上下文使用:{used} / {max} tokens',
'ai.chat.activity.title': 'Agent 活動',
'ai.chat.activity.plan': '計畫',
@@ -225,20 +226,24 @@ export const zhTWAiMessages: Messages = {
'ai.codebuddy.envVars': '環境變數',
'ai.codebuddy.envVars.placeholder': 'CODEBUDDY_API_KEY=...\nCODEBUDDY_AUTH_TOKEN=...\nOTHER_VAR=...',
'ai.codebuddy.envVars.hint': '每行一個 KEY=VALUE,傳給 CodeBuddy agent。可在此設定 CODEBUDDY_API_KEY 或 CODEBUDDY_AUTH_TOKEN 完成認證。明文存在本機。',
'ai.codebuddy.advancedSection': '進階選項(SDK 0.3.230)',
'ai.codebuddy.advancedSection': '進階選項(SDK 0.3.258)',
'ai.codebuddy.effort': '推理力度',
'ai.codebuddy.effort.default': '預設',
'ai.codebuddy.effort.minimal': 'Minimal',
'ai.codebuddy.effort.low': 'Low',
'ai.codebuddy.effort.medium': 'Medium',
'ai.codebuddy.effort.high': 'High',
'ai.codebuddy.effort.xhigh': 'XHigh',
'ai.codebuddy.effort.hint': '控制模型推理深度。簡單命令用 Low 節省 token,複雜診斷用 High/XHigh。',
'ai.codebuddy.effort.max': 'Max',
'ai.codebuddy.effort.hint': '控制模型推理深度。簡單命令用 Minimal/Low 節省 token,複雜診斷用 Max/High。',
'ai.codebuddy.maxTurns': '最大輪次',
'ai.codebuddy.maxTurns.hint': '限制每次請求的最大對話輪次,防止 AI 失控循環。留空使用預設值。',
'ai.codebuddy.maxBudget': '預算上限 (USD)',
'ai.codebuddy.maxBudget.hint': '每次請求的最大花費(美元)。超出後自動停止。留空不限制。',
'ai.codebuddy.sandbox': '沙箱模式',
'ai.codebuddy.sandbox.hint': '在沙箱中執行工具呼叫,限制檔案系統和網路存取。',
'ai.codebuddy.persistSession': '工作階段持久化',
'ai.codebuddy.persistSession.hint': '將 CodeBuddy 自身的工作階段記錄儲存到磁碟,以便 CodeBuddy 後續恢復工作階段。關閉後將停用 CodeBuddy 的工作階段儲存;Netcatty 仍可能儲存聊天記錄和外部工作階段中繼資料。檔案檢查點會略過。需要 CodeBuddy CLI 2.125.1 或更高版本。',
'ai.codebuddy.fileCheckpointing': '檔案檢查點',
'ai.codebuddy.fileCheckpointing.hint': '啟用檔案操作檢查點,AI 修改檔案後可回滾。',
'ai.codebuddy.elicitation.title': 'CodeBuddy 需要你的輸入',
@@ -347,6 +347,11 @@ export const zhTWTerminalMessages: Messages = {
'settings.terminal.behavior.dynamicTabTitle.off': '關閉',
'settings.terminal.behavior.dynamicTabTitle.agent': '僅 Agent',
'settings.terminal.behavior.dynamicTabTitle.all': '全部工作階段',
'settings.terminal.behavior.tabDoubleClick': '分頁雙擊行為',
'settings.terminal.behavior.tabDoubleClick.desc': '選擇雙擊頂部終端分頁時要執行的動作。',
'settings.terminal.behavior.tabDoubleClick.duplicate': '複製工作階段(新連線)',
'settings.terminal.behavior.tabDoubleClick.copy': '複製分頁(重用連線)',
'settings.terminal.behavior.tabDoubleClick.disabled': '無動作',
'settings.terminal.behavior.oscNotifications': 'OSC 桌面通知',
'settings.terminal.behavior.oscNotifications.desc':
'當程式發出 OSC 9、OSC 777 notify 或 kitty OSC 99 時顯示系統通知。Codex 等 Agent 會用它提示回合完成或等待核准。',
+4 -1
View File
@@ -31,6 +31,7 @@ import {
type TerminalSessionInfo,
} from '../../infrastructure/ai/aiChatStreamingSupport';
import { useAgentCompactionUi } from './useAgentCompactionUi';
import { useI18n } from '../i18n/I18nProvider';
export { getNetcattyBridge } from '../../infrastructure/ai/aiChatStreamingSupport';
export type { ActiveCompactionUi } from './useAgentCompactionUi';
@@ -134,6 +135,7 @@ export function useAIChatStreaming({
updateMessageById,
persistContextCompaction,
}: UseAIChatStreamingParams): UseAIChatStreamingReturn {
const { t } = useI18n();
const [streamingSessionIds, setStreamingSessions] = useState<Set<string>>(
() => new Set(sharedStreamingSessionIds),
);
@@ -195,7 +197,8 @@ export function useAIChatStreaming({
setStreamingForScope,
getLatestSession: (sessionId: string) => latestAISessionsSnapshot?.find(s => s.id === sessionId),
persistContextCompaction,
}), [addMessageToSession, updateLastMessage, updateMessageById, reportStreamError, setStreamingForScope, persistContextCompaction]);
translate: t,
}), [addMessageToSession, updateLastMessage, updateMessageById, reportStreamError, setStreamingForScope, persistContextCompaction, t]);
const sendToExternalAgent = useCallback(async (
sessionId: string,
+1 -1
View File
@@ -266,7 +266,7 @@ const SYNCABLE_TERMINAL_KEYS = [
'linkModifier', 'keywordHighlightEnabled', 'keywordHighlightRules',
'keepaliveInterval', 'keepaliveCountMax', 'disableBracketedPaste', 'clearWipesScrollback',
'autoUploadClipboardImageOnPaste',
'preserveSelectionOnInput', 'forcePromptNewLine', 'osc52Clipboard', 'oscNotifications', 'dynamicTabTitleMode',
'preserveSelectionOnInput', 'forcePromptNewLine', 'osc52Clipboard', 'oscNotifications', 'dynamicTabTitleMode', 'tabDoubleClickBehavior',
'autoCloseOnExit', 'disconnectedNoticeMode',
'showHostInfoBar', 'hostInfoBarTitleMode', 'showServerStats',
'serverStatsRefreshInterval',
+1 -1
View File
@@ -189,7 +189,7 @@ test('normalizeStoredAgentModelSelection keeps bare CodeBuddy ids unsuffixed', (
const presets: AgentModelPreset[] = [{
id: 'glm-5.1',
name: 'GLM 5.1',
thinkingLevels: ['low', 'medium', 'high', 'xhigh'],
thinkingLevels: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'],
defaultThinkingLevel: 'medium',
encodeDefaultThinking: false,
}];
+27 -7
View File
@@ -32,7 +32,7 @@ const {
} = await import("../application/state/terminalDragData.ts");
const {
activateLogViewTab,
createTopTabCopyDoubleClickHandler,
createTopTabSessionDoubleClickHandler,
formatSessionTopTabLabel,
formatSessionTopTabTooltip,
resolveSessionTabCodingCliIconState,
@@ -41,6 +41,7 @@ const {
const { activeTabStore } = await import("../application/state/activeTabStore.ts");
const indexCss = readFileSync(new URL("../index.css", import.meta.url), "utf8");
const topTabsSource = readFileSync(new URL("./TopTabs.tsx", import.meta.url), "utf8");
const topTabItemsSource = readFileSync(new URL("./top-tabs/TopTabItems.tsx", import.meta.url), "utf8");
const topTabsQuickControlsSource = readFileSync(new URL("./TopTabsQuickControls.tsx", import.meta.url), "utf8");
const syncStatusButtonSource = readFileSync(new URL("./SyncStatusButton.tsx", import.meta.url), "utf8");
const switchSource = readFileSync(new URL("./ui/switch.tsx", import.meta.url), "utf8");
@@ -53,7 +54,6 @@ const appSource = [
].join("\n");
const externalMcpToggleSource = readFileSync(new URL("../application/state/useExternalMcpToggleState.ts", import.meta.url), "utf8");
const zhTwAiSource = readFileSync(new URL("../application/i18n/locales/zh-TW/ai.ts", import.meta.url), "utf8");
const topTabItemsSource = readFileSync(new URL("./top-tabs/TopTabItems.tsx", import.meta.url), "utf8");
const terminalViewSource = readFileSync(new URL("./terminal/TerminalView.tsx", import.meta.url), "utf8");
test("host tree tab gutter fills the remaining sidebar width", () => {
@@ -248,17 +248,37 @@ test("disabling dynamic titles freezes a stored coding CLI icon and stops title
);
});
test("session top tabs copy the session on double click through the existing copy handler", () => {
test("session top tab double click dispatches the configured behavior", () => {
const copiedSessionIds: string[] = [];
const handleDoubleClick = createTopTabCopyDoubleClickHandler(
(sessionId) => copiedSessionIds.push(sessionId),
"session-1",
const duplicatedSessionIds: string[] = [];
const handlers = (["duplicate", "copy", "disabled"] as const).map((behavior) =>
createTopTabSessionDoubleClickHandler({
behavior,
onCopySession: (sessionId) => copiedSessionIds.push(sessionId),
onDuplicateSession: (sessionId) => duplicatedSessionIds.push(sessionId),
sessionId: `session-${behavior}`,
}),
);
handlers.forEach((handler) => handler({} as Parameters<typeof handler>[0]));
assert.deepEqual(duplicatedSessionIds, ["session-duplicate"]);
assert.deepEqual(copiedSessionIds, ["session-copy"]);
assert.match(topTabItemsSource, /behavior: tabDoubleClickBehavior/);
assert.match(topTabItemsSource, /onDoubleClick=\{handleDoubleClick\}/);
});
test("duplicate double-click behavior falls back to copying when unavailable", () => {
const copiedSessionIds: string[] = [];
const handleDoubleClick = createTopTabSessionDoubleClickHandler({
behavior: "duplicate",
onCopySession: (sessionId) => copiedSessionIds.push(sessionId),
sessionId: "session-1",
});
handleDoubleClick({} as Parameters<typeof handleDoubleClick>[0]);
assert.deepEqual(copiedSessionIds, ["session-1"]);
assert.match(topTabItemsSource, /onDoubleClick=\{handleDoubleClick\}/);
});
test("session close button double click stays on the close button", () => {
+5 -1
View File
@@ -11,7 +11,7 @@ import {
appendHostFromWorkspaceDrop,
resolveFocusSidebarDragKind,
} from '../domain/focusSidebarHostDrop';
import type { DynamicTabTitleMode, KeyBinding } from '../domain/models';
import type { DynamicTabTitleMode, KeyBinding, TerminalTabDoubleClickBehavior } from '../domain/models';
import { getTopTabInsertionTarget, getWorkspaceSessionDragId, hasWorkspaceSessionDrag } from '../application/state/terminalDragData';
import {
@@ -168,6 +168,7 @@ interface TopTabsProps {
showHostTreeSidebar: boolean;
switchTabKeyBinding: Pick<KeyBinding, 'mac' | 'pc'> | null;
dynamicTabTitleMode?: DynamicTabTitleMode;
tabDoubleClickBehavior: TerminalTabDoubleClickBehavior;
editorTabs: readonly EditorTabChrome[];
pluginViewTabs: readonly PluginViewTab[];
onClosePluginViewTab: (tabId: string) => void;
@@ -217,6 +218,7 @@ const TopTabsInner: React.FC<TopTabsProps> = ({
showHostTreeSidebar,
switchTabKeyBinding,
dynamicTabTitleMode,
tabDoubleClickBehavior,
editorTabs,
pluginViewTabs,
onClosePluginViewTab,
@@ -901,6 +903,7 @@ const TopTabsInner: React.FC<TopTabsProps> = ({
onEditHost={onEditHost}
renderBulkCloseItems={renderBulkCloseItems}
dynamicTabTitleMode={dynamicTabTitleMode}
tabDoubleClickBehavior={tabDoubleClickBehavior}
t={t}
tabAnimationClass={getTabAnimationClass(session.id)}
shortcutNumber={tabShortcutNumbers?.get(session.id)}
@@ -1320,6 +1323,7 @@ export const topTabsAreEqual = (prev: TopTabsProps, next: TopTabsProps): boolean
prev.showHostTreeSidebar === next.showHostTreeSidebar &&
prev.switchTabKeyBinding === next.switchTabKeyBinding &&
prev.dynamicTabTitleMode === next.dynamicTabTitleMode &&
prev.tabDoubleClickBehavior === next.tabDoubleClickBehavior &&
prev.hostById === next.hostById
);
};
@@ -18,6 +18,12 @@ const dynamicTabTitleModeOptions = (
}
).DYNAMIC_TAB_TITLE_MODE_OPTIONS;
const tabDoubleClickBehaviorOptions = (
terminalBehaviorSettings as {
TAB_DOUBLE_CLICK_BEHAVIOR_OPTIONS?: Array<{ value: string; labelKey: string }>;
}
).TAB_DOUBLE_CLICK_BEHAVIOR_OPTIONS;
test("middle-click settings expose only supported behaviors", () => {
assert.ok(Array.isArray(middleClickBehaviorOptions));
assert.deepEqual(
@@ -34,6 +40,15 @@ test("dynamic tab title settings expose off, agent-only, and all modes", () => {
);
});
test("tab double-click settings expose duplicate, copy, and disabled modes", () => {
assert.ok(Array.isArray(tabDoubleClickBehaviorOptions));
assert.deepEqual(
tabDoubleClickBehaviorOptions.map((option) => option.value),
["duplicate", "copy", "disabled"],
);
assert.match(source, /updateTerminalSetting\("tabDoubleClickBehavior", v as TerminalTabDoubleClickBehavior\)/);
});
test("terminal behavior settings expose word separator editing", () => {
assert.match(source, /settings\.terminal\.behavior\.wordSeparators/);
assert.match(source, /terminalSettings\.wordSeparators/);
+1 -1
View File
@@ -112,7 +112,7 @@ function getSavedManagedAgentPathInfo(
return {
path: command,
binPath: command,
version: null,
version: managed.cliVersion ?? null,
available: savedAvailable,
installed: true,
authenticated: undefined,
@@ -1,6 +1,6 @@
import React from "react";
import { DEFAULT_TERMINAL_WORD_SEPARATORS } from "../../../domain/models";
import type { DisconnectedNoticeMode, DynamicTabTitleMode, LinkModifier, MiddleClickBehavior, OscNotificationMode, RightClickBehavior, TerminalSettings } from "../../../domain/models";
import type { DisconnectedNoticeMode, DynamicTabTitleMode, LinkModifier, MiddleClickBehavior, OscNotificationMode, RightClickBehavior, TerminalSettings, TerminalTabDoubleClickBehavior } from "../../../domain/models";
import { Input } from "../../ui/input";
import { Label } from "../../ui/label";
import { SectionHeader, Select, SettingsAnchor, SettingRow, Toggle } from "../settings-ui";
@@ -31,6 +31,15 @@ export const DYNAMIC_TAB_TITLE_MODE_OPTIONS: Array<{
{ value: "all", labelKey: "settings.terminal.behavior.dynamicTabTitle.all" },
];
export const TAB_DOUBLE_CLICK_BEHAVIOR_OPTIONS: Array<{
value: TerminalTabDoubleClickBehavior;
labelKey: string;
}> = [
{ value: "duplicate", labelKey: "settings.terminal.behavior.tabDoubleClick.duplicate" },
{ value: "copy", labelKey: "settings.terminal.behavior.tabDoubleClick.copy" },
{ value: "disabled", labelKey: "settings.terminal.behavior.tabDoubleClick.disabled" },
];
export const TerminalBehaviorSettings: React.FC<TerminalBehaviorSettingsProps> = ({
t,
terminalSettings,
@@ -214,6 +223,22 @@ export const TerminalBehaviorSettings: React.FC<TerminalBehaviorSettingsProps> =
<Toggle checked={terminalSettings.forcePromptNewLine ?? false} onChange={(v) => updateTerminalSetting("forcePromptNewLine", v)} />
</SettingRow>
<SettingRow
anchorId="terminal-tab-double-click"
label={t("settings.terminal.behavior.tabDoubleClick")}
description={t("settings.terminal.behavior.tabDoubleClick.desc")}
>
<Select
value={terminalSettings.tabDoubleClickBehavior}
options={TAB_DOUBLE_CLICK_BEHAVIOR_OPTIONS.map((option) => ({
value: option.value,
label: t(option.labelKey),
}))}
onChange={(v) => updateTerminalSetting("tabDoubleClickBehavior", v as TerminalTabDoubleClickBehavior)}
className="w-44"
/>
</SettingRow>
<SettingRow
anchorId="terminal-dynamic-tab-title"
label={t("settings.terminal.behavior.dynamicTabTitle")}
+41 -2
View File
@@ -4,7 +4,10 @@ import { useI18n } from "../../../../application/i18n/I18nProvider";
import { Button } from "../../../ui/button";
import { cn } from "../../../../lib/utils";
import type { AgentPathInfo } from "./types";
import type { CodebuddyAdvancedOptions } from "../../../../infrastructure/ai/types";
import {
isCodebuddySessionPersistenceSupported,
type CodebuddyAdvancedOptions,
} from "../../../../infrastructure/ai/types";
import { parseEnvLines, serializeEnvLines } from "./codebuddyConfigEnv";
const INTERNET_ENV_OPTIONS = [
@@ -15,10 +18,12 @@ const INTERNET_ENV_OPTIONS = [
const EFFORT_OPTIONS = [
{ value: "", labelKey: "ai.codebuddy.effort.default" },
{ value: "minimal", labelKey: "ai.codebuddy.effort.minimal" },
{ value: "low", labelKey: "ai.codebuddy.effort.low" },
{ value: "medium", labelKey: "ai.codebuddy.effort.medium" },
{ value: "high", labelKey: "ai.codebuddy.effort.high" },
{ value: "xhigh", labelKey: "ai.codebuddy.effort.xhigh" },
{ value: "max", labelKey: "ai.codebuddy.effort.max" },
] as const;
export const CodebuddyCard: React.FC<{
@@ -50,6 +55,13 @@ export const CodebuddyCard: React.FC<{
}) => {
const { t } = useI18n();
const found = pathInfo?.available;
const persistSessionSupported = Boolean(
found && isCodebuddySessionPersistenceSupported(pathInfo?.version),
);
// Unsupported/unknown CLIs keep their own default (persistence enabled),
// even if a stale false value remains in saved settings.
const persistSessionEnabled = !persistSessionSupported
|| advancedOptions?.persistSession !== false;
// Collapsed by default; auto-expand when the user already has config so it
// isn't hidden. Local UI state — not persisted.
const [configOpen, setConfigOpen] = useState(
@@ -193,7 +205,7 @@ export const CodebuddyCard: React.FC<{
)}
</div>
{/* Advanced SDK options (SDK 0.3.230) */}
{/* Advanced SDK options (SDK 0.3.258) */}
{onAdvancedOptionsChange && (
<div className="border-t border-border/60 pt-3">
<button
@@ -279,6 +291,33 @@ export const CodebuddyCard: React.FC<{
)} />
</button>
</div>
{/* Session Persistence */}
<div className="flex items-center justify-between gap-2">
<div>
<span className="text-xs text-muted-foreground">{t('ai.codebuddy.persistSession')}</span>
<p className="text-[11px] text-muted-foreground leading-4">{t('ai.codebuddy.persistSession.hint')}</p>
</div>
<button
type="button"
role="switch"
disabled={!persistSessionSupported}
aria-checked={persistSessionEnabled}
onClick={() => {
if (!persistSessionSupported) return;
updateAdvanced({ persistSession: advancedOptions?.persistSession === false ? undefined : false });
}}
className={cn(
"relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full transition-colors",
!persistSessionSupported && "cursor-not-allowed opacity-50",
persistSessionEnabled ? "bg-primary" : "bg-muted",
)}
>
<span className={cn(
"inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
persistSessionEnabled ? "translate-x-[18px]" : "translate-x-[3px]",
)} />
</button>
</div>
{/* File Checkpointing */}
<div className="flex items-center justify-between gap-2">
<div>
+55 -4
View File
@@ -1,6 +1,9 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { JSDOM } from "jsdom";
import { TreeNode } from "./SftpPaneTreeNode.tsx";
import { getSftpTreeEntryOpenAction } from "./SftpPaneTreeView.tsx";
import type { SftpFileEntry } from "../../types";
@@ -18,7 +21,55 @@ test("tree activation enters directories and keeps file opening separate", () =>
assert.equal(getSftpTreeEntryOpenAction(entry("notes.txt", "file")), "open");
});
test("tree row double click routes through the open action", () => {
const source = fs.readFileSync(new URL("./SftpPaneTreeNode.tsx", import.meta.url), "utf8");
assert.match(source, /onDoubleClick=\{\(\) => onOpenEntry\(entry, entryPath\)\}/);
test("tree double clicks preserve folder, parent, file and arrow actions", async () => {
const dom = new JSDOM('<div id="root"></div>');
const keys = ["window", "document", "IS_REACT_ACT_ENVIRONMENT"] as const;
const originals = keys.map(key => Object.getOwnPropertyDescriptor(globalThis, key));
Object.defineProperty(globalThis, "window", { configurable: true, value: dom.window });
Object.defineProperty(globalThis, "document", { configurable: true, value: dom.window.document });
Object.defineProperty(globalThis, "IS_REACT_ACT_ENVIRONMENT", { configurable: true, value: true });
const container = dom.window.document.getElementById("root")!;
const root = createRoot(container);
const calls: string[] = [];
const noop = () => {};
const render = async (item: SftpFileEntry, isLoading = false) => {
await act(async () => root.render(React.createElement(TreeNode, {
entry: item, entryPath: `/root/${item.name}`, depth: 0,
columnTemplate: "1fr", visibleColumns: { name: true, modified: false, size: false, type: false, owner: false },
isSelected: false, isExpanded: false, isLoading, isDragOver: false,
onToggleExpand: () => calls.push("toggle"), onNodeClick: () => calls.push("select"),
onOpenEntry: () => calls.push("open"), onContextMenu: () => calls.push("menu"),
onDragStart: noop, onDragEnd: noop, onDragOverEntry: noop, onDropEntry: noop, onDragLeaveEntry: noop,
})));
calls.length = 0;
};
const doubleClick = async (target: Element) => {
for (const [type, detail] of [["click", 1], ["click", 2], ["dblclick", 2]] as const) {
await act(async () => { target.dispatchEvent(new dom.window.MouseEvent(type, { bubbles: true, detail })); });
}
};
try {
await render(entry("docs", "directory"));
await doubleClick(container.querySelector('[data-entry-name="docs"]')!);
assert.deepEqual(calls, ["select", "select", "toggle"]);
calls.length = 0;
await doubleClick(container.querySelector('.lucide-chevron-right')!);
assert.deepEqual(calls, ["toggle", "toggle"]);
await render(entry("docs", "directory"), true);
await doubleClick(container.querySelector('.lucide-loader-circle, .lucide-loader-2')!);
assert.deepEqual(calls, ["select", "select"]);
for (const item of [entry("..", "directory"), entry("notes.txt", "file")]) {
await render(item);
await doubleClick(container.querySelector('[data-section]')!);
assert.deepEqual(calls, ["select", "select", "open"]);
}
} finally {
await act(async () => root.unmount());
dom.window.close();
keys.forEach((key, index) => {
const descriptor = originals[index];
if (descriptor) Object.defineProperty(globalThis, key, descriptor);
else Reflect.deleteProperty(globalThis, key);
});
}
});
+9 -2
View File
@@ -67,7 +67,11 @@ export const TreeNode = React.memo<TreeNodeProps>(({
)}
style={{ gridTemplateColumns: columnTemplate, height: TREE_ROW_HEIGHT }}
onClick={e => onNodeClick(entry, entryPath, e)}
onDoubleClick={() => onOpenEntry(entry, entryPath)}
onDoubleClick={() => {
if (isParentEntry) { onOpenEntry(entry, entryPath); return; }
if (isDir) void onToggleExpand(entry, entryPath);
else onOpenEntry(entry, entryPath);
}}
onContextMenu={e => {
if (!isParentEntry) {
onContextMenu(entry, entryPath, e);
@@ -84,7 +88,10 @@ export const TreeNode = React.memo<TreeNodeProps>(({
className="flex min-w-0 items-center gap-1"
style={{ paddingLeft: depth * 16 + 8 }}
>
<span className="shrink-0 w-4 flex items-center justify-center">
<span
className="shrink-0 w-4 flex items-center justify-center"
onDoubleClick={e => { if (!isParentEntry && isDir) e.stopPropagation(); }}
>
{isParentEntry ? (
<CornerUpLeft size={14} className="text-muted-foreground" />
) : isDir ? (
+32 -6
View File
@@ -20,7 +20,7 @@ import { resolveSessionCodingCliProvider } from '../../domain/codingCliProviderM
import type { CodingCliProvider } from '../../domain/codingCliProviders';
import { resolveCodingCliActivityPhase, type CodingCliActivityPhase } from '../../domain/codingCliTitleParse';
import { resolveSessionTabTitle, resolveWorkspaceTabLabel } from '../../domain/sessionTabTitle';
import type { DynamicTabTitleMode } from '../../domain/models';
import type { DynamicTabTitleMode, TerminalTabDoubleClickBehavior } from '../../domain/models';
import { CodingCliProviderIcon } from '../icons/CodingCliProviderIcon';
import { cn } from '../../lib/utils';
import { Host, TerminalSession, Workspace } from '../../types';
@@ -242,9 +242,28 @@ export const formatSessionTopTabLabel = (
};
export const createTopTabCopyDoubleClickHandler = (
onCopySession: (sessionId: string) => void,
sessionId: string,
): React.MouseEventHandler<HTMLDivElement> => () => onCopySession(sessionId);
onCopy: (id: string) => void,
id: string,
): React.MouseEventHandler<HTMLDivElement> => () => onCopy(id);
export const createTopTabSessionDoubleClickHandler = ({
behavior,
onCopySession,
onDuplicateSession,
sessionId,
}: {
behavior: TerminalTabDoubleClickBehavior;
onCopySession: (sessionId: string) => void;
onDuplicateSession?: (sessionId: string) => void;
sessionId: string;
}): React.MouseEventHandler<HTMLDivElement> => () => {
if (behavior === 'disabled') return;
if (behavior === 'duplicate') {
(onDuplicateSession ?? onCopySession)(sessionId);
return;
}
onCopySession(sessionId);
};
export const stopCloseButtonDoubleClickPropagation = (
event: Pick<React.MouseEvent, 'stopPropagation'>,
@@ -713,6 +732,7 @@ interface SessionTopTabProps {
onEditHost?: (host: Host) => void;
renderBulkCloseItems: RenderBulkCloseItems;
dynamicTabTitleMode?: DynamicTabTitleMode;
tabDoubleClickBehavior: TerminalTabDoubleClickBehavior;
t: TranslateFn;
tabAnimationClass?: string;
shortcutNumber?: number;
@@ -739,6 +759,7 @@ export const SessionTopTab: React.FC<SessionTopTabProps> = memo(({
onEditHost,
renderBulkCloseItems,
dynamicTabTitleMode,
tabDoubleClickBehavior,
t,
tabAnimationClass,
shortcutNumber,
@@ -757,8 +778,13 @@ export const SessionTopTab: React.FC<SessionTopTabProps> = memo(({
activeTabStore.setActiveTabId(session.id);
}, [session.id]);
const handleDoubleClick = useMemo(
() => createTopTabCopyDoubleClickHandler(onCopySession, session.id),
[onCopySession, session.id],
() => createTopTabSessionDoubleClickHandler({
behavior: tabDoubleClickBehavior,
onCopySession,
onDuplicateSession,
sessionId: session.id,
}),
[tabDoubleClickBehavior, onCopySession, onDuplicateSession, session.id],
);
const addressTooltip = formatSessionTopTabTooltip(session);
const tabTitle = formatSessionTopTabLabel(session, dynamicTabTitleMode);
+12 -12
View File
@@ -29,20 +29,20 @@
<text x="85" y="85.0" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="end">6K</text>
<text x="43" y="271.5" fill="#e6edf3" font-size="17" font-family="Handlee, cursive" font-weight="bold" transform="rotate(-90, 43, 271.5)">GitHub Stars</text>
<text x="90.0" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Jan 2026</text>
<text x="169.6" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Feb 2026</text>
<text x="246.4" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Mar 2026</text>
<text x="331.5" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Apr 2026</text>
<text x="413.8" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">May 2026</text>
<text x="498.8" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Jun 2026</text>
<text x="581.2" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Jul 2026</text>
<text x="666.2" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Aug 2026</text>
<text x="751.3" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Sep 2026</text>
<text x="167.4" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Feb 2026</text>
<text x="242.1" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Mar 2026</text>
<text x="324.8" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Apr 2026</text>
<text x="404.8" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">May 2026</text>
<text x="487.5" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Jun 2026</text>
<text x="567.6" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Jul 2026</text>
<text x="650.3" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Aug 2026</text>
<text x="733.0" y="488" fill="#e6edf3" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Sep 2026</text>
<text x="50%" y="525" fill="#e6edf3" font-size="17" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Date</text>
<line x1="90" y1="80" x2="90" y2="463" stroke="#30363d" stroke-width="2.5" filter="url(#xkcdify)"/>
<line x1="90" y1="463" x2="765" y2="463" stroke="#30363d" stroke-width="2.5" filter="url(#xkcdify)"/>
<path d="M 90.0,462.0 C 90.5,462.0 92.3,461.6 92.7,461.5 C 93.7,461.4 94.6,461.1 95.5,461.0 C 96.4,460.8 97.3,460.5 98.2,460.4 C 99.1,460.3 100.1,460.4 101.0,460.3 C 101.9,460.3 102.8,460.2 103.7,460.2 C 104.6,460.1 105.5,460.1 106.5,460.0 C 107.4,459.9 107.8,459.9 109.2,459.8 C 110.6,459.7 113.3,459.7 114.7,459.6 C 116.1,459.6 116.5,459.5 117.4,459.5 C 118.4,459.5 119.3,459.4 120.2,459.4 C 121.1,459.4 122.0,459.4 122.9,459.4 C 123.8,459.3 124.8,459.3 125.7,459.3 C 126.6,459.3 127.5,459.2 128.4,459.1 C 129.3,459.0 130.2,459.0 131.2,458.9 C 132.1,458.7 133.0,458.4 133.9,458.0 C 134.8,457.5 135.7,456.7 136.6,456.4 C 137.6,456.1 138.5,456.2 139.4,456.2 C 140.3,456.1 141.2,456.1 142.1,456.0 C 143.0,456.0 143.5,455.9 144.9,455.9 C 146.2,455.9 148.5,455.9 150.4,455.9 C 152.2,455.8 154.5,455.8 155.9,455.7 C 157.2,455.7 157.7,455.6 158.6,455.6 C 159.5,455.6 160.4,455.5 161.3,455.5 C 162.3,455.4 163.2,455.4 164.1,455.3 C 165.0,455.3 165.9,455.3 166.8,455.3 C 167.7,455.3 168.2,455.2 169.6,455.2 C 170.9,455.2 172.8,455.1 175.1,455.1 C 177.3,455.1 181.0,455.0 183.3,455.0 C 185.6,455.0 186.0,455.0 188.8,455.0 C 191.5,454.9 197.5,454.8 199.8,454.7 C 202.0,454.6 201.1,454.7 202.5,454.6 C 203.9,454.6 205.7,454.6 208.0,454.6 C 210.3,454.5 211.6,454.5 216.2,454.4 C 220.8,454.4 231.8,454.3 235.4,454.3 C 239.1,454.2 237.3,454.2 238.2,454.2 C 239.1,454.2 239.5,454.9 240.9,454.1 C 242.3,453.3 245.0,452.6 246.4,449.3 C 247.8,445.9 248.2,436.9 249.1,434.1 C 250.1,431.3 251.0,432.9 251.9,432.5 C 252.8,432.0 253.7,431.8 254.6,431.5 C 255.5,431.2 256.5,430.9 257.4,430.8 C 258.3,430.6 259.2,430.5 260.1,430.4 C 261.0,430.3 262.0,430.1 262.9,430.0 C 263.8,429.9 264.7,430.0 265.6,429.9 C 266.5,429.8 267.4,429.6 268.4,429.5 C 269.3,429.4 270.2,429.4 271.1,429.3 C 272.0,429.1 272.9,428.7 273.8,428.6 C 274.8,428.4 275.7,428.5 276.6,428.4 C 277.5,428.3 278.4,428.0 279.3,428.0 C 280.2,427.9 281.2,427.9 282.1,427.9 C 283.0,427.8 283.9,427.8 284.8,427.7 C 285.7,427.6 286.6,427.6 287.6,427.5 C 288.5,427.4 289.4,427.4 290.3,427.3 C 291.2,427.2 292.1,426.9 293.0,426.7 C 294.0,426.6 294.9,427.4 295.8,426.3 C 296.7,425.2 297.6,421.5 298.5,420.3 C 299.5,419.1 300.4,419.2 301.3,419.0 C 302.2,418.8 303.1,419.1 304.0,419.0 C 304.9,418.8 305.9,418.4 306.8,418.1 C 307.7,417.7 308.6,417.5 309.5,416.9 C 310.4,416.3 311.3,415.2 312.3,414.5 C 313.2,413.8 314.1,413.1 315.0,412.6 C 315.9,412.1 316.8,411.7 317.7,411.4 C 318.7,411.1 319.6,410.9 320.5,410.8 C 321.4,410.6 322.3,410.7 323.2,410.5 C 324.1,410.4 325.1,410.1 326.0,410.0 C 326.9,409.9 327.8,410.0 328.7,409.9 C 329.6,409.8 330.5,409.8 331.5,409.6 C 332.4,409.5 333.3,409.5 334.2,409.1 C 335.1,408.6 336.0,407.3 337.0,406.8 C 337.9,406.2 338.8,406.0 339.7,405.6 C 340.6,405.1 341.5,406.0 342.4,404.1 C 343.4,402.3 344.3,396.7 345.2,394.6 C 346.1,392.4 347.0,392.1 347.9,391.2 C 348.8,390.3 349.8,389.7 350.7,389.1 C 351.6,388.4 352.5,387.8 353.4,387.3 C 354.3,386.8 355.2,386.4 356.2,386.1 C 357.1,385.8 358.0,385.7 358.9,385.4 C 359.8,385.2 360.7,384.9 361.6,384.5 C 362.6,384.2 363.5,383.6 364.4,383.3 C 365.3,383.0 366.2,383.1 367.1,382.8 C 368.0,382.5 369.0,381.9 369.9,381.5 C 370.8,381.2 371.7,380.9 372.6,380.6 C 373.5,380.3 374.5,380.1 375.4,379.9 C 376.3,379.7 377.2,379.7 378.1,379.5 C 379.0,379.3 379.9,379.1 380.9,378.9 C 381.8,378.7 382.7,378.5 383.6,378.3 C 384.5,378.0 385.4,377.8 386.3,377.5 C 387.3,377.1 388.2,376.4 389.1,376.1 C 390.0,375.7 390.9,375.6 391.8,375.4 C 392.7,375.2 393.7,375.1 394.6,375.0 C 395.5,374.8 396.4,374.7 397.3,374.6 C 398.2,374.5 399.1,374.3 400.1,374.2 C 401.0,374.1 401.9,374.2 402.8,374.0 C 403.7,373.9 404.6,373.6 405.5,373.3 C 406.5,373.0 407.4,372.4 408.3,372.0 C 409.2,371.6 410.1,371.2 411.0,371.0 C 412.0,370.7 412.9,370.7 413.8,370.5 C 414.7,370.3 415.6,370.3 416.5,370.0 C 417.4,369.7 418.4,369.1 419.3,368.8 C 420.2,368.6 421.1,368.7 422.0,368.5 C 422.9,368.4 423.8,368.1 424.8,367.9 C 425.7,367.7 426.6,367.5 427.5,367.2 C 428.4,367.0 429.3,366.8 430.2,366.5 C 431.2,366.3 432.1,366.2 433.0,366.0 C 433.9,365.7 434.8,365.3 435.7,365.0 C 436.6,364.7 437.6,364.6 438.5,364.3 C 439.4,364.0 440.3,363.7 441.2,363.4 C 442.1,363.0 443.0,362.7 444.0,362.4 C 444.9,362.1 445.8,362.0 446.7,361.6 C 447.6,361.1 448.5,360.0 449.5,359.6 C 450.4,359.2 451.3,359.1 452.2,359.0 C 453.1,358.8 454.0,358.8 454.9,358.7 C 455.9,358.6 456.8,358.4 457.7,358.2 C 458.6,358.1 459.5,358.4 460.4,357.9 C 461.3,357.5 462.3,356.2 463.2,355.7 C 464.1,355.2 465.0,355.3 465.9,355.0 C 466.8,354.7 467.7,354.1 468.7,353.9 C 469.6,353.7 470.5,353.8 471.4,353.6 C 472.3,353.4 473.2,352.7 474.1,352.5 C 475.1,352.3 476.0,352.3 476.9,352.2 C 477.8,352.1 478.7,352.1 479.6,351.8 C 480.5,351.5 481.5,351.2 482.4,350.6 C 483.3,350.0 484.2,348.8 485.1,348.2 C 486.0,347.5 487.0,347.3 487.9,346.8 C 488.8,346.4 489.7,346.3 490.6,345.4 C 491.5,344.6 492.4,342.6 493.4,341.7 C 494.3,340.7 495.2,340.3 496.1,339.7 C 497.0,339.2 497.9,339.1 498.8,338.2 C 499.8,337.4 500.7,335.5 501.6,334.6 C 502.5,333.8 503.4,333.7 504.3,333.2 C 505.2Line truncated
<path d="M 90.0,462.0 C 90.5,462.0 92.3,461.6 92.7,461.5 C 93.7,461.4 94.6,461.1 95.5,461.0 C 96.4,460.8 97.3,460.5 98.2,460.4 C 99.1,460.3 100.1,460.4 101.0,460.3 C 101.9,460.3 102.8,460.2 103.7,460.2 C 104.6,460.1 105.5,460.1 106.5,460.0 C 107.4,459.9 107.8,459.9 109.2,459.8 C 110.6,459.7 113.3,459.7 114.7,459.6 C 116.1,459.6 116.5,459.5 117.4,459.5 C 118.4,459.5 119.3,459.4 120.2,459.4 C 121.1,459.4 122.0,459.4 122.9,459.4 C 123.8,459.3 124.8,459.3 125.7,459.3 C 126.6,459.3 127.5,459.2 128.4,459.1 C 129.3,459.0 130.2,459.0 131.2,458.9 C 132.1,458.7 133.0,458.4 133.9,458.0 C 134.8,457.5 135.7,456.7 136.6,456.4 C 137.6,456.1 138.5,456.2 139.4,456.2 C 140.3,456.1 141.2,456.1 142.1,456.0 C 143.0,456.0 143.5,455.9 144.9,455.9 C 146.2,455.9 148.5,455.9 150.4,455.9 C 152.2,455.8 154.5,455.8 155.9,455.7 C 157.2,455.7 157.7,455.6 158.6,455.6 C 159.5,455.6 160.4,455.5 161.3,455.5 C 162.3,455.4 163.2,455.4 164.1,455.3 C 165.0,455.3 165.9,455.3 166.8,455.3 C 167.7,455.3 168.2,455.2 169.6,455.2 C 170.9,455.2 172.8,455.1 175.1,455.1 C 177.3,455.1 181.0,455.0 183.3,455.0 C 185.6,455.0 186.0,455.0 188.8,455.0 C 191.5,454.9 197.5,454.8 199.8,454.7 C 202.0,454.6 201.1,454.7 202.5,454.6 C 203.9,454.6 205.7,454.6 208.0,454.6 C 210.3,454.5 211.6,454.5 216.2,454.4 C 220.8,454.4 231.8,454.3 235.4,454.3 C 239.1,454.2 237.3,454.2 238.2,454.2 C 239.1,454.2 239.5,454.9 240.9,454.1 C 242.3,453.3 245.0,452.6 246.4,449.3 C 247.8,445.9 248.2,436.9 249.1,434.1 C 250.1,431.3 251.0,432.9 251.9,432.5 C 252.8,432.0 253.7,431.8 254.6,431.5 C 255.5,431.2 256.5,430.9 257.4,430.8 C 258.3,430.6 259.2,430.5 260.1,430.4 C 261.0,430.3 262.0,430.1 262.9,430.0 C 263.8,429.9 264.7,430.0 265.6,429.9 C 266.5,429.8 267.4,429.6 268.4,429.5 C 269.3,429.4 270.2,429.4 271.1,429.3 C 272.0,429.1 272.9,428.7 273.8,428.6 C 274.8,428.4 275.7,428.5 276.6,428.4 C 277.5,428.3 278.4,428.0 279.3,428.0 C 280.2,427.9 281.2,427.9 282.1,427.9 C 283.0,427.8 283.9,427.8 284.8,427.7 C 285.7,427.6 286.6,427.6 287.6,427.5 C 288.5,427.4 289.4,427.4 290.3,427.3 C 291.2,427.2 292.1,426.9 293.0,426.7 C 294.0,426.6 294.9,427.4 295.8,426.3 C 296.7,425.2 297.6,421.5 298.5,420.3 C 299.5,419.1 300.4,419.2 301.3,419.0 C 302.2,418.8 303.1,419.1 304.0,419.0 C 304.9,418.8 305.9,418.4 306.8,418.1 C 307.7,417.7 308.6,417.5 309.5,416.9 C 310.4,416.3 311.3,415.2 312.3,414.5 C 313.2,413.8 314.1,413.1 315.0,412.6 C 315.9,412.1 316.8,411.7 317.7,411.4 C 318.7,411.1 319.6,410.9 320.5,410.8 C 321.4,410.6 322.3,410.7 323.2,410.5 C 324.1,410.4 325.1,410.1 326.0,410.0 C 326.9,409.9 327.8,410.0 328.7,409.9 C 329.6,409.8 330.5,409.8 331.5,409.6 C 332.4,409.5 333.3,409.5 334.2,409.1 C 335.1,408.6 336.0,407.3 337.0,406.8 C 337.9,406.2 338.8,406.0 339.7,405.6 C 340.6,405.1 341.5,406.0 342.4,404.1 C 343.4,402.3 344.3,396.7 345.2,394.6 C 346.1,392.4 347.0,392.1 347.9,391.2 C 348.8,390.3 349.8,389.7 350.7,389.1 C 351.6,388.4 352.5,387.8 353.4,387.3 C 354.3,386.8 355.2,386.4 356.2,386.1 C 357.1,385.8 358.0,385.7 358.9,385.4 C 359.8,385.2 360.7,384.9 361.6,384.5 C 362.6,384.2 363.5,383.6 364.4,383.3 C 365.3,383.0 366.2,383.1 367.1,382.8 C 368.0,382.5 369.0,381.9 369.9,381.5 C 370.8,381.2 371.7,380.9 372.6,380.6 C 373.5,380.3 374.5,380.1 375.4,379.9 C 376.3,379.7 377.2,379.7 378.1,379.5 C 379.0,379.3 379.9,379.1 380.9,378.9 C 381.8,378.7 382.7,378.5 383.6,378.3 C 384.5,378.0 385.4,377.8 386.3,377.5 C 387.3,377.1 388.2,376.4 389.1,376.1 C 390.0,375.7 390.9,375.6 391.8,375.4 C 392.7,375.2 393.7,375.1 394.6,375.0 C 395.5,374.8 396.4,374.7 397.3,374.6 C 398.2,374.5 399.1,374.3 400.1,374.2 C 401.0,374.1 401.9,374.2 402.8,374.0 C 403.7,373.9 404.6,373.6 405.5,373.3 C 406.5,373.0 407.4,372.4 408.3,372.0 C 409.2,371.6 410.1,371.2 411.0,371.0 C 412.0,370.7 412.9,370.7 413.8,370.5 C 414.7,370.3 415.6,370.3 416.5,370.0 C 417.4,369.7 418.4,369.1 419.3,368.8 C 420.2,368.6 421.1,368.7 422.0,368.5 C 422.9,368.4 423.8,368.1 424.8,367.9 C 425.7,367.7 426.6,367.5 427.5,367.2 C 428.4,367.0 429.3,366.8 430.2,366.5 C 431.2,366.3 432.1,366.2 433.0,366.0 C 433.9,365.7 434.8,365.3 435.7,365.0 C 436.6,364.7 437.6,364.6 438.5,364.3 C 439.4,364.0 440.3,363.7 441.2,363.4 C 442.1,363.0 443.0,362.7 444.0,362.4 C 444.9,362.1 445.8,362.0 446.7,361.6 C 447.6,361.1 448.5,360.0 449.5,359.6 C 450.4,359.2 451.3,359.1 452.2,359.0 C 453.1,358.8 454.0,358.8 454.9,358.7 C 455.9,358.6 456.8,358.4 457.7,358.2 C 458.6,358.1 459.5,358.4 460.4,357.9 C 461.3,357.5 462.3,356.2 463.2,355.7 C 464.1,355.2 465.0,355.3 465.9,355.0 C 466.8,354.7 467.7,354.1 468.7,353.9 C 469.6,353.7 470.5,353.8 471.4,353.6 C 472.3,353.4 473.2,352.7 474.1,352.5 C 475.1,352.3 476.0,352.3 476.9,352.2 C 477.8,352.1 478.7,352.1 479.6,351.8 C 480.5,351.5 481.5,351.2 482.4,350.6 C 483.3,350.0 484.2,348.8 485.1,348.2 C 486.0,347.5 487.0,347.3 487.9,346.8 C 488.8,346.4 489.7,346.3 490.6,345.4 C 491.5,344.6 492.4,342.6 493.4,341.7 C 494.3,340.7 495.2,340.3 496.1,339.7 C 497.0,339.2 497.9,339.1 498.8,338.2 C 499.8,337.4 500.7,335.5 501.6,334.6 C 502.5,333.8 503.4,333.7 504.3,333.2 C 505.2Line truncated
<circle cx="765.0" cy="100.2" r="5" fill="#dd4528" stroke="#0d1117" stroke-width="2" filter="url(#xkcdify)"/>
<text x="753.0" y="77.0" fill="#dd4528" font-size="18" font-family="Handlee, cursive" font-weight="bold" text-anchor="end">5683</text>
<path d="M 90.0,462.0 C 90.4,462.0 92.2,461.6 92.7,461.5 C 93.6,461.4 94.4,461.1 95.3,461.0 C 96.2,460.8 97.1,460.5 98.0,460.4 C 98.9,460.3 99.8,460.4 100.7,460.3 C 101.6,460.3 102.5,460.2 103.3,460.2 C 104.2,460.1 105.1,460.1 106.0,460.0 C 106.9,459.9 107.3,459.9 108.7,459.8 C 110.0,459.7 112.7,459.7 114.0,459.6 C 115.3,459.6 115.8,459.5 116.7,459.5 C 117.6,459.5 118.5,459.4 119.3,459.4 C 120.2,459.4 121.1,459.4 122.0,459.4 C 122.9,459.3 123.8,459.3 124.7,459.3 C 125.6,459.3 126.5,459.2 127.4,459.1 C 128.2,459.0 129.1,459.0 130.0,458.9 C 130.9,458.7 131.8,458.4 132.7,458.0 C 133.6,457.5 134.5,456.7 135.4,456.4 C 136.2,456.1 137.1,456.2 138.0,456.2 C 138.9,456.1 139.8,456.1 140.7,456.0 C 141.6,456.0 142.0,455.9 143.4,455.9 C 144.7,455.9 146.9,455.9 148.7,455.9 C 150.5,455.8 152.7,455.8 154.0,455.7 C 155.4,455.7 155.8,455.6 156.7,455.6 C 157.6,455.6 158.5,455.5 159.4,455.5 C 160.3,455.4 161.1,455.4 162.0,455.3 C 162.9,455.3 163.8,455.3 164.7,455.3 C 165.6,455.3 166.0,455.2 167.4,455.2 C 168.7,455.2 170.5,455.1 172.7,455.1 C 174.9,455.1 178.5,455.0 180.7,455.0 C 182.9,455.0 183.4,455.0 186.0,455.0 C 188.7,454.9 194.5,454.8 196.7,454.7 C 198.9,454.6 198.1,454.7 199.4,454.6 C 200.7,454.6 202.5,454.6 204.7,454.6 C 206.9,454.5 208.3,454.5 212.7,454.4 C 217.2,454.4 227.8,454.3 231.4,454.3 C 235.0,454.2 233.2,454.2 234.1,454.2 C 235.0,454.2 235.4,454.9 236.7,454.1 C 238.1,453.3 240.7,452.6 242.1,449.3 C 243.4,445.9 243.9,436.9 244.7,434.1 C 245.6,431.3 246.5,432.9 247.4,432.5 C 248.3,432.0 249.2,431.8 250.1,431.5 C 251.0,431.2 251.9,430.9 252.7,430.8 C 253.6,430.6 254.5,430.5 255.4,430.4 C 256.3,430.3 257.2,430.1 258.1,430.0 C 259.0,429.9 259.9,430.0 260.8,429.9 C 261.6,429.8 262.5,429.6 263.4,429.5 C 264.3,429.4 265.2,429.4 266.1,429.3 C 267.0,429.1 267.9,428.7 268.8,428.6 C 269.6,428.4 270.5,428.5 271.4,428.4 C 272.3,428.3 273.2,428.0 274.1,428.0 C 275.0,427.9 275.9,427.9 276.8,427.9 C 277.6,427.8 278.5,427.8 279.4,427.7 C 280.3,427.6 281.2,427.6 282.1,427.5 C 283.0,427.4 283.9,427.4 284.8,427.3 C 285.7,427.2 286.5,426.9 287.4,426.7 C 288.3,426.6 289.2,427.4 290.1,426.3 C 291.0,425.2 291.9,421.5 292.8,420.3 C 293.7,419.1 294.5,419.2 295.4,419.0 C 296.3,418.8 297.2,419.1 298.1,419.0 C 299.0,418.8 299.9,418.4 300.8,418.1 C 301.7,417.7 302.5,417.5 303.4,416.9 C 304.3,416.3 305.2,415.2 306.1,414.5 C 307.0,413.8 307.9,413.1 308.8,412.6 C 309.7,412.1 310.6,411.7 311.4,411.4 C 312.3,411.1 313.2,410.9 314.1,410.8 C 315.0,410.6 315.9,410.7 316.8,410.5 C 317.7,410.4 318.6,410.1 319.4,410.0 C 320.3,409.9 321.2,410.0 322.1,409.9 C 323.0,409.8 323.9,409.8 324.8,409.6 C 325.7,409.5 326.6,409.5 327.5,409.1 C 328.3,408.6 329.2,407.3 330.1,406.8 C 331.0,406.2 331.9,406.0 332.8,405.6 C 333.7,405.1 334.6,406.0 335.5,404.1 C 336.3,402.3 337.2,396.7 338.1,394.6 C 339.0,392.4 339.9,392.1 340.8,391.2 C 341.7,390.3 342.6,389.7 343.5,389.1 C 344.3,388.4 345.2,387.8 346.1,387.3 C 347.0,386.8 347.9,386.4 348.8,386.1 C 349.7,385.8 350.6,385.7 351.5,385.4 C 352.4,385.2 353.2,384.9 354.1,384.5 C 355.0,384.2 355.9,383.6 356.8,383.3 C 357.7,383.0 358.6,383.1 359.5,382.8 C 360.4,382.5 361.2,381.9 362.1,381.5 C 363.0,381.2 363.9,380.9 364.8,380.6 C 365.7,380.3 366.6,380.1 367.5,379.9 C 368.4,379.7 369.2,379.7 370.1,379.5 C 371.0,379.3 371.9,379.1 372.8,378.9 C 373.7,378.7 374.6,378.5 375.5,378.3 C 376.4,378.0 377.3,377.8 378.1,377.5 C 379.0,377.1 379.9,376.4 380.8,376.1 C 381.7,375.7 382.6,375.6 383.5,375.4 C 384.4,375.2 385.3,375.1 386.1,375.0 C 387.0,374.8 387.9,374.7 388.8,374.6 C 389.7,374.5 390.6,374.3 391.5,374.2 C 392.4,374.1 393.3,374.2 394.2,374.0 C 395.0,373.9 395.9,373.6 396.8,373.3 C 397.7,373.0 398.6,372.4 399.5,372.0 C 400.4,371.6 401.3,371.2 402.2,371.0 C 403.0,370.7 403.9,370.7 404.8,370.5 C 405.7,370.3 406.6,370.3 407.5,370.0 C 408.4,369.7 409.3,369.1 410.2,368.8 C 411.0,368.6 411.9,368.7 412.8,368.5 C 413.7,368.4 414.6,368.1 415.5,367.9 C 416.4,367.7 417.3,367.5 418.2,367.2 C 419.1,367.0 419.9,366.8 420.8,366.5 C 421.7,366.3 422.6,366.2 423.5,366.0 C 424.4,365.7 425.3,365.3 426.2,365.0 C 427.1,364.7 427.9,364.6 428.8,364.3 C 429.7,364.0 430.6,363.7 431.5,363.4 C 432.4,363.0 433.3,362.7 434.2,362.4 C 435.1,362.1 435.9,362.0 436.8,361.6 C 437.7,361.1 438.6,360.0 439.5,359.6 C 440.4,359.2 441.3,359.1 442.2,359.0 C 443.1,358.8 444.0,358.8 444.8,358.7 C 445.7,358.6 446.6,358.4 447.5,358.2 C 448.4,358.1 449.3,358.4 450.2,357.9 C 451.1,357.5 452.0,356.2 452.8,355.7 C 453.7,355.2 454.6,355.3 455.5,355.0 C 456.4,354.7 457.3,354.1 458.2,353.9 C 459.1,353.7 460.0,353.8 460.8,353.6 C 461.7,353.4 462.6,352.7 463.5,352.5 C 464.4,352.3 465.3,352.3 466.2,352.2 C 467.1,352.1 468.0,352.1 468.9,351.8 C 469.7,351.5 470.6,351.2 471.5,350.6 C 472.4,350.0 473.3,348.8 474.2,348.2 C 475.1,347.5 476.0,347.3 476.9,346.8 C 477.7,346.4 478.6,346.3 479.5,345.4 C 480.4,344.6 481.3,342.6 482.2,341.7 C 483.1,340.7 484.0,340.3 484.9,339.7 C 485.8,339.2 486.6,339.1 487.5,338.2 C 488.4,337.4 489.3,335.5 490.2,334.6 C 491.1,333.8 492.0,333.7 492.9,333.2 C 493.8,Line truncated
<path d="M 90.0,462.0 C 90.4,462.0 92.2,461.6 92.7,461.5 C 93.6,461.4 94.4,461.1 95.3,461.0 C 96.2,460.8 97.1,460.5 98.0,460.4 C 98.9,460.3 99.8,460.4 100.7,460.3 C 101.6,460.3 102.5,460.2 103.3,460.2 C 104.2,460.1 105.1,460.1 106.0,460.0 C 106.9,459.9 107.3,459.9 108.7,459.8 C 110.0,459.7 112.7,459.7 114.0,459.6 C 115.3,459.6 115.8,459.5 116.7,459.5 C 117.6,459.5 118.5,459.4 119.3,459.4 C 120.2,459.4 121.1,459.4 122.0,459.4 C 122.9,459.3 123.8,459.3 124.7,459.3 C 125.6,459.3 126.5,459.2 127.4,459.1 C 128.2,459.0 129.1,459.0 130.0,458.9 C 130.9,458.7 131.8,458.4 132.7,458.0 C 133.6,457.5 134.5,456.7 135.4,456.4 C 136.2,456.1 137.1,456.2 138.0,456.2 C 138.9,456.1 139.8,456.1 140.7,456.0 C 141.6,456.0 142.0,455.9 143.4,455.9 C 144.7,455.9 146.9,455.9 148.7,455.9 C 150.5,455.8 152.7,455.8 154.0,455.7 C 155.4,455.7 155.8,455.6 156.7,455.6 C 157.6,455.6 158.5,455.5 159.4,455.5 C 160.3,455.4 161.1,455.4 162.0,455.3 C 162.9,455.3 163.8,455.3 164.7,455.3 C 165.6,455.3 166.0,455.2 167.4,455.2 C 168.7,455.2 170.5,455.1 172.7,455.1 C 174.9,455.1 178.5,455.0 180.7,455.0 C 182.9,455.0 183.4,455.0 186.0,455.0 C 188.7,454.9 194.5,454.8 196.7,454.7 C 198.9,454.6 198.1,454.7 199.4,454.6 C 200.7,454.6 202.5,454.6 204.7,454.6 C 206.9,454.5 208.3,454.5 212.7,454.4 C 217.2,454.4 227.8,454.3 231.4,454.3 C 235.0,454.2 233.2,454.2 234.1,454.2 C 235.0,454.2 235.4,454.9 236.7,454.1 C 238.1,453.3 240.7,452.6 242.1,449.3 C 243.4,445.9 243.9,436.9 244.7,434.1 C 245.6,431.3 246.5,432.9 247.4,432.5 C 248.3,432.0 249.2,431.8 250.1,431.5 C 251.0,431.2 251.9,430.9 252.7,430.8 C 253.6,430.6 254.5,430.5 255.4,430.4 C 256.3,430.3 257.2,430.1 258.1,430.0 C 259.0,429.9 259.9,430.0 260.8,429.9 C 261.6,429.8 262.5,429.6 263.4,429.5 C 264.3,429.4 265.2,429.4 266.1,429.3 C 267.0,429.1 267.9,428.7 268.8,428.6 C 269.6,428.4 270.5,428.5 271.4,428.4 C 272.3,428.3 273.2,428.0 274.1,428.0 C 275.0,427.9 275.9,427.9 276.8,427.9 C 277.6,427.8 278.5,427.8 279.4,427.7 C 280.3,427.6 281.2,427.6 282.1,427.5 C 283.0,427.4 283.9,427.4 284.8,427.3 C 285.7,427.2 286.5,426.9 287.4,426.7 C 288.3,426.6 289.2,427.4 290.1,426.3 C 291.0,425.2 291.9,421.5 292.8,420.3 C 293.7,419.1 294.5,419.2 295.4,419.0 C 296.3,418.8 297.2,419.1 298.1,419.0 C 299.0,418.8 299.9,418.4 300.8,418.1 C 301.7,417.7 302.5,417.5 303.4,416.9 C 304.3,416.3 305.2,415.2 306.1,414.5 C 307.0,413.8 307.9,413.1 308.8,412.6 C 309.7,412.1 310.6,411.7 311.4,411.4 C 312.3,411.1 313.2,410.9 314.1,410.8 C 315.0,410.6 315.9,410.7 316.8,410.5 C 317.7,410.4 318.6,410.1 319.4,410.0 C 320.3,409.9 321.2,410.0 322.1,409.9 C 323.0,409.8 323.9,409.8 324.8,409.6 C 325.7,409.5 326.6,409.5 327.5,409.1 C 328.3,408.6 329.2,407.3 330.1,406.8 C 331.0,406.2 331.9,406.0 332.8,405.6 C 333.7,405.1 334.6,406.0 335.5,404.1 C 336.3,402.3 337.2,396.7 338.1,394.6 C 339.0,392.4 339.9,392.1 340.8,391.2 C 341.7,390.3 342.6,389.7 343.5,389.1 C 344.3,388.4 345.2,387.8 346.1,387.3 C 347.0,386.8 347.9,386.4 348.8,386.1 C 349.7,385.8 350.6,385.7 351.5,385.4 C 352.4,385.2 353.2,384.9 354.1,384.5 C 355.0,384.2 355.9,383.6 356.8,383.3 C 357.7,383.0 358.6,383.1 359.5,382.8 C 360.4,382.5 361.2,381.9 362.1,381.5 C 363.0,381.2 363.9,380.9 364.8,380.6 C 365.7,380.3 366.6,380.1 367.5,379.9 C 368.4,379.7 369.2,379.7 370.1,379.5 C 371.0,379.3 371.9,379.1 372.8,378.9 C 373.7,378.7 374.6,378.5 375.5,378.3 C 376.4,378.0 377.3,377.8 378.1,377.5 C 379.0,377.1 379.9,376.4 380.8,376.1 C 381.7,375.7 382.6,375.6 383.5,375.4 C 384.4,375.2 385.3,375.1 386.1,375.0 C 387.0,374.8 387.9,374.7 388.8,374.6 C 389.7,374.5 390.6,374.3 391.5,374.2 C 392.4,374.1 393.3,374.2 394.2,374.0 C 395.0,373.9 395.9,373.6 396.8,373.3 C 397.7,373.0 398.6,372.4 399.5,372.0 C 400.4,371.6 401.3,371.2 402.2,371.0 C 403.0,370.7 403.9,370.7 404.8,370.5 C 405.7,370.3 406.6,370.3 407.5,370.0 C 408.4,369.7 409.3,369.1 410.2,368.8 C 411.0,368.6 411.9,368.7 412.8,368.5 C 413.7,368.4 414.6,368.1 415.5,367.9 C 416.4,367.7 417.3,367.5 418.2,367.2 C 419.1,367.0 419.9,366.8 420.8,366.5 C 421.7,366.3 422.6,366.2 423.5,366.0 C 424.4,365.7 425.3,365.3 426.2,365.0 C 427.1,364.7 427.9,364.6 428.8,364.3 C 429.7,364.0 430.6,363.7 431.5,363.4 C 432.4,363.0 433.3,362.7 434.2,362.4 C 435.1,362.1 435.9,362.0 436.8,361.6 C 437.7,361.1 438.6,360.0 439.5,359.6 C 440.4,359.2 441.3,359.1 442.2,359.0 C 443.1,358.8 444.0,358.8 444.8,358.7 C 445.7,358.6 446.6,358.4 447.5,358.2 C 448.4,358.1 449.3,358.4 450.2,357.9 C 451.1,357.5 452.0,356.2 452.8,355.7 C 453.7,355.2 454.6,355.3 455.5,355.0 C 456.4,354.7 457.3,354.1 458.2,353.9 C 459.1,353.7 460.0,353.8 460.8,353.6 C 461.7,353.4 462.6,352.7 463.5,352.5 C 464.4,352.3 465.3,352.3 466.2,352.2 C 467.1,352.1 468.0,352.1 468.9,351.8 C 469.7,351.5 470.6,351.2 471.5,350.6 C 472.4,350.0 473.3,348.8 474.2,348.2 C 475.1,347.5 476.0,347.3 476.9,346.8 C 477.7,346.4 478.6,346.3 479.5,345.4 C 480.4,344.6 481.3,342.6 482.2,341.7 C 483.1,340.7 484.0,340.3 484.9,339.7 C 485.8,339.2 486.6,339.1 487.5,338.2 C 488.4,337.4 489.3,335.5 490.2,334.6 C 491.1,333.8 492.0,333.7 492.9,333.2 C 493.8,Line truncated
<circle cx="765.0" cy="85.3" r="5" fill="#dd4528" stroke="#0d1117" stroke-width="2" filter="url(#xkcdify)"/>
<text x="753.0" y="77.0" fill="#dd4528" font-size="18" font-family="Handlee, cursive" font-weight="bold" text-anchor="end">5917</text>
<a href="https://github.com/carsteneu/mystarhistory" target="_blank" rel="noopener"><text x="763" y="527" font-size="13" font-family="Handlee, cursive" fill="#fff" text-anchor="end">Made with carsteneu/mystarhistory</text></a>
</svg>

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

+12 -12
View File
@@ -29,20 +29,20 @@
<text x="85" y="85.0" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="end">6K</text>
<text x="43" y="271.5" fill="#000" font-size="17" font-family="Handlee, cursive" font-weight="bold" transform="rotate(-90, 43, 271.5)">GitHub Stars</text>
<text x="90.0" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Jan 2026</text>
<text x="169.6" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Feb 2026</text>
<text x="246.4" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Mar 2026</text>
<text x="331.5" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Apr 2026</text>
<text x="413.8" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">May 2026</text>
<text x="498.8" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Jun 2026</text>
<text x="581.2" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Jul 2026</text>
<text x="666.2" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Aug 2026</text>
<text x="751.3" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Sep 2026</text>
<text x="167.4" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Feb 2026</text>
<text x="242.1" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Mar 2026</text>
<text x="324.8" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Apr 2026</text>
<text x="404.8" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">May 2026</text>
<text x="487.5" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Jun 2026</text>
<text x="567.6" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Jul 2026</text>
<text x="650.3" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Aug 2026</text>
<text x="733.0" y="488" fill="#000" font-size="16" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Sep 2026</text>
<text x="50%" y="525" fill="#000" font-size="17" font-family="Handlee, cursive" font-weight="bold" text-anchor="middle">Date</text>
<line x1="90" y1="80" x2="90" y2="463" stroke="#222" stroke-width="2.5" filter="url(#xkcdify)"/>
<line x1="90" y1="463" x2="765" y2="463" stroke="#222" stroke-width="2.5" filter="url(#xkcdify)"/>
<path d="M 90.0,462.0 C 90.5,462.0 92.3,461.6 92.7,461.5 C 93.7,461.4 94.6,461.1 95.5,461.0 C 96.4,460.8 97.3,460.5 98.2,460.4 C 99.1,460.3 100.1,460.4 101.0,460.3 C 101.9,460.3 102.8,460.2 103.7,460.2 C 104.6,460.1 105.5,460.1 106.5,460.0 C 107.4,459.9 107.8,459.9 109.2,459.8 C 110.6,459.7 113.3,459.7 114.7,459.6 C 116.1,459.6 116.5,459.5 117.4,459.5 C 118.4,459.5 119.3,459.4 120.2,459.4 C 121.1,459.4 122.0,459.4 122.9,459.4 C 123.8,459.3 124.8,459.3 125.7,459.3 C 126.6,459.3 127.5,459.2 128.4,459.1 C 129.3,459.0 130.2,459.0 131.2,458.9 C 132.1,458.7 133.0,458.4 133.9,458.0 C 134.8,457.5 135.7,456.7 136.6,456.4 C 137.6,456.1 138.5,456.2 139.4,456.2 C 140.3,456.1 141.2,456.1 142.1,456.0 C 143.0,456.0 143.5,455.9 144.9,455.9 C 146.2,455.9 148.5,455.9 150.4,455.9 C 152.2,455.8 154.5,455.8 155.9,455.7 C 157.2,455.7 157.7,455.6 158.6,455.6 C 159.5,455.6 160.4,455.5 161.3,455.5 C 162.3,455.4 163.2,455.4 164.1,455.3 C 165.0,455.3 165.9,455.3 166.8,455.3 C 167.7,455.3 168.2,455.2 169.6,455.2 C 170.9,455.2 172.8,455.1 175.1,455.1 C 177.3,455.1 181.0,455.0 183.3,455.0 C 185.6,455.0 186.0,455.0 188.8,455.0 C 191.5,454.9 197.5,454.8 199.8,454.7 C 202.0,454.6 201.1,454.7 202.5,454.6 C 203.9,454.6 205.7,454.6 208.0,454.6 C 210.3,454.5 211.6,454.5 216.2,454.4 C 220.8,454.4 231.8,454.3 235.4,454.3 C 239.1,454.2 237.3,454.2 238.2,454.2 C 239.1,454.2 239.5,454.9 240.9,454.1 C 242.3,453.3 245.0,452.6 246.4,449.3 C 247.8,445.9 248.2,436.9 249.1,434.1 C 250.1,431.3 251.0,432.9 251.9,432.5 C 252.8,432.0 253.7,431.8 254.6,431.5 C 255.5,431.2 256.5,430.9 257.4,430.8 C 258.3,430.6 259.2,430.5 260.1,430.4 C 261.0,430.3 262.0,430.1 262.9,430.0 C 263.8,429.9 264.7,430.0 265.6,429.9 C 266.5,429.8 267.4,429.6 268.4,429.5 C 269.3,429.4 270.2,429.4 271.1,429.3 C 272.0,429.1 272.9,428.7 273.8,428.6 C 274.8,428.4 275.7,428.5 276.6,428.4 C 277.5,428.3 278.4,428.0 279.3,428.0 C 280.2,427.9 281.2,427.9 282.1,427.9 C 283.0,427.8 283.9,427.8 284.8,427.7 C 285.7,427.6 286.6,427.6 287.6,427.5 C 288.5,427.4 289.4,427.4 290.3,427.3 C 291.2,427.2 292.1,426.9 293.0,426.7 C 294.0,426.6 294.9,427.4 295.8,426.3 C 296.7,425.2 297.6,421.5 298.5,420.3 C 299.5,419.1 300.4,419.2 301.3,419.0 C 302.2,418.8 303.1,419.1 304.0,419.0 C 304.9,418.8 305.9,418.4 306.8,418.1 C 307.7,417.7 308.6,417.5 309.5,416.9 C 310.4,416.3 311.3,415.2 312.3,414.5 C 313.2,413.8 314.1,413.1 315.0,412.6 C 315.9,412.1 316.8,411.7 317.7,411.4 C 318.7,411.1 319.6,410.9 320.5,410.8 C 321.4,410.6 322.3,410.7 323.2,410.5 C 324.1,410.4 325.1,410.1 326.0,410.0 C 326.9,409.9 327.8,410.0 328.7,409.9 C 329.6,409.8 330.5,409.8 331.5,409.6 C 332.4,409.5 333.3,409.5 334.2,409.1 C 335.1,408.6 336.0,407.3 337.0,406.8 C 337.9,406.2 338.8,406.0 339.7,405.6 C 340.6,405.1 341.5,406.0 342.4,404.1 C 343.4,402.3 344.3,396.7 345.2,394.6 C 346.1,392.4 347.0,392.1 347.9,391.2 C 348.8,390.3 349.8,389.7 350.7,389.1 C 351.6,388.4 352.5,387.8 353.4,387.3 C 354.3,386.8 355.2,386.4 356.2,386.1 C 357.1,385.8 358.0,385.7 358.9,385.4 C 359.8,385.2 360.7,384.9 361.6,384.5 C 362.6,384.2 363.5,383.6 364.4,383.3 C 365.3,383.0 366.2,383.1 367.1,382.8 C 368.0,382.5 369.0,381.9 369.9,381.5 C 370.8,381.2 371.7,380.9 372.6,380.6 C 373.5,380.3 374.5,380.1 375.4,379.9 C 376.3,379.7 377.2,379.7 378.1,379.5 C 379.0,379.3 379.9,379.1 380.9,378.9 C 381.8,378.7 382.7,378.5 383.6,378.3 C 384.5,378.0 385.4,377.8 386.3,377.5 C 387.3,377.1 388.2,376.4 389.1,376.1 C 390.0,375.7 390.9,375.6 391.8,375.4 C 392.7,375.2 393.7,375.1 394.6,375.0 C 395.5,374.8 396.4,374.7 397.3,374.6 C 398.2,374.5 399.1,374.3 400.1,374.2 C 401.0,374.1 401.9,374.2 402.8,374.0 C 403.7,373.9 404.6,373.6 405.5,373.3 C 406.5,373.0 407.4,372.4 408.3,372.0 C 409.2,371.6 410.1,371.2 411.0,371.0 C 412.0,370.7 412.9,370.7 413.8,370.5 C 414.7,370.3 415.6,370.3 416.5,370.0 C 417.4,369.7 418.4,369.1 419.3,368.8 C 420.2,368.6 421.1,368.7 422.0,368.5 C 422.9,368.4 423.8,368.1 424.8,367.9 C 425.7,367.7 426.6,367.5 427.5,367.2 C 428.4,367.0 429.3,366.8 430.2,366.5 C 431.2,366.3 432.1,366.2 433.0,366.0 C 433.9,365.7 434.8,365.3 435.7,365.0 C 436.6,364.7 437.6,364.6 438.5,364.3 C 439.4,364.0 440.3,363.7 441.2,363.4 C 442.1,363.0 443.0,362.7 444.0,362.4 C 444.9,362.1 445.8,362.0 446.7,361.6 C 447.6,361.1 448.5,360.0 449.5,359.6 C 450.4,359.2 451.3,359.1 452.2,359.0 C 453.1,358.8 454.0,358.8 454.9,358.7 C 455.9,358.6 456.8,358.4 457.7,358.2 C 458.6,358.1 459.5,358.4 460.4,357.9 C 461.3,357.5 462.3,356.2 463.2,355.7 C 464.1,355.2 465.0,355.3 465.9,355.0 C 466.8,354.7 467.7,354.1 468.7,353.9 C 469.6,353.7 470.5,353.8 471.4,353.6 C 472.3,353.4 473.2,352.7 474.1,352.5 C 475.1,352.3 476.0,352.3 476.9,352.2 C 477.8,352.1 478.7,352.1 479.6,351.8 C 480.5,351.5 481.5,351.2 482.4,350.6 C 483.3,350.0 484.2,348.8 485.1,348.2 C 486.0,347.5 487.0,347.3 487.9,346.8 C 488.8,346.4 489.7,346.3 490.6,345.4 C 491.5,344.6 492.4,342.6 493.4,341.7 C 494.3,340.7 495.2,340.3 496.1,339.7 C 497.0,339.2 497.9,339.1 498.8,338.2 C 499.8,337.4 500.7,335.5 501.6,334.6 C 502.5,333.8 503.4,333.7 504.3,333.2 C 505.2Line truncated
<path d="M 90.0,462.0 C 90.5,462.0 92.3,461.6 92.7,461.5 C 93.7,461.4 94.6,461.1 95.5,461.0 C 96.4,460.8 97.3,460.5 98.2,460.4 C 99.1,460.3 100.1,460.4 101.0,460.3 C 101.9,460.3 102.8,460.2 103.7,460.2 C 104.6,460.1 105.5,460.1 106.5,460.0 C 107.4,459.9 107.8,459.9 109.2,459.8 C 110.6,459.7 113.3,459.7 114.7,459.6 C 116.1,459.6 116.5,459.5 117.4,459.5 C 118.4,459.5 119.3,459.4 120.2,459.4 C 121.1,459.4 122.0,459.4 122.9,459.4 C 123.8,459.3 124.8,459.3 125.7,459.3 C 126.6,459.3 127.5,459.2 128.4,459.1 C 129.3,459.0 130.2,459.0 131.2,458.9 C 132.1,458.7 133.0,458.4 133.9,458.0 C 134.8,457.5 135.7,456.7 136.6,456.4 C 137.6,456.1 138.5,456.2 139.4,456.2 C 140.3,456.1 141.2,456.1 142.1,456.0 C 143.0,456.0 143.5,455.9 144.9,455.9 C 146.2,455.9 148.5,455.9 150.4,455.9 C 152.2,455.8 154.5,455.8 155.9,455.7 C 157.2,455.7 157.7,455.6 158.6,455.6 C 159.5,455.6 160.4,455.5 161.3,455.5 C 162.3,455.4 163.2,455.4 164.1,455.3 C 165.0,455.3 165.9,455.3 166.8,455.3 C 167.7,455.3 168.2,455.2 169.6,455.2 C 170.9,455.2 172.8,455.1 175.1,455.1 C 177.3,455.1 181.0,455.0 183.3,455.0 C 185.6,455.0 186.0,455.0 188.8,455.0 C 191.5,454.9 197.5,454.8 199.8,454.7 C 202.0,454.6 201.1,454.7 202.5,454.6 C 203.9,454.6 205.7,454.6 208.0,454.6 C 210.3,454.5 211.6,454.5 216.2,454.4 C 220.8,454.4 231.8,454.3 235.4,454.3 C 239.1,454.2 237.3,454.2 238.2,454.2 C 239.1,454.2 239.5,454.9 240.9,454.1 C 242.3,453.3 245.0,452.6 246.4,449.3 C 247.8,445.9 248.2,436.9 249.1,434.1 C 250.1,431.3 251.0,432.9 251.9,432.5 C 252.8,432.0 253.7,431.8 254.6,431.5 C 255.5,431.2 256.5,430.9 257.4,430.8 C 258.3,430.6 259.2,430.5 260.1,430.4 C 261.0,430.3 262.0,430.1 262.9,430.0 C 263.8,429.9 264.7,430.0 265.6,429.9 C 266.5,429.8 267.4,429.6 268.4,429.5 C 269.3,429.4 270.2,429.4 271.1,429.3 C 272.0,429.1 272.9,428.7 273.8,428.6 C 274.8,428.4 275.7,428.5 276.6,428.4 C 277.5,428.3 278.4,428.0 279.3,428.0 C 280.2,427.9 281.2,427.9 282.1,427.9 C 283.0,427.8 283.9,427.8 284.8,427.7 C 285.7,427.6 286.6,427.6 287.6,427.5 C 288.5,427.4 289.4,427.4 290.3,427.3 C 291.2,427.2 292.1,426.9 293.0,426.7 C 294.0,426.6 294.9,427.4 295.8,426.3 C 296.7,425.2 297.6,421.5 298.5,420.3 C 299.5,419.1 300.4,419.2 301.3,419.0 C 302.2,418.8 303.1,419.1 304.0,419.0 C 304.9,418.8 305.9,418.4 306.8,418.1 C 307.7,417.7 308.6,417.5 309.5,416.9 C 310.4,416.3 311.3,415.2 312.3,414.5 C 313.2,413.8 314.1,413.1 315.0,412.6 C 315.9,412.1 316.8,411.7 317.7,411.4 C 318.7,411.1 319.6,410.9 320.5,410.8 C 321.4,410.6 322.3,410.7 323.2,410.5 C 324.1,410.4 325.1,410.1 326.0,410.0 C 326.9,409.9 327.8,410.0 328.7,409.9 C 329.6,409.8 330.5,409.8 331.5,409.6 C 332.4,409.5 333.3,409.5 334.2,409.1 C 335.1,408.6 336.0,407.3 337.0,406.8 C 337.9,406.2 338.8,406.0 339.7,405.6 C 340.6,405.1 341.5,406.0 342.4,404.1 C 343.4,402.3 344.3,396.7 345.2,394.6 C 346.1,392.4 347.0,392.1 347.9,391.2 C 348.8,390.3 349.8,389.7 350.7,389.1 C 351.6,388.4 352.5,387.8 353.4,387.3 C 354.3,386.8 355.2,386.4 356.2,386.1 C 357.1,385.8 358.0,385.7 358.9,385.4 C 359.8,385.2 360.7,384.9 361.6,384.5 C 362.6,384.2 363.5,383.6 364.4,383.3 C 365.3,383.0 366.2,383.1 367.1,382.8 C 368.0,382.5 369.0,381.9 369.9,381.5 C 370.8,381.2 371.7,380.9 372.6,380.6 C 373.5,380.3 374.5,380.1 375.4,379.9 C 376.3,379.7 377.2,379.7 378.1,379.5 C 379.0,379.3 379.9,379.1 380.9,378.9 C 381.8,378.7 382.7,378.5 383.6,378.3 C 384.5,378.0 385.4,377.8 386.3,377.5 C 387.3,377.1 388.2,376.4 389.1,376.1 C 390.0,375.7 390.9,375.6 391.8,375.4 C 392.7,375.2 393.7,375.1 394.6,375.0 C 395.5,374.8 396.4,374.7 397.3,374.6 C 398.2,374.5 399.1,374.3 400.1,374.2 C 401.0,374.1 401.9,374.2 402.8,374.0 C 403.7,373.9 404.6,373.6 405.5,373.3 C 406.5,373.0 407.4,372.4 408.3,372.0 C 409.2,371.6 410.1,371.2 411.0,371.0 C 412.0,370.7 412.9,370.7 413.8,370.5 C 414.7,370.3 415.6,370.3 416.5,370.0 C 417.4,369.7 418.4,369.1 419.3,368.8 C 420.2,368.6 421.1,368.7 422.0,368.5 C 422.9,368.4 423.8,368.1 424.8,367.9 C 425.7,367.7 426.6,367.5 427.5,367.2 C 428.4,367.0 429.3,366.8 430.2,366.5 C 431.2,366.3 432.1,366.2 433.0,366.0 C 433.9,365.7 434.8,365.3 435.7,365.0 C 436.6,364.7 437.6,364.6 438.5,364.3 C 439.4,364.0 440.3,363.7 441.2,363.4 C 442.1,363.0 443.0,362.7 444.0,362.4 C 444.9,362.1 445.8,362.0 446.7,361.6 C 447.6,361.1 448.5,360.0 449.5,359.6 C 450.4,359.2 451.3,359.1 452.2,359.0 C 453.1,358.8 454.0,358.8 454.9,358.7 C 455.9,358.6 456.8,358.4 457.7,358.2 C 458.6,358.1 459.5,358.4 460.4,357.9 C 461.3,357.5 462.3,356.2 463.2,355.7 C 464.1,355.2 465.0,355.3 465.9,355.0 C 466.8,354.7 467.7,354.1 468.7,353.9 C 469.6,353.7 470.5,353.8 471.4,353.6 C 472.3,353.4 473.2,352.7 474.1,352.5 C 475.1,352.3 476.0,352.3 476.9,352.2 C 477.8,352.1 478.7,352.1 479.6,351.8 C 480.5,351.5 481.5,351.2 482.4,350.6 C 483.3,350.0 484.2,348.8 485.1,348.2 C 486.0,347.5 487.0,347.3 487.9,346.8 C 488.8,346.4 489.7,346.3 490.6,345.4 C 491.5,344.6 492.4,342.6 493.4,341.7 C 494.3,340.7 495.2,340.3 496.1,339.7 C 497.0,339.2 497.9,339.1 498.8,338.2 C 499.8,337.4 500.7,335.5 501.6,334.6 C 502.5,333.8 503.4,333.7 504.3,333.2 C 505.2Line truncated
<circle cx="765.0" cy="100.2" r="5" fill="#dd4528" stroke="#fff" stroke-width="2" filter="url(#xkcdify)"/>
<text x="753.0" y="77.0" fill="#dd4528" font-size="18" font-family="Handlee, cursive" font-weight="bold" text-anchor="end">5683</text>
<path d="M 90.0,462.0 C 90.4,462.0 92.2,461.6 92.7,461.5 C 93.6,461.4 94.4,461.1 95.3,461.0 C 96.2,460.8 97.1,460.5 98.0,460.4 C 98.9,460.3 99.8,460.4 100.7,460.3 C 101.6,460.3 102.5,460.2 103.3,460.2 C 104.2,460.1 105.1,460.1 106.0,460.0 C 106.9,459.9 107.3,459.9 108.7,459.8 C 110.0,459.7 112.7,459.7 114.0,459.6 C 115.3,459.6 115.8,459.5 116.7,459.5 C 117.6,459.5 118.5,459.4 119.3,459.4 C 120.2,459.4 121.1,459.4 122.0,459.4 C 122.9,459.3 123.8,459.3 124.7,459.3 C 125.6,459.3 126.5,459.2 127.4,459.1 C 128.2,459.0 129.1,459.0 130.0,458.9 C 130.9,458.7 131.8,458.4 132.7,458.0 C 133.6,457.5 134.5,456.7 135.4,456.4 C 136.2,456.1 137.1,456.2 138.0,456.2 C 138.9,456.1 139.8,456.1 140.7,456.0 C 141.6,456.0 142.0,455.9 143.4,455.9 C 144.7,455.9 146.9,455.9 148.7,455.9 C 150.5,455.8 152.7,455.8 154.0,455.7 C 155.4,455.7 155.8,455.6 156.7,455.6 C 157.6,455.6 158.5,455.5 159.4,455.5 C 160.3,455.4 161.1,455.4 162.0,455.3 C 162.9,455.3 163.8,455.3 164.7,455.3 C 165.6,455.3 166.0,455.2 167.4,455.2 C 168.7,455.2 170.5,455.1 172.7,455.1 C 174.9,455.1 178.5,455.0 180.7,455.0 C 182.9,455.0 183.4,455.0 186.0,455.0 C 188.7,454.9 194.5,454.8 196.7,454.7 C 198.9,454.6 198.1,454.7 199.4,454.6 C 200.7,454.6 202.5,454.6 204.7,454.6 C 206.9,454.5 208.3,454.5 212.7,454.4 C 217.2,454.4 227.8,454.3 231.4,454.3 C 235.0,454.2 233.2,454.2 234.1,454.2 C 235.0,454.2 235.4,454.9 236.7,454.1 C 238.1,453.3 240.7,452.6 242.1,449.3 C 243.4,445.9 243.9,436.9 244.7,434.1 C 245.6,431.3 246.5,432.9 247.4,432.5 C 248.3,432.0 249.2,431.8 250.1,431.5 C 251.0,431.2 251.9,430.9 252.7,430.8 C 253.6,430.6 254.5,430.5 255.4,430.4 C 256.3,430.3 257.2,430.1 258.1,430.0 C 259.0,429.9 259.9,430.0 260.8,429.9 C 261.6,429.8 262.5,429.6 263.4,429.5 C 264.3,429.4 265.2,429.4 266.1,429.3 C 267.0,429.1 267.9,428.7 268.8,428.6 C 269.6,428.4 270.5,428.5 271.4,428.4 C 272.3,428.3 273.2,428.0 274.1,428.0 C 275.0,427.9 275.9,427.9 276.8,427.9 C 277.6,427.8 278.5,427.8 279.4,427.7 C 280.3,427.6 281.2,427.6 282.1,427.5 C 283.0,427.4 283.9,427.4 284.8,427.3 C 285.7,427.2 286.5,426.9 287.4,426.7 C 288.3,426.6 289.2,427.4 290.1,426.3 C 291.0,425.2 291.9,421.5 292.8,420.3 C 293.7,419.1 294.5,419.2 295.4,419.0 C 296.3,418.8 297.2,419.1 298.1,419.0 C 299.0,418.8 299.9,418.4 300.8,418.1 C 301.7,417.7 302.5,417.5 303.4,416.9 C 304.3,416.3 305.2,415.2 306.1,414.5 C 307.0,413.8 307.9,413.1 308.8,412.6 C 309.7,412.1 310.6,411.7 311.4,411.4 C 312.3,411.1 313.2,410.9 314.1,410.8 C 315.0,410.6 315.9,410.7 316.8,410.5 C 317.7,410.4 318.6,410.1 319.4,410.0 C 320.3,409.9 321.2,410.0 322.1,409.9 C 323.0,409.8 323.9,409.8 324.8,409.6 C 325.7,409.5 326.6,409.5 327.5,409.1 C 328.3,408.6 329.2,407.3 330.1,406.8 C 331.0,406.2 331.9,406.0 332.8,405.6 C 333.7,405.1 334.6,406.0 335.5,404.1 C 336.3,402.3 337.2,396.7 338.1,394.6 C 339.0,392.4 339.9,392.1 340.8,391.2 C 341.7,390.3 342.6,389.7 343.5,389.1 C 344.3,388.4 345.2,387.8 346.1,387.3 C 347.0,386.8 347.9,386.4 348.8,386.1 C 349.7,385.8 350.6,385.7 351.5,385.4 C 352.4,385.2 353.2,384.9 354.1,384.5 C 355.0,384.2 355.9,383.6 356.8,383.3 C 357.7,383.0 358.6,383.1 359.5,382.8 C 360.4,382.5 361.2,381.9 362.1,381.5 C 363.0,381.2 363.9,380.9 364.8,380.6 C 365.7,380.3 366.6,380.1 367.5,379.9 C 368.4,379.7 369.2,379.7 370.1,379.5 C 371.0,379.3 371.9,379.1 372.8,378.9 C 373.7,378.7 374.6,378.5 375.5,378.3 C 376.4,378.0 377.3,377.8 378.1,377.5 C 379.0,377.1 379.9,376.4 380.8,376.1 C 381.7,375.7 382.6,375.6 383.5,375.4 C 384.4,375.2 385.3,375.1 386.1,375.0 C 387.0,374.8 387.9,374.7 388.8,374.6 C 389.7,374.5 390.6,374.3 391.5,374.2 C 392.4,374.1 393.3,374.2 394.2,374.0 C 395.0,373.9 395.9,373.6 396.8,373.3 C 397.7,373.0 398.6,372.4 399.5,372.0 C 400.4,371.6 401.3,371.2 402.2,371.0 C 403.0,370.7 403.9,370.7 404.8,370.5 C 405.7,370.3 406.6,370.3 407.5,370.0 C 408.4,369.7 409.3,369.1 410.2,368.8 C 411.0,368.6 411.9,368.7 412.8,368.5 C 413.7,368.4 414.6,368.1 415.5,367.9 C 416.4,367.7 417.3,367.5 418.2,367.2 C 419.1,367.0 419.9,366.8 420.8,366.5 C 421.7,366.3 422.6,366.2 423.5,366.0 C 424.4,365.7 425.3,365.3 426.2,365.0 C 427.1,364.7 427.9,364.6 428.8,364.3 C 429.7,364.0 430.6,363.7 431.5,363.4 C 432.4,363.0 433.3,362.7 434.2,362.4 C 435.1,362.1 435.9,362.0 436.8,361.6 C 437.7,361.1 438.6,360.0 439.5,359.6 C 440.4,359.2 441.3,359.1 442.2,359.0 C 443.1,358.8 444.0,358.8 444.8,358.7 C 445.7,358.6 446.6,358.4 447.5,358.2 C 448.4,358.1 449.3,358.4 450.2,357.9 C 451.1,357.5 452.0,356.2 452.8,355.7 C 453.7,355.2 454.6,355.3 455.5,355.0 C 456.4,354.7 457.3,354.1 458.2,353.9 C 459.1,353.7 460.0,353.8 460.8,353.6 C 461.7,353.4 462.6,352.7 463.5,352.5 C 464.4,352.3 465.3,352.3 466.2,352.2 C 467.1,352.1 468.0,352.1 468.9,351.8 C 469.7,351.5 470.6,351.2 471.5,350.6 C 472.4,350.0 473.3,348.8 474.2,348.2 C 475.1,347.5 476.0,347.3 476.9,346.8 C 477.7,346.4 478.6,346.3 479.5,345.4 C 480.4,344.6 481.3,342.6 482.2,341.7 C 483.1,340.7 484.0,340.3 484.9,339.7 C 485.8,339.2 486.6,339.1 487.5,338.2 C 488.4,337.4 489.3,335.5 490.2,334.6 C 491.1,333.8 492.0,333.7 492.9,333.2 C 493.8,Line truncated
<path d="M 90.0,462.0 C 90.4,462.0 92.2,461.6 92.7,461.5 C 93.6,461.4 94.4,461.1 95.3,461.0 C 96.2,460.8 97.1,460.5 98.0,460.4 C 98.9,460.3 99.8,460.4 100.7,460.3 C 101.6,460.3 102.5,460.2 103.3,460.2 C 104.2,460.1 105.1,460.1 106.0,460.0 C 106.9,459.9 107.3,459.9 108.7,459.8 C 110.0,459.7 112.7,459.7 114.0,459.6 C 115.3,459.6 115.8,459.5 116.7,459.5 C 117.6,459.5 118.5,459.4 119.3,459.4 C 120.2,459.4 121.1,459.4 122.0,459.4 C 122.9,459.3 123.8,459.3 124.7,459.3 C 125.6,459.3 126.5,459.2 127.4,459.1 C 128.2,459.0 129.1,459.0 130.0,458.9 C 130.9,458.7 131.8,458.4 132.7,458.0 C 133.6,457.5 134.5,456.7 135.4,456.4 C 136.2,456.1 137.1,456.2 138.0,456.2 C 138.9,456.1 139.8,456.1 140.7,456.0 C 141.6,456.0 142.0,455.9 143.4,455.9 C 144.7,455.9 146.9,455.9 148.7,455.9 C 150.5,455.8 152.7,455.8 154.0,455.7 C 155.4,455.7 155.8,455.6 156.7,455.6 C 157.6,455.6 158.5,455.5 159.4,455.5 C 160.3,455.4 161.1,455.4 162.0,455.3 C 162.9,455.3 163.8,455.3 164.7,455.3 C 165.6,455.3 166.0,455.2 167.4,455.2 C 168.7,455.2 170.5,455.1 172.7,455.1 C 174.9,455.1 178.5,455.0 180.7,455.0 C 182.9,455.0 183.4,455.0 186.0,455.0 C 188.7,454.9 194.5,454.8 196.7,454.7 C 198.9,454.6 198.1,454.7 199.4,454.6 C 200.7,454.6 202.5,454.6 204.7,454.6 C 206.9,454.5 208.3,454.5 212.7,454.4 C 217.2,454.4 227.8,454.3 231.4,454.3 C 235.0,454.2 233.2,454.2 234.1,454.2 C 235.0,454.2 235.4,454.9 236.7,454.1 C 238.1,453.3 240.7,452.6 242.1,449.3 C 243.4,445.9 243.9,436.9 244.7,434.1 C 245.6,431.3 246.5,432.9 247.4,432.5 C 248.3,432.0 249.2,431.8 250.1,431.5 C 251.0,431.2 251.9,430.9 252.7,430.8 C 253.6,430.6 254.5,430.5 255.4,430.4 C 256.3,430.3 257.2,430.1 258.1,430.0 C 259.0,429.9 259.9,430.0 260.8,429.9 C 261.6,429.8 262.5,429.6 263.4,429.5 C 264.3,429.4 265.2,429.4 266.1,429.3 C 267.0,429.1 267.9,428.7 268.8,428.6 C 269.6,428.4 270.5,428.5 271.4,428.4 C 272.3,428.3 273.2,428.0 274.1,428.0 C 275.0,427.9 275.9,427.9 276.8,427.9 C 277.6,427.8 278.5,427.8 279.4,427.7 C 280.3,427.6 281.2,427.6 282.1,427.5 C 283.0,427.4 283.9,427.4 284.8,427.3 C 285.7,427.2 286.5,426.9 287.4,426.7 C 288.3,426.6 289.2,427.4 290.1,426.3 C 291.0,425.2 291.9,421.5 292.8,420.3 C 293.7,419.1 294.5,419.2 295.4,419.0 C 296.3,418.8 297.2,419.1 298.1,419.0 C 299.0,418.8 299.9,418.4 300.8,418.1 C 301.7,417.7 302.5,417.5 303.4,416.9 C 304.3,416.3 305.2,415.2 306.1,414.5 C 307.0,413.8 307.9,413.1 308.8,412.6 C 309.7,412.1 310.6,411.7 311.4,411.4 C 312.3,411.1 313.2,410.9 314.1,410.8 C 315.0,410.6 315.9,410.7 316.8,410.5 C 317.7,410.4 318.6,410.1 319.4,410.0 C 320.3,409.9 321.2,410.0 322.1,409.9 C 323.0,409.8 323.9,409.8 324.8,409.6 C 325.7,409.5 326.6,409.5 327.5,409.1 C 328.3,408.6 329.2,407.3 330.1,406.8 C 331.0,406.2 331.9,406.0 332.8,405.6 C 333.7,405.1 334.6,406.0 335.5,404.1 C 336.3,402.3 337.2,396.7 338.1,394.6 C 339.0,392.4 339.9,392.1 340.8,391.2 C 341.7,390.3 342.6,389.7 343.5,389.1 C 344.3,388.4 345.2,387.8 346.1,387.3 C 347.0,386.8 347.9,386.4 348.8,386.1 C 349.7,385.8 350.6,385.7 351.5,385.4 C 352.4,385.2 353.2,384.9 354.1,384.5 C 355.0,384.2 355.9,383.6 356.8,383.3 C 357.7,383.0 358.6,383.1 359.5,382.8 C 360.4,382.5 361.2,381.9 362.1,381.5 C 363.0,381.2 363.9,380.9 364.8,380.6 C 365.7,380.3 366.6,380.1 367.5,379.9 C 368.4,379.7 369.2,379.7 370.1,379.5 C 371.0,379.3 371.9,379.1 372.8,378.9 C 373.7,378.7 374.6,378.5 375.5,378.3 C 376.4,378.0 377.3,377.8 378.1,377.5 C 379.0,377.1 379.9,376.4 380.8,376.1 C 381.7,375.7 382.6,375.6 383.5,375.4 C 384.4,375.2 385.3,375.1 386.1,375.0 C 387.0,374.8 387.9,374.7 388.8,374.6 C 389.7,374.5 390.6,374.3 391.5,374.2 C 392.4,374.1 393.3,374.2 394.2,374.0 C 395.0,373.9 395.9,373.6 396.8,373.3 C 397.7,373.0 398.6,372.4 399.5,372.0 C 400.4,371.6 401.3,371.2 402.2,371.0 C 403.0,370.7 403.9,370.7 404.8,370.5 C 405.7,370.3 406.6,370.3 407.5,370.0 C 408.4,369.7 409.3,369.1 410.2,368.8 C 411.0,368.6 411.9,368.7 412.8,368.5 C 413.7,368.4 414.6,368.1 415.5,367.9 C 416.4,367.7 417.3,367.5 418.2,367.2 C 419.1,367.0 419.9,366.8 420.8,366.5 C 421.7,366.3 422.6,366.2 423.5,366.0 C 424.4,365.7 425.3,365.3 426.2,365.0 C 427.1,364.7 427.9,364.6 428.8,364.3 C 429.7,364.0 430.6,363.7 431.5,363.4 C 432.4,363.0 433.3,362.7 434.2,362.4 C 435.1,362.1 435.9,362.0 436.8,361.6 C 437.7,361.1 438.6,360.0 439.5,359.6 C 440.4,359.2 441.3,359.1 442.2,359.0 C 443.1,358.8 444.0,358.8 444.8,358.7 C 445.7,358.6 446.6,358.4 447.5,358.2 C 448.4,358.1 449.3,358.4 450.2,357.9 C 451.1,357.5 452.0,356.2 452.8,355.7 C 453.7,355.2 454.6,355.3 455.5,355.0 C 456.4,354.7 457.3,354.1 458.2,353.9 C 459.1,353.7 460.0,353.8 460.8,353.6 C 461.7,353.4 462.6,352.7 463.5,352.5 C 464.4,352.3 465.3,352.3 466.2,352.2 C 467.1,352.1 468.0,352.1 468.9,351.8 C 469.7,351.5 470.6,351.2 471.5,350.6 C 472.4,350.0 473.3,348.8 474.2,348.2 C 475.1,347.5 476.0,347.3 476.9,346.8 C 477.7,346.4 478.6,346.3 479.5,345.4 C 480.4,344.6 481.3,342.6 482.2,341.7 C 483.1,340.7 484.0,340.3 484.9,339.7 C 485.8,339.2 486.6,339.1 487.5,338.2 C 488.4,337.4 489.3,335.5 490.2,334.6 C 491.1,333.8 492.0,333.7 492.9,333.2 C 493.8,Line truncated
<circle cx="765.0" cy="85.3" r="5" fill="#dd4528" stroke="#fff" stroke-width="2" filter="url(#xkcdify)"/>
<text x="753.0" y="77.0" fill="#dd4528" font-size="18" font-family="Handlee, cursive" font-weight="bold" text-anchor="end">5917</text>
<a href="https://github.com/carsteneu/mystarhistory" target="_blank" rel="noopener"><text x="763" y="527" font-size="13" font-family="Handlee, cursive" fill="#000" text-anchor="end">Made with carsteneu/mystarhistory</text></a>
</svg>

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 33 KiB

+15
View File
@@ -67,6 +67,8 @@ export interface KeywordHighlightRule {
customized?: boolean;
}
export type TerminalTabDoubleClickBehavior = 'duplicate' | 'copy' | 'disabled';
export interface TerminalSettings {
// Rendering
scrollback: number; // Number of lines kept in buffer
@@ -122,6 +124,8 @@ export interface TerminalSettings {
linkModifier: LinkModifier; // Modifier key to click links
autoCloseOnExit: boolean; // Automatically close terminal UI after eligible session exits
disconnectedNoticeMode: DisconnectedNoticeMode; // Non-blocking terminal line or legacy dialog after disconnect
/** Action when double-clicking a terminal session tab. */
tabDoubleClickBehavior: TerminalTabDoubleClickBehavior;
// Keyword Highlighting
keywordHighlightEnabled: boolean;
@@ -358,6 +362,12 @@ const isDynamicTabTitleMode = (value: unknown): value is DynamicTabTitleMode =>
value === 'all'
);
const isTerminalTabDoubleClickBehavior = (value: unknown): value is TerminalTabDoubleClickBehavior => (
value === 'duplicate' ||
value === 'copy' ||
value === 'disabled'
);
const isHostInfoBarTitleMode = (value: unknown): value is HostInfoBarTitleMode => (
value === 'address' ||
value === 'label'
@@ -404,6 +414,9 @@ export const normalizeTerminalSettings = (
dynamicTabTitleMode: isDynamicTabTitleMode(settings?.dynamicTabTitleMode)
? settings.dynamicTabTitleMode
: DEFAULT_TERMINAL_SETTINGS.dynamicTabTitleMode,
tabDoubleClickBehavior: isTerminalTabDoubleClickBehavior(settings?.tabDoubleClickBehavior)
? settings.tabDoubleClickBehavior
: DEFAULT_TERMINAL_SETTINGS.tabDoubleClickBehavior,
hostInfoBarTitleMode: isHostInfoBarTitleMode(settings?.hostInfoBarTitleMode)
? settings.hostInfoBarTitleMode
: DEFAULT_TERMINAL_SETTINGS.hostInfoBarTitleMode,
@@ -535,6 +548,8 @@ const DEFAULT_TERMINAL_SETTINGS: TerminalSettings = {
osc52Clipboard: 'write-only', // OSC-52: allow remote programs to write clipboard by default
oscNotifications: 'always', // Honor OSC 9/777/99 desktop notifications by default
dynamicTabTitleMode: 'agent',
// Prefer an independent SSH login so a double-click never restores the source cwd.
tabDoubleClickBehavior: 'duplicate',
rendererType: 'auto', // Auto-detect best renderer based on hardware
hibernateHiddenTabs: false,
hibernateHiddenTabsDelaySec: 5,
+8
View File
@@ -384,6 +384,14 @@ export const SETTINGS_SEARCH_CATALOG: readonly SettingsSearchEntry[] = [
descriptionKey: "settings.terminal.behavior.clearWipesScrollback.desc",
sectionKey: "settings.terminal.section.behavior",
},
{
id: "terminal-tab-double-click",
tab: "terminal",
labelKey: "settings.terminal.behavior.tabDoubleClick",
descriptionKey: "settings.terminal.behavior.tabDoubleClick.desc",
sectionKey: "settings.terminal.section.behavior",
keywords: ["tab", "double click", "复制会话", "双击"],
},
{
id: "terminal-dynamic-tab-title",
tab: "terminal",
+17
View File
@@ -128,6 +128,23 @@ test("normalizeTerminalSettings falls back for unsupported dynamic tab title mod
);
});
test("normalizeTerminalSettings defaults tab double click to a new connection", () => {
assert.equal(normalizeTerminalSettings().tabDoubleClickBehavior, "duplicate");
});
test("normalizeTerminalSettings preserves supported tab double-click behaviors", () => {
assert.equal(normalizeTerminalSettings({ tabDoubleClickBehavior: "duplicate" }).tabDoubleClickBehavior, "duplicate");
assert.equal(normalizeTerminalSettings({ tabDoubleClickBehavior: "copy" }).tabDoubleClickBehavior, "copy");
assert.equal(normalizeTerminalSettings({ tabDoubleClickBehavior: "disabled" }).tabDoubleClickBehavior, "disabled");
});
test("normalizeTerminalSettings rejects unsupported tab double-click behaviors", () => {
assert.equal(
normalizeTerminalSettings({ tabDoubleClickBehavior: "legacy" as never }).tabDoubleClickBehavior,
"duplicate",
);
});
test("normalizeTerminalSettings enables OSC desktop notifications by default", () => {
assert.equal(normalizeTerminalSettings().oscNotifications, "always");
});
+139 -24
View File
@@ -76,7 +76,7 @@ function buildCodebuddyThinkingEnv(thinking) {
// ---------------------------------------------------------------------------
/** Convert neutral injectMcp configs into the SDK's keyed mcpServers map.
* Supports stdio (default), sse, http, and sdk (in-process) transport types (SDK 0.3.230). */
* Supports stdio (default), sse, http, and sdk (in-process) transport types (SDK 0.3.258). */
function toSdkMcpServers(injectedMcpServers) {
const map = {};
for (const cfg of injectedMcpServers || []) {
@@ -193,7 +193,7 @@ function codebuddyBuiltinTools(toolIntegrationMode) {
: [...MCP_MODE_TOOLS];
}
const CODEBUDDY_REASONING_LEVELS = ["low", "medium", "high", "xhigh"];
const CODEBUDDY_REASONING_LEVELS = ["minimal", "low", "medium", "high", "xhigh", "max"];
const CODEBUDDY_REASONING_LEVEL_SET = new Set(CODEBUDDY_REASONING_LEVELS);
function splitCodebuddyModelSelection(model) {
@@ -207,12 +207,39 @@ function splitCodebuddyModelSelection(model) {
return { model: model.slice(0, slash), effort };
}
function attachCodebuddyThinkingLevels(preset) {
/**
* Narrow a model-declared effort list to the SDK's canonical Effort union.
* Accepts whatever the CLI/runtime reports (SDK 0.3.258 `RawLanguageModel
* .reasoning.supportedEfforts`), keeps only known values, preserves canonical
* order, and returns null when nothing usable was declared so callers can fall
* back to the full union.
*/
function normalizeCodebuddyThinkingLevels(supported) {
if (!Array.isArray(supported) || supported.length === 0) return null;
const ordered = CODEBUDDY_REASONING_LEVELS.filter((level) => supported.includes(level));
return ordered.length > 0 ? ordered : null;
}
function attachCodebuddyThinkingLevels(preset, capability) {
if (!preset) return preset;
// Prefer the levels the model actually declares (SDK 0.3.258). Without a
// usable declaration the full Effort union is offered, matching prior
// behaviour exactly.
const levels = normalizeCodebuddyThinkingLevels(capability?.supportedEfforts)
|| [...CODEBUDDY_REASONING_LEVELS];
const declaredDefault = capability?.defaultEffort;
let defaultThinkingLevel;
if (declaredDefault && levels.includes(declaredDefault)) {
defaultThinkingLevel = declaredDefault;
} else if (levels.includes("medium")) {
defaultThinkingLevel = "medium";
} else {
defaultThinkingLevel = levels[0];
}
return {
...preset,
thinkingLevels: [...CODEBUDDY_REASONING_LEVELS],
defaultThinkingLevel: "medium",
thinkingLevels: levels,
defaultThinkingLevel,
encodeDefaultThinking: false,
};
}
@@ -220,7 +247,7 @@ function attachCodebuddyThinkingLevels(preset) {
function buildCodebuddyQueryOptions({
cwd, model, env, injectedMcpServers, abortController,
resume, pathToCodebuddyCode, toolIntegrationMode, thinking,
// Phase 1: SDK 0.3.230 options
// Phase 1: SDK 0.3.258 options
systemPrompt, effort, maxTurns, maxBudgetUsd, fallbackModel,
sandbox, agents, outputFormat, enableFileCheckpointing,
traceId, parentSpanId, persistSession, sessionId, hooks,
@@ -258,7 +285,7 @@ function buildCodebuddyQueryOptions({
if (thinkingConfig) {
options.thinking = thinkingConfig;
}
// --- SDK 0.3.230 options ---
// --- SDK 0.3.258 options ---
// System prompt: string is treated as append to default.
if (systemPrompt) {
options.systemPrompt = typeof systemPrompt === "string"
@@ -304,7 +331,7 @@ function buildCodebuddyQueryOptions({
if (sessionId) options.sessionId = sessionId;
// Lifecycle hooks.
if (hooks && typeof hooks === "object") options.hooks = hooks;
// Supported by @tencent-ai/agent-sdk 0.3.230 Options.elicitation.
// Supported by @tencent-ai/agent-sdk 0.3.258 Options.elicitation.
// QueryController advertises capabilities.elicitation.form during
// initialization and routes elicitation_create control requests here.
if (elicitation && typeof elicitation === "object") options.elicitation = elicitation;
@@ -660,37 +687,119 @@ function mapCodebuddyModels(models) {
if (!m) return null;
const id = m.id || m.modelId || m.value;
if (!id) return null;
return attachCodebuddyThinkingLevels({
id,
name: m.name || m.displayName || id,
description: m.description,
});
// SDK 0.3.258: raw model records carry reasoning capability metadata
// (RawLanguageModel.reasoning.supportedEfforts / defaultEffort). Simple
// ModelInfo responses omit it — then the full Effort union is offered,
// exactly as before.
const reasoning = m.reasoning && typeof m.reasoning === "object" ? m.reasoning : null;
return attachCodebuddyThinkingLevels(
{
id,
name: m.name || m.displayName || id,
description: m.description,
},
reasoning,
);
})
.filter(Boolean);
}
/**
* Fetch available CodeBuddy models via the SDK control channel. Opens a
* streaming (idle) session so no turn is billed, asks supportedModels(), then
* tears down. Returns [] on failure (caller falls back to curated presets).
* Fetch available CodeBuddy models via the SDK control channel. Prefer the V2
* session API because it exposes RawLanguageModel reasoning metadata. Fall
* back to the legacy idle query API for older SDK/CLI combinations.
* @param {object} args
* @param {string} [args.pathToCodebuddyCode]
* @param {object} [args.env]
* @param {Function} [args.queryFn] inject query() for tests
* @param {Function} [args.createSessionFn] inject V2 createSession() for tests
*/
async function raceCodebuddyModelRequest(request, signal) {
if (!signal) return { type: "models", models: await request() };
let onAbort;
const abortPromise = new Promise((resolve) => {
onAbort = () => resolve({ type: "aborted" });
if (signal.aborted) onAbort();
else signal.addEventListener("abort", onAbort, { once: true });
});
try {
return await Promise.race([
Promise.resolve().then(request).then((models) => ({ type: "models", models })),
abortPromise,
]);
} finally {
if (onAbort) signal.removeEventListener("abort", onAbort);
}
}
async function listCodebuddyModelsViaSession({ createSessionFn, pathToCodebuddyCode, env, signal }) {
if (typeof createSessionFn !== "function") return null;
let session;
try {
session = createSessionFn({ pathToCodebuddyCode, env });
if (typeof session?.getAvailableModelsRaw !== "function") return null;
const rawResult = await raceCodebuddyModelRequest(
() => session.getAvailableModelsRaw(),
signal,
);
if (rawResult.type === "aborted") return [];
if (Array.isArray(rawResult.models) && rawResult.models.length > 0) {
return mapCodebuddyModels(rawResult.models);
}
// Some CLI versions implement the V2 control request but only return the
// simplified catalog. Preserve model discovery in that case, without
// pretending that reasoning capabilities were declared.
if (typeof session.getAvailableModels === "function") {
const simpleResult = await raceCodebuddyModelRequest(
() => session.getAvailableModels(),
signal,
);
if (simpleResult.type === "aborted") return [];
if (Array.isArray(simpleResult.models) && simpleResult.models.length > 0) {
return mapCodebuddyModels(simpleResult.models);
}
}
return [];
} catch {
return signal?.aborted ? [] : null;
} finally {
try { session?.close?.(); } catch { /* best effort */ }
}
}
async function listCodebuddyModels({
pathToCodebuddyCode,
env,
queryFn,
createSessionFn,
abortController,
signal,
}) {
const externalSignal = signal || abortController?.signal;
if (externalSignal?.aborted) return [];
let sdk;
let createSession = createSessionFn;
if (!createSession && !queryFn) {
try {
sdk = await import("@tencent-ai/agent-sdk");
createSession = sdk.unstable_v2_createSession;
} catch { /* use the legacy path below */ }
}
const v2Models = await listCodebuddyModelsViaSession({
createSessionFn: createSession,
pathToCodebuddyCode,
env,
signal: externalSignal,
});
if (v2Models !== null) return v2Models;
let query = queryFn;
if (!query) {
let sdk;
try { sdk = await import("@tencent-ai/agent-sdk"); } catch { return []; }
try {
sdk ||= await import("@tencent-ai/agent-sdk");
} catch { return []; }
query = sdk.query;
}
const queryAbortController = new AbortController();
@@ -740,13 +849,13 @@ async function listCodebuddyModels({
}
// ---------------------------------------------------------------------------
// Hooks (SDK 0.3.230 lifecycle callbacks)
// Hooks (SDK 0.3.258 lifecycle callbacks)
// ---------------------------------------------------------------------------
/**
* Build SDK hooks option from an emitter. Registers PreToolUse / PostToolUse /
* PostToolUseFailure / SessionEnd / Notification hooks that forward events to
* the renderer via the existing emitter event channel.
* PostToolUseFailure / SessionEnd / Notification / PostCompact hooks that
* forward events to the renderer via the existing emitter event channel.
* @param {object} emitter createStreamEmitter(...)
* @returns {object} hooks map suitable for Options.hooks
*/
@@ -825,6 +934,12 @@ function buildCodebuddyHooks(
title: input.title,
notificationType: input.notification_type,
})),
// SDK 0.3.258 — fires after context compaction, carrying the summary that
// was written. Distinct from PreCompact (which the SDK also exposes).
PostCompact: makeHook("PostCompact", (input) => ({
trigger: input.trigger,
compactSummary: input.compact_summary,
})),
};
for (const [eventName, matchers] of Object.entries(additionalHooks || {})) {
if (!Array.isArray(matchers) || matchers.length === 0) continue;
@@ -834,7 +949,7 @@ function buildCodebuddyHooks(
}
// ---------------------------------------------------------------------------
// Elicitation handler (SDK 0.3.230 — interactive confirmations)
// Elicitation handler (SDK 0.3.258 — interactive confirmations)
// ---------------------------------------------------------------------------
/**
@@ -930,7 +1045,7 @@ function buildCodebuddyElicitation(emitter, pendingMap, { chatSessionId } = {})
}
// ---------------------------------------------------------------------------
// MCP server status & account info (SDK 0.3.230)
// MCP server status & account info (SDK 0.3.258)
// ---------------------------------------------------------------------------
/**
@@ -998,7 +1113,7 @@ async function getCodebuddyAccountInfo({ pathToCodebuddyCode, env, queryFn }) {
}
// ---------------------------------------------------------------------------
// Plugin management (SDK 0.3.230)
// Plugin management (SDK 0.3.258)
// ---------------------------------------------------------------------------
async function codebuddyInstallPlugin(options) {
@@ -6,6 +6,7 @@ const {
buildCodebuddyCanUseTool,
buildCodebuddyPromptInput,
codebuddyBuiltinTools,
listCodebuddyModels,
mapCodebuddyModels,
runCodebuddyTurn,
translateCodebuddyMessage,
@@ -426,7 +427,7 @@ test("mapCodebuddyModels maps model ids and drops invalid entries", () => {
id: "glm-5.1",
name: "GLM-5.1",
description: undefined,
thinkingLevels: ["low", "medium", "high", "xhigh"],
thinkingLevels: ["minimal", "low", "medium", "high", "xhigh", "max"],
defaultThinkingLevel: "medium",
encodeDefaultThinking: false,
},
@@ -434,7 +435,7 @@ test("mapCodebuddyModels maps model ids and drops invalid entries", () => {
id: "cb-1",
name: "CodeBuddy 1",
description: "default",
thinkingLevels: ["low", "medium", "high", "xhigh"],
thinkingLevels: ["minimal", "low", "medium", "high", "xhigh", "max"],
defaultThinkingLevel: "medium",
encodeDefaultThinking: false,
},
@@ -442,7 +443,7 @@ test("mapCodebuddyModels maps model ids and drops invalid entries", () => {
id: "cb-2",
name: "CodeBuddy 2",
description: undefined,
thinkingLevels: ["low", "medium", "high", "xhigh"],
thinkingLevels: ["minimal", "low", "medium", "high", "xhigh", "max"],
defaultThinkingLevel: "medium",
encodeDefaultThinking: false,
},
@@ -450,11 +451,77 @@ test("mapCodebuddyModels maps model ids and drops invalid entries", () => {
assert.deepEqual(mapCodebuddyModels(null), []);
});
test("mapCodebuddyModels honours model-declared effort capabilities", () => {
const [declared, fallback] = mapCodebuddyModels([
{
id: "glm-5.1",
name: "GLM-5.1",
reasoning: { supportedEfforts: ["high", "low"], defaultEffort: "high" },
},
{ id: "cb-1", name: "CodeBuddy 1" },
]);
// Declared levels are kept and re-ordered to the canonical union order; the
// declared default wins over the generic "medium" fallback.
assert.deepEqual(declared.thinkingLevels, ["low", "high"]);
assert.equal(declared.defaultThinkingLevel, "high");
// No declaration → the full Effort union with the medium default.
assert.deepEqual(fallback.thinkingLevels, ["minimal", "low", "medium", "high", "xhigh", "max"]);
assert.equal(fallback.defaultThinkingLevel, "medium");
});
test("mapCodebuddyModels ignores unusable effort declarations", () => {
const models = mapCodebuddyModels([
{ id: "m1", reasoning: { supportedEfforts: ["turbo"] } },
{ id: "m2", reasoning: { supportedEfforts: [] } },
{ id: "m3", reasoning: "garbage" },
]);
for (const model of models) {
assert.deepEqual(model.thinkingLevels, ["minimal", "low", "medium", "high", "xhigh", "max"]);
assert.equal(model.defaultThinkingLevel, "medium");
}
});
test("mapCodebuddyModels picks a usable default when the declaration omits medium", () => {
const [model] = mapCodebuddyModels([
{ id: "m1", reasoning: { supportedEfforts: ["max", "minimal"], defaultEffort: "bogus" } },
]);
assert.deepEqual(model.thinkingLevels, ["minimal", "max"]);
// Unusable declared default + medium absent → fall back to the lowest level.
assert.equal(model.defaultThinkingLevel, "minimal");
});
test("listCodebuddyModels uses V2 raw model capabilities when available", async () => {
let closed = false;
const models = await listCodebuddyModels({
pathToCodebuddyCode: "/usr/local/bin/codebuddy",
env: { CODEBUDDY_ENV: "test" },
createSessionFn: (options) => {
assert.equal(options.pathToCodebuddyCode, "/usr/local/bin/codebuddy");
assert.deepEqual(options.env, { CODEBUDDY_ENV: "test" });
return {
async getAvailableModelsRaw() {
return [{
value: "glm-5.1",
displayName: "GLM-5.1",
reasoning: { supportedEfforts: ["high", "low"], defaultEffort: "high" },
}];
},
close() { closed = true; },
};
},
});
assert.equal(closed, true);
assert.equal(models[0].id, "glm-5.1");
assert.deepEqual(models[0].thinkingLevels, ["low", "high"]);
assert.equal(models[0].defaultThinkingLevel, "high");
});
// ---------------------------------------------------------------------------
// SDK 0.3.230 options
// SDK 0.3.258 options
// ---------------------------------------------------------------------------
test("buildCodebuddyQueryOptions passes SDK 0.3.230 options", () => {
test("buildCodebuddyQueryOptions passes SDK 0.3.258 options", () => {
const opts = buildCodebuddyQueryOptions({
cwd: "/tmp",
env: {},
@@ -569,6 +636,7 @@ test("buildCodebuddyHooks returns hook matchers that emit events", async () => {
assert.ok(Array.isArray(hooks.PostToolUseFailure));
assert.ok(Array.isArray(hooks.SessionEnd));
assert.ok(Array.isArray(hooks.Notification));
assert.ok(Array.isArray(hooks.PostCompact));
// Invoke PreToolUse hook callback
const preHook = hooks.PreToolUse[0].hooks[0];
@@ -583,6 +651,23 @@ test("buildCodebuddyHooks returns hook matchers that emit events", async () => {
assert.equal(events[0].ev.toolName, "Bash");
});
test("buildCodebuddyHooks forwards PostCompact with the compaction summary", async () => {
const { events, emitter } = collector();
emitter.emitEvent = (ev) => events.push({ k: "event", ev });
const hooks = buildCodebuddyHooks(emitter);
const postCompact = hooks.PostCompact[0].hooks[0];
const result = await postCompact(
{ hook_event_name: "PostCompact", trigger: "auto", compact_summary: "summarised history" },
undefined,
{ signal: new AbortController().signal },
);
assert.deepEqual(result, { continue: true });
assert.equal(events[0].ev.hookEvent, "PostCompact");
assert.equal(events[0].ev.trigger, "auto");
assert.equal(events[0].ev.compactSummary, "summarised history");
});
test("buildCodebuddyHooks blocks non-Netcatty Bash commands in skills mode", async () => {
const { emitter } = collector();
emitter.emitEvent = () => {};
@@ -47,6 +47,7 @@ function computeOptionsFingerprint(sessionOptions) {
agents: sessionOptions.agents,
thinking: sessionOptions.thinking,
effort: sessionOptions.effort,
persistSession: sessionOptions.persistSession,
hasHooks: Boolean(sessionOptions.hooks),
hasCanUseTool: typeof sessionOptions.canUseTool === "function",
hasElicitation: Boolean(sessionOptions.elicitation),
@@ -337,7 +338,7 @@ class CodebuddySessionManager {
* Report mid-turn steer as unsupported for the current V2 Session API.
*/
async steer() {
// SDK 0.3.230 Session.send() starts a new turn by resetting the shared
// SDK 0.3.258 Session.send() starts a new turn by resetting the shared
// message iterator and discarding pending messages. Calling it while
// runTurn() owns session.stream() can strand that active consumer.
// Keep this disabled until the SDK exposes a dedicated mid-turn steer API.
@@ -227,6 +227,7 @@ test("computeOptionsFingerprint detects option changes", () => {
const diffModel = { cwd: "/tmp", model: "glm-4", maxTurns: 10, effort: "high" };
const diffMaxTurns = { cwd: "/tmp", model: "glm-5", maxTurns: 20, effort: "high" };
const diffEffort = { cwd: "/tmp", model: "glm-5", maxTurns: 10, effort: "low" };
const diffPersistSession = { ...base, persistSession: false };
const diffExtraArgs = {
...base,
extraArgs: { "dangerously-skip-permissions": "false" },
@@ -235,6 +236,7 @@ test("computeOptionsFingerprint detects option changes", () => {
assert.notEqual(computeOptionsFingerprint(base), computeOptionsFingerprint(diffModel));
assert.notEqual(computeOptionsFingerprint(base), computeOptionsFingerprint(diffMaxTurns));
assert.notEqual(computeOptionsFingerprint(base), computeOptionsFingerprint(diffEffort));
assert.notEqual(computeOptionsFingerprint(base), computeOptionsFingerprint(diffPersistSession));
assert.notEqual(computeOptionsFingerprint(base), computeOptionsFingerprint(diffExtraArgs));
});
+5 -1
View File
@@ -186,7 +186,7 @@ const DRIVER_REGISTRY = {
resume: ctx.resumeSessionId,
pathToCodebuddyCode: ctx.binPath,
toolIntegrationMode: ctx.toolIntegrationMode,
// SDK 0.3.230 options
// SDK 0.3.258 options
systemPrompt: ctx.systemPrompt,
effort: ctx.effort,
maxTurns: ctx.maxTurns,
@@ -198,6 +198,7 @@ const DRIVER_REGISTRY = {
enableFileCheckpointing: ctx.enableFileCheckpointing,
traceId: ctx.traceId,
parentSpanId: ctx.parentSpanId,
persistSession: ctx.persistSession,
hooks: codebuddy.buildCodebuddyHooks(ctx.emitter, {
toolIntegrationMode: ctx.toolIntegrationMode,
additionalHooks: ctx.hooks,
@@ -239,6 +240,9 @@ const DRIVER_REGISTRY = {
agents: options.agents,
thinking: options.thinking,
effort: options.effort,
// SessionOptions gained persistSession in SDK 0.3.258; without this the
// V2 path would silently drop the setting (query() already forwards it).
persistSession: options.persistSession,
};
const v2Result = await codebuddySessionManager.runTurn({
sessionKey,
@@ -523,10 +523,10 @@ function registerSdkStreamHandlers(ctx) {
model, existingSessionId, toolIntegrationMode,
defaultTargetSession, userSkillsContext, agentEnv: requestedAgentEnv, agentCommand,
codexRuntime: requestedCodexRuntime, permissionMode,
// SDK 0.3.230 advanced options (passed from renderer via sdkAgentAdapter)
// SDK 0.3.258 advanced options (passed from renderer via sdkAgentAdapter)
effort, maxTurns, maxBudgetUsd, fallbackModel,
sandbox, agents, outputFormat, enableFileCheckpointing,
traceId, parentSpanId,
traceId, parentSpanId, persistSession,
} = payload;
const backendKey = resolveBackendKey(sdkBackend);
@@ -793,7 +793,7 @@ function registerSdkStreamHandlers(ctx) {
// when the CLI hits a security restriction, route the decision
// through the renderer approval UI instead of throwing an error.
requestApprovalFromRenderer: mcpServerBridge.requestApprovalFromRenderer,
// SDK 0.3.230 advanced options
// SDK 0.3.258 advanced options
effort: effort || undefined,
maxTurns: maxTurns || undefined,
maxBudgetUsd: maxBudgetUsd || undefined,
@@ -804,6 +804,7 @@ function registerSdkStreamHandlers(ctx) {
enableFileCheckpointing: enableFileCheckpointing != null ? enableFileCheckpointing : undefined,
traceId: traceId || undefined,
parentSpanId: parentSpanId || undefined,
persistSession: persistSession != null ? persistSession : undefined,
};
const result = codexRuntime === "app-server"
? await codexAppServerRuntime.runTurn(commonTurnContext)
@@ -979,7 +980,7 @@ function registerSdkStreamHandlers(ctx) {
const runtime = sdkRequestRuntimes.get(requestId);
if (!runtime) return { status: "busy" };
// SDK 0.3.230 Session.send() resets the active message stream, so it
// SDK 0.3.258 Session.send() resets the active message stream, so it
// cannot safely implement mid-turn steering.
if (runtime.backendKey === "codebuddy") {
return { status: "unsupported" };
@@ -1096,7 +1097,7 @@ function registerSdkStreamHandlers(ctx) {
}
});
// --- CodeBuddy SDK 0.3.230 IPC handlers ---
// --- CodeBuddy SDK 0.3.258 IPC handlers ---
ipcMain.handle("netcatty:ai:sdk-agent:mcp-status", async (event, payload) => {
if (!validateSender(event)) return { ok: false, error: "Unauthorized IPC sender" };
+7 -1
View File
@@ -635,7 +635,13 @@ function createSessionOpsApi(ctx) {
_rc_cwd=$(readlink "/proc/$1/cwd" 2>/dev/null)
if [ -n "$_rc_cwd" ]; then printf '%s\\n' "$_rc_cwd"; return 0; fi
if command -v lsof >/dev/null 2>&1; then
_rc_cwd=$(LC_ALL=C lsof -a -p "$1" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -n1)
# An unknown-type record can put a readlink error in its name field.
# Only a confirmed directory name is usable as an upload destination.
_rc_cwd=$(LC_ALL=C lsof -a -p "$1" -d cwd -Fnt 2>/dev/null | awk '
/^f/ { is_dir=0 }
/^t/ { is_dir=($0 == "tDIR" || $0 == "tVDIR") }
/^n/ && is_dir { print substr($0, 2); exit }
')
if [ -n "$_rc_cwd" ]; then printf 'NETCATTY_LSOF_CWD=%s\\n' "$_rc_cwd"; return 0; fi
fi
return 1
@@ -29,7 +29,7 @@ function makeApi(session, siblingSessions = [], overrides = {}) {
sessions,
setTimeout: overrides.setTimeout || setTimeout,
clearTimeout: overrides.clearTimeout || clearTimeout,
quoteShellArg,
quoteShellArg: overrides.quoteShellArg || quoteShellArg,
log: () => {},
});
}
@@ -123,6 +123,47 @@ test("session cwd probe decodes the marked lsof pathname", async () => {
assert.deepEqual(result, { success: true, cwd: "/srv/中文" });
});
test("lsof cwd fallback accepts only directory records, not diagnostic names (#3237)", async () => {
let script;
const api = makeApi({
shellPid: "4242",
connRef: { count: 1 },
stream: {},
conn: {
exec(_command, callback) {
callback(null, makePwdStream("/srv/app", "4242"));
},
},
}, [], {
quoteShellArg(value) {
if (!script && value.includes("read_shell_cwd()")) script = value;
return quoteShellArg(value);
},
});
await api.getSessionPwd(null, { sessionId: "session-1" });
const helper = script.match(/ read_shell_cwd\(\) \{[\s\S]*?\n \}/)?.[0];
assert.ok(helper, "exercise the actual remote cwd helper");
const { spawnSync } = require("node:child_process");
for (const [output, expected] of [
["p4242\nfcwd\ntunknown\nn/proc/4242/cwd (readlink: Permission denied)\n", null],
["p4242\nfcwd\ntunknown\nn/proc/4242/cwd (readlink: No such file or directory)\n", null],
["p4242\nfcwd\nn/proc/4242/cwd\n", null],
["p4242\nfcwd\ntDIR\nn/srv/app\n", "/srv/app"],
["p4242\nfcwd\ntVDIR\nn/usr/home/alice\n", "/usr/home/alice"],
["p4242\nfcwd\ntDIR\nn/tmp/literal (readlink: Permission denied)\n", "/tmp/literal (readlink: Permission denied)"],
["p4242\nfcwd\ntDIR\nn/tmp/\\xe4\\xb8\\xad\\xe6\\x96\\x87\n", "/tmp/\\xe4\\xb8\\xad\\xe6\\x96\\x87"],
]) {
const result = spawnSync("sh", ["-c", `
readlink() { return 1; }
lsof() { printf '%s' "$LSOF_TEST_OUTPUT"; }
${helper}
read_shell_cwd 4242
`], { encoding: "utf8", env: { ...process.env, LSOF_TEST_OUTPUT: output } });
assert.equal(result.status, expected === null ? 1 : 0, output);
assert.equal(result.stdout, expected === null ? "" : `NETCATTY_LSOF_CWD=${expected}\n`, output);
}
});
test("session cwd probe closes a remote command that exceeds its timeout", async () => {
const stream = new EventEmitter();
stream.stderr = new EventEmitter();
+1
View File
@@ -1899,6 +1899,7 @@ function createPreloadApi(ctx) {
fallbackModel: codebuddyOptions.fallbackModel,
sandbox: codebuddyOptions.sandbox,
enableFileCheckpointing: codebuddyOptions.enableFileCheckpointing,
persistSession: codebuddyOptions.persistSession,
}
: {};
return ipcRenderer.invoke("netcatty:ai:sdk-agent:stream", {
@@ -573,3 +573,75 @@ test('an item id whose every fragment lacks ciphertext still discards the tool e
assert.equal(sdkMessages.length, 1);
assert.equal(sdkMessages[0].content, 'Running it.');
});
test('Anthropic-format signed thinking survives build and stream preparation', () => {
// Anthropic-compatible providers (including DeepSeek's /anthropic endpoint)
// validate signed thinking blocks: prior assistant turns must echo back
// content[type=thinking] with their signature on every request that carries
// tools, or they reject the turn with 400 "The `content[].thinking` in the
// thinking mode must be passed back to the API". The turn driver preserves
// reasoning for anthropic-style providers, so the signed thinking block
// replayed here must survive final stream preparation.
const messages: ChatMessage[] = [
{
id: 'assistant-1',
role: 'assistant',
content: 'Running it.',
timestamp: 1,
providerContinuation: {
source: { providerConfigId: 'anthropic-1', providerType: 'anthropic', modelId: 'deepseek-v4-flash' },
reasoningParts: [{
text: 'prior thinking',
providerOptions: { anthropic: { signature: 'sig-abc' } },
}],
},
toolCalls: [{ id: 'call-1', name: 'terminal_execute', arguments: { command: 'ls' } }],
},
{
id: 'tool-1',
role: 'tool',
content: '',
timestamp: 2,
toolResults: [{ toolCallId: 'call-1', content: 'output' }],
},
{
id: 'assistant-2',
role: 'assistant',
content: 'All done.',
timestamp: 4,
providerContinuation: {
source: { providerConfigId: 'anthropic-1', providerType: 'anthropic', modelId: 'deepseek-v4-flash' },
reasoningParts: [{
text: 'final thinking',
providerOptions: { anthropic: { signature: 'sig-xyz' } },
}],
},
},
];
const built = buildCattySdkMessages({
allMessages: messages,
includeCurrentUserMessage: false,
trimmed: '',
continuationContext: createContinuationContext('anthropic-1', 'anthropic', 'deepseek-v4-flash'),
chatSessionId: 'chat-1',
toolOutputStore: new ToolOutputStore(),
fieldsByMessage: new Map(),
});
const prepared = prepareCattyMessagesForStream(built, { preserveReasoning: true });
const toolExchangeContent = prepared[0].content;
assert.ok(Array.isArray(toolExchangeContent));
assert.deepEqual(
toolExchangeContent.map(part => part.type),
['reasoning', 'text', 'tool-call'],
);
const toolThinking = toolExchangeContent.find(part => part.type === 'reasoning');
assert.deepEqual(toolThinking?.providerOptions?.anthropic, { signature: 'sig-abc' });
const plainContent = prepared.at(-1).content;
assert.ok(Array.isArray(plainContent));
const plainThinking = plainContent.find(part => part.type === 'reasoning');
assert.equal(plainThinking?.text, 'final thinking');
assert.deepEqual(plainThinking?.providerOptions?.anthropic, { signature: 'sig-xyz' });
});
@@ -275,6 +275,16 @@ async function runCattyTurn(input: CattyTurnInput, ctx: TurnDriverContext): Prom
),
);
const preserveStatelessResponsesReasoning = reasoningProviderOptions?.openai?.store === false;
// Anthropic-compatible providers validate signed thinking blocks: every
// content[type=thinking] block (with its signature) captured on prior
// assistant turns must be echoed back verbatim on subsequent requests that
// carry tools, or the API rejects the turn with "The `content[].thinking`
// in the thinking mode must be passed back to the API" (DeepSeek's
// Anthropic-format endpoint is strict about this even for earlier turns).
// Keep the reasoning parts buildCattySdkMessages replayed instead of
// pruning them like generic Chat Completions histories.
const preserveAnthropicThinkingReplay = resolveProviderStyle(context.activeProvider) === 'anthropic';
const preserveReasoningForStream = preserveStatelessResponsesReasoning || preserveAnthropicThinkingReplay;
const reasoningReserveTokens = estimateReasoningOutputReserve(reasoningProviderOptions);
// Fold thinking budget into compaction maxOutput only. reservedTokens is
// added to estimated input separately, so adding the budget there too
@@ -290,7 +300,7 @@ async function runCattyTurn(input: CattyTurnInput, ctx: TurnDriverContext): Prom
const prepareMessagesForStream = (messages: ModelMessage[]): ModelMessage[] => {
const pruned = prepareCattyMessagesForStream(messages, {
preserveReasoning: preserveStatelessResponsesReasoning,
preserveReasoning: preserveReasoningForStream,
});
continuationContext.openAIChatAssistantFields = collectOpenAIChatAssistantFieldsForMessages(
pruned,
@@ -103,6 +103,7 @@ test('plan updates replace the same activity across tool message boundaries', as
status: 'completed',
items: [{ text: 'Run command', completed: true }],
});
onEvent?.({ type: 'hook', hookEvent: 'PostCompact' });
onDone?.();
});
return { ok: true };
@@ -135,6 +136,7 @@ test('plan updates replace the same activity across tool message boundaries', as
reportStreamError: () => {},
setStreamingForScope: () => {},
getLatestSession: () => session,
translate: (key) => key === 'ai.chat.contextCompacted' ? 'Context compacted (localized)' : key,
};
const agentConfig: ExternalAgentConfig = {
id: 'codex',
@@ -183,6 +185,10 @@ test('plan updates replace the same activity across tool message boundaries', as
assert.deepEqual(planActivities[0].type === 'plan_update' ? planActivities[0].items : [], [
{ text: 'Run command', completed: true },
]);
assert.equal(
session.messages.findLast(message => message.role === 'assistant')?.statusText,
'Context compacted (localized)',
);
});
test('accepted steering persists a user bubble and routes buffered output to a continuation', async () => {
@@ -364,6 +364,16 @@ async function runExternalTurn(
updateActiveAssistant(msg => ({ ...msg, statusText: message }));
});
}
} else if (hookEvent === 'PostCompact') {
// SDK 0.3.258 — history was summarised; tell the user why earlier
// detail is no longer present in this turn's context.
flushTextBeforeNonTextEvent();
runOrBufferUiOperation(() => {
updateActiveAssistant(msg => ({
...msg,
statusText: ui.translate?.('ai.chat.contextCompacted') ?? 'Context compacted',
}));
});
}
},
onElicitationCreate: (elicitationId: string, request: Record<string, unknown>) => {
@@ -49,6 +49,7 @@ export interface TurnUiCallbacks {
sessionId: string,
compaction: import('../../types').AISessionContextCompaction,
) => void;
translate?: (key: string) => string;
}
export interface CattyTurnContext {
+36
View File
@@ -581,3 +581,39 @@ test('runSdkAgentTurn forwards CodeBuddy options, hooks, and elicitation events'
]);
assert.deepEqual(errors, []);
});
test('runSdkAgentTurn strips unsupported CodeBuddy session persistence', async () => {
let streamArgs: unknown[] = [];
const bridge: Record<string, (...args: unknown[]) => unknown> = {
aiSdkAgentStream: async (...args: unknown[]) => {
streamArgs = args;
return { ok: true };
},
aiSdkAgentCancel: async () => ({ ok: true }),
onAiSdkAgentEvent: () => () => {},
onAiSdkAgentDone: (_requestId: unknown, callback: unknown) => {
queueMicrotask(callback as () => void);
return () => {};
},
onAiSdkAgentError: () => () => {},
};
await runSdkAgentTurn(
bridge,
'request-codebuddy-old',
'chat-codebuddy-old',
{
id: 'codebuddy-old',
name: 'CodeBuddy',
command: 'codebuddy',
enabled: true,
sdkBackend: 'codebuddy',
cliVersion: '2.125.0',
codebuddyOptions: { persistSession: false },
},
'hello',
createCallbacks([]),
);
assert.equal(streamArgs[17], undefined);
});
+14 -1
View File
@@ -12,6 +12,7 @@ import type {
AIToolIntegrationMode,
ExternalAgentConfig,
} from './types';
import { isCodebuddySessionPersistenceSupported } from './types';
import { getExternalAgentSdkBackend, getManualAgentCommand } from './managedAgents';
import { encodeSdkSessionIdentity } from './harness/sdkSessionIdentity';
import { mapSdkStreamEventToAgentEvents } from './harness/agentEventAdapter';
@@ -98,6 +99,18 @@ interface StreamEvent {
[key: string]: unknown;
}
function getCodebuddySdkOptions(config: ExternalAgentConfig): ExternalAgentConfig['codebuddyOptions'] {
const options = config.codebuddyOptions;
if (!options || options.persistSession !== false) return options;
if (isCodebuddySessionPersistenceSupported(config.cliVersion)) return options;
// The SDK translates false to --no-session-persistence. Do not send that
// flag to an older or unprobed CLI; undefined preserves its default behavior.
const supportedOptions = { ...options };
delete supportedOptions.persistSession;
return Object.keys(supportedOptions).length > 0 ? supportedOptions : undefined;
}
export interface FileAttachment {
base64Data: string;
mediaType: string;
@@ -353,7 +366,7 @@ export async function runSdkAgentTurn(
agentCommand,
sdkBackend === 'codex' ? (config.codexRuntime ?? 'sdk') : undefined,
permissionMode,
sdkBackend === 'codebuddy' ? config.codebuddyOptions : undefined,
sdkBackend === 'codebuddy' ? getCodebuddySdkOptions(config) : undefined,
).then((result) => {
if (result?.ok === false) {
settle(() => {
+11 -1
View File
@@ -4,6 +4,7 @@ import assert from 'node:assert/strict';
import {
CLAUDE_MODEL_PRESETS,
CODEBUDDY_MODEL_PRESETS,
CODEBUDDY_PERSIST_SESSION_MIN_CLI_VERSION,
CODEX_GPT_5_6_MIN_CLI_VERSION,
CODEX_MODEL_PRESETS,
CURSOR_MODEL_PRESETS,
@@ -14,6 +15,7 @@ import {
resolveAgentCliVersion,
resolveAgentModelSelection,
resolveDiscoveredAgentCliVersion,
isCodebuddySessionPersistenceSupported,
} from './types';
test('Claude presets advertise effort levels separately from the model id', () => {
@@ -44,10 +46,18 @@ test('getAgentModelPresets returns Cursor presets for cursor-agent paths and sdk
);
assert.deepEqual(
getAgentModelPresets(undefined, 'codebuddy')[0]?.thinkingLevels,
['low', 'medium', 'high', 'xhigh'],
['minimal', 'low', 'medium', 'high', 'xhigh', 'max'],
);
});
test('CodeBuddy session persistence is gated by CLI version', () => {
assert.equal(CODEBUDDY_PERSIST_SESSION_MIN_CLI_VERSION, '2.125.1');
assert.equal(isCodebuddySessionPersistenceSupported('CodeBuddy 2.125.0'), false);
assert.equal(isCodebuddySessionPersistenceSupported('CodeBuddy 2.125.1'), true);
assert.equal(isCodebuddySessionPersistenceSupported('2.126.0'), true);
assert.equal(isCodebuddySessionPersistenceSupported(undefined), false);
});
test('getAgentModelPresets keeps Codex presets separate from CodeBuddy presets', () => {
assert.deepEqual(getAgentModelPresets('codex'), CODEX_MODEL_PRESETS);
assert.notDeepEqual(CODEBUDDY_MODEL_PRESETS, CODEX_MODEL_PRESETS);
+25 -5
View File
@@ -306,14 +306,14 @@ export interface ExternalAgentConfig {
acpArgs?: string[];
/** Internal: disabled only because the managed CLI was unavailable. */
autoDisabledUntilAvailable?: boolean;
/** CodeBuddy-specific advanced options (SDK 0.3.230). */
/** CodeBuddy-specific advanced options (SDK 0.3.258). */
codebuddyOptions?: CodebuddyAdvancedOptions;
}
/** Advanced options specific to the CodeBuddy backend (SDK 0.3.230+). */
/** Advanced options specific to the CodeBuddy backend (SDK 0.3.258+). */
export interface CodebuddyAdvancedOptions {
/** Effort level for model reasoning. */
effort?: 'low' | 'medium' | 'high' | 'xhigh';
/** Effort level for model reasoning (SDK 0.3.258 Effort union). */
effort?: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
/** Maximum conversation turns per request. */
maxTurns?: number;
/** Maximum budget in USD per request. */
@@ -324,6 +324,14 @@ export interface CodebuddyAdvancedOptions {
enableFileCheckpointing?: boolean;
/** Fallback model when primary is unavailable. */
fallbackModel?: string;
/**
* Persist the session transcript to disk. Defaults to true; set false to keep
* the conversation in memory only (CodeBuddy writes no session data under
* its config dir, and file checkpointing is skipped). Netcatty chat history
* and external session metadata are unaffected. Requires CodeBuddy CLI >=
* 2.125.1.
*/
persistSession?: boolean;
}
// Discovered agent from system PATH
@@ -587,6 +595,9 @@ const CODEX_REASONING_LEVELS_5_6_LUNA = ['low', 'medium', 'high', 'xhigh', 'max'
/** Official catalog `minimal_client_version` for GPT-5.6 family. */
export const CODEX_GPT_5_6_MIN_CLI_VERSION = '0.144.0';
/** Minimum CodeBuddy CLI version that supports `persistSession: false`. */
export const CODEBUDDY_PERSIST_SESSION_MIN_CLI_VERSION = '2.125.1';
function codexPreset(
id: string,
name: string,
@@ -666,6 +677,15 @@ export function filterAgentModelPresetsForCliVersion(
});
}
/** Return whether the installed CodeBuddy CLI accepts session persistence options. */
export function isCodebuddySessionPersistenceSupported(
cliVersionText: string | null | undefined,
): boolean {
const cliSemver = extractCliSemver(cliVersionText);
return cliSemver !== null
&& compareSemverCore(cliSemver, CODEBUDDY_PERSIST_SESSION_MIN_CLI_VERSION) >= 0;
}
/** Resolve the model id (with optional /effort) for auto-selection. */
export function resolveAgentModelSelection(preset: AgentModelPreset): string {
const levels = preset.thinkingLevels;
@@ -781,7 +801,7 @@ export const CURSOR_MODEL_PRESETS: AgentModelPreset[] = [
// CodeBuddy's SDK model enumeration can be empty depending on CLI/account
// state; keep a CLI-supported fallback list so users can still pass --model.
const CODEBUDDY_REASONING_LEVELS = ['low', 'medium', 'high', 'xhigh'] as const;
const CODEBUDDY_REASONING_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const;
function codebuddyPreset(id: string, name: string): AgentModelPreset {
return {
+16 -5
View File
@@ -123,7 +123,7 @@
"@cursor/sdk": "^1.0.18",
"@github/copilot-sdk": "1.0.0",
"@opencode-ai/sdk": "^1.17.9",
"@tencent-ai/agent-sdk": "^0.3.230",
"@tencent-ai/agent-sdk": "^0.3.258",
"@vscode/windows-process-tree": "^0.7.0"
}
},
@@ -8614,14 +8614,15 @@
}
},
"node_modules/@tencent-ai/agent-sdk": {
"version": "0.3.230",
"resolved": "https://registry.npmjs.org/@tencent-ai/agent-sdk/-/agent-sdk-0.3.230.tgz",
"integrity": "sha512-Jc4tvpCrNvqra8sY8Vf+NEODsPcHj42RK3/TGAa35brzpNWPMydSb5BATdq/NLKLBoAqn8KJsS+Day3ItQCFTQ==",
"version": "0.3.258",
"resolved": "https://registry.npmjs.org/@tencent-ai/agent-sdk/-/agent-sdk-0.3.258.tgz",
"integrity": "sha512-3m3ildYkb965g/7zGeYoniA1Jeylpv19nUswvHrrxXixpIKePgYqNqTYHVIG24TBBp632/xBs5Gn2Fu+o/9rBQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"@agentclientprotocol/sdk": "^0.25.0",
"@modelcontextprotocol/sdk": "^1.29.0"
"@modelcontextprotocol/sdk": "^1.29.0",
"uuidv7": "^1.2.1"
},
"peerDependencies": {
"zod": "^4.0.0"
@@ -19678,6 +19679,16 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/uuidv7": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/uuidv7/-/uuidv7-1.2.1.tgz",
"integrity": "sha512-4kPkK3/XTQW9Hbm4CaqfICn+kY9LJtDVEOfgsRRra/+n2Ofg4NqzRFceAkxvQ/Ud/6BpHOPzj8cirqM7TzTN5Q==",
"license": "Apache-2.0",
"optional": true,
"bin": {
"uuidv7": "cli.js"
}
},
"node_modules/uvu": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
+1 -1
View File
@@ -176,7 +176,7 @@
"@cursor/sdk": "^1.0.18",
"@github/copilot-sdk": "1.0.0",
"@opencode-ai/sdk": "^1.17.9",
"@tencent-ai/agent-sdk": "^0.3.230",
"@tencent-ai/agent-sdk": "^0.3.258",
"@vscode/windows-process-tree": "^0.7.0"
},
"overrides": {