diff --git a/CHANGELOG.md b/CHANGELOG.md index 2317a2d..94c751d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.8] - 2026-07-15 + +### Added + +- 🏠 **Choose how `.cptr` is handled in projects.** Admins can now turn the automatic `.gitignore` entry on or off, so local chats, memory, caches, audio samples, and generated files stay private by default unless you choose otherwise. +- 🤖 **A llama.cpp connection mode.** OpenAI-compatible connections can now be marked as llama.cpp, so Qwen and other local reasoning models can receive prior thinking in the format llama.cpp expects. +- 🧩 **Skills can open their own companion files.** `view_skill` can now load files such as `references/guide.md` from the selected skill folder, without opening broader home-directory access. +- 🌐 **Sized browser screenshots.** Browser tasks can request a screenshot at a specific size and Computer will restore the normal page view afterward. + +### Changed + +- ⚙️ **Admin switches behave like switches.** In Models, Agents, Connections, Messaging, and Tool Servers, click the row text to edit and click the switch only to turn something on or off. +- 🔎 **Workspace search respects `.gitignore`.** File search and content search now skip ignored folders and files, keeping generated output, caches, and local Computer data out of normal results. +- 🧠 **Reasoning replay is stricter by default.** Computer now keeps strict OpenAI-compatible connections clean, and only sends llama.cpp-style `reasoning_content` when the connection is set to llama.cpp. + +### Fixed + +- 🧠 **The chat size meter no longer freezes behind llama.cpp.** Computer now normalizes token usage from OpenAI-compatible streams and sends live context updates to the chat while a response is still running. +- 💬 **Compacted chats survive regenerated answers better.** Summaries are saved on a stable kept user message when possible, so regenerating an assistant answer is less likely to lose the compacted history. +- ✂️ **Manual compact keeps the latest turn available.** Compacting a chat now summarizes the older branch and leaves the newest message ready for the next response. +- 🧠 **Prior reasoning is preserved more reliably.** Reasoning from ordinary replies, tool-call turns, Chat Completions streams, and Responses streams is less likely to be dropped before the next model request. +- 🎨 **Token usage details match the chosen theme.** The usage popover now uses Computer's app surface colors instead of the wrong gray background. +- ✨ **Messages render more faithfully.** Your own messages can show Markdown, and raw markup appears as text instead of disappearing. +- 🗂️ **Commit suggestions are less brittle.** If the model returns a plain commit message instead of perfect structured output, Computer can still use the useful parts. +- 📝 **File edits keep text encoding consistent.** Created and edited files are written as UTF-8, which avoids surprises with non-ASCII project files. + ## [0.9.7] - 2026-07-12 ### Fixed diff --git a/cptr/env.py b/cptr/env.py index 5d856a5..14ad9d4 100644 --- a/cptr/env.py +++ b/cptr/env.py @@ -66,6 +66,10 @@ def _env_int(name: str, default: int) -> int: # Claude SDK stdout JSON buffer; chat/tool output caps apply later after parsing. CLAUDE_CODE_MAX_BUFFER_SIZE = _env_int("CPTR_CLAUDE_CODE_MAX_BUFFER_SIZE", 128 * 1024 * 1024) +# ── Workspace storage ─────────────────────────────────────── +WORKSPACE_AUTO_GITIGNORE_DOT_CPTR_ENV = os.environ.get("CPTR_AUTO_GITIGNORE_DOT_CPTR") +WORKSPACE_AUTO_GITIGNORE_DOT_CPTR = _env_bool("CPTR_AUTO_GITIGNORE_DOT_CPTR", "true") + # ── Execute timeout ───────────────────────────────────────── # Default wait (seconds) for run_command / check_task when the caller # doesn't pass an explicit wait value. None = return immediately. diff --git a/cptr/frontend/package-lock.json b/cptr/frontend/package-lock.json index 1f05b5e..d258992 100644 --- a/cptr/frontend/package-lock.json +++ b/cptr/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "0.4.0", + "version": "0.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frontend", - "version": "0.3.4", + "version": "0.4.1", "dependencies": { "@codemirror/lang-cpp": "^6.0.3", "@codemirror/lang-css": "^6.3.1", diff --git a/cptr/frontend/package.json b/cptr/frontend/package.json index 8c07701..60d9fa0 100644 --- a/cptr/frontend/package.json +++ b/cptr/frontend/package.json @@ -93,4 +93,4 @@ "tippy.js": "^6.3.7", "xlsx": "^0.18.5" } -} \ No newline at end of file +} diff --git a/cptr/frontend/src/app.css b/cptr/frontend/src/app.css index c29a90c..e03784f 100644 --- a/cptr/frontend/src/app.css +++ b/cptr/frontend/src/app.css @@ -29,8 +29,8 @@ --app-fg: #525252; --app-fg-muted: color-mix(in oklab, var(--app-fg) 62%, var(--app-bg)); --app-fg-subtle: color-mix(in oklab, var(--app-fg) 48%, var(--app-bg)); - --app-border: color-mix(in oklab, var(--app-fg) 7%, transparent); - --app-divider: color-mix(in oklab, var(--app-fg) 5%, transparent); + --app-border: color-mix(in oklab, var(--app-fg) 5%, transparent); + --app-divider: color-mix(in oklab, var(--app-fg) 3.5%, transparent); --app-hover: color-mix(in oklab, var(--app-fg) 6%, transparent); --app-active: color-mix(in oklab, var(--app-fg) 7%, transparent); --app-ui-font: var(--font-sans); diff --git a/cptr/frontend/src/lib/apis/admin.ts b/cptr/frontend/src/lib/apis/admin.ts index a8140ff..b567453 100644 --- a/cptr/frontend/src/lib/apis/admin.ts +++ b/cptr/frontend/src/lib/apis/admin.ts @@ -130,6 +130,7 @@ export interface Connection { name: string; provider: string; api_type: string; + provider_type?: 'default' | 'llama.cpp' | null; prefix_id: string | null; base_url: string | null; api_key: string | null; @@ -146,6 +147,7 @@ export const createConnection = (conn: { name: string; provider: string; api_type?: string; + provider_type?: 'default' | 'llama.cpp' | null; prefix_id?: string | null; base_url?: string | null; api_key?: string | null; diff --git a/cptr/frontend/src/lib/apis/chat.ts b/cptr/frontend/src/lib/apis/chat.ts index a5c8339..eb1e171 100644 --- a/cptr/frontend/src/lib/apis/chat.ts +++ b/cptr/frontend/src/lib/apis/chat.ts @@ -171,8 +171,11 @@ export const answerAskUser = ( export const cancelTask = (chatId: string, messageId: string) => fetchJSON(`/api/chats/${chatId}/messages/${messageId}/cancel`, { method: 'POST' }); -export const compactChat = (chatId: string, modelId: string) => - fetchJSON(`/api/chats/${chatId}/compact`, jsonBody({ model_id: modelId })); +export const compactChat = (chatId: string, modelId?: string | null) => + fetchJSON( + `/api/chats/${chatId}/compact`, + jsonBody({ model_id: modelId || null }) + ); export const updateCurrentMessage = (chatId: string, messageId: string) => fetchJSON<{ ok: boolean }>(`/api/chats/${chatId}/current`, jsonBody({ message_id: messageId })); diff --git a/cptr/frontend/src/lib/components/Admin/Agents.svelte b/cptr/frontend/src/lib/components/Admin/Agents.svelte index 59477f7..f081951 100644 --- a/cptr/frontend/src/lib/components/Admin/Agents.svelte +++ b/cptr/frontend/src/lib/components/Admin/Agents.svelte @@ -10,6 +10,7 @@ } from '$lib/apis/admin'; import Icon from '$lib/components/Icon.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; + import ToggleSwitch from '$lib/components/common/ToggleSwitch.svelte'; import { t } from '$lib/i18n'; import { refreshChatState } from '$lib/stores/chat'; import AgentProfileModal from './AgentProfileModal.svelte'; @@ -179,8 +180,7 @@ modal = null; } - function toggleProfile(e: Event, index: number) { - e.stopPropagation(); + function toggleProfile(index: number) { const profile = profiles[index]; if (!profile) return; const mode = profile.mode === 'disabled' ? 'enabled' : 'disabled'; @@ -262,11 +262,12 @@ {:else}
{#each profiles as profile, index (profile.id)} - + toggleProfile(index)} /> +
{/each} diff --git a/cptr/frontend/src/lib/components/Admin/Connections.svelte b/cptr/frontend/src/lib/components/Admin/Connections.svelte index 94730a2..d1c261b 100644 --- a/cptr/frontend/src/lib/components/Admin/Connections.svelte +++ b/cptr/frontend/src/lib/components/Admin/Connections.svelte @@ -8,6 +8,7 @@ import { refreshChatState } from '$lib/stores/chat'; import { t } from '$lib/i18n'; import Spinner from '$lib/components/common/Spinner.svelte'; + import ToggleSwitch from '$lib/components/common/ToggleSwitch.svelte'; let connections = $state([]); let loading = $state(true); @@ -35,9 +36,7 @@ load(); } - async function toggleEnabled(e: Event, conn: Connection) { - e.stopPropagation(); - const newVal = !conn.enabled; + async function toggleEnabled(conn: Connection, newVal: boolean) { // Optimistic update conn.enabled = newVal; connections = [...connections]; @@ -72,37 +71,17 @@ {:else}
{#each connections as conn} - + + toggleEnabled(conn, value)} /> +
{/each} {#if connections.length === 0} diff --git a/cptr/frontend/src/lib/components/Admin/CreateConnectionModal.svelte b/cptr/frontend/src/lib/components/Admin/CreateConnectionModal.svelte index 4c81224..a352ddd 100644 --- a/cptr/frontend/src/lib/components/Admin/CreateConnectionModal.svelte +++ b/cptr/frontend/src/lib/components/Admin/CreateConnectionModal.svelte @@ -17,6 +17,7 @@ let formName = $state(''); let formProvider = $state<'openai' | 'anthropic'>('openai'); let formApiType = $state<'chat_completions' | 'responses'>('chat_completions'); + let formProviderType = $state<'default' | 'llama.cpp'>('default'); let formBaseUrl = $state(''); let formApiKey = $state(''); let formPrefixId = $state(''); @@ -49,6 +50,7 @@ name, provider: formProvider, api_type: formProvider === 'openai' ? formApiType : 'chat_completions', + provider_type: formProvider === 'openai' ? formProviderType : 'default', prefix_id: formPrefixId.trim() || null, base_url: formBaseUrl.trim(), api_key: formApiKey.trim(), @@ -115,6 +117,21 @@ + + + {/if}