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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@ 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.21] - 2026-08-04

### Added

- 📊 **Usage has its own Settings page.** See your token activity, active streaks, favorite models, busy days, and most used tools in one place.
- 🛠️ **Tool approval has its own Admin page.** Admins can choose the review model and decide which built-in tools can run automatically.

### Changed

- 🤖 **Agent chats can start from Home.** Coding agents can now answer even when a chat is not attached to a workspace.
- 🌐 **Browser sessions recover more smoothly.** Browser streaming catches up faster when video falls behind and cleans up more reliably when a session closes.
- 🧰 **OpenCode on Docker is easier to connect.** Computer now tries the right host address when needed and gives clearer setup guidance if it cannot connect.
- 📝 **More C and C++ files open with the right language.** Files ending in `.cc`, `.cxx`, `.hh`, and `.hxx` are now recognized.

### Fixed

- 💬 **Empty replies are retried instead of getting stuck.** If a chat service sends back nothing useful, Computer now tries again before showing a failure.
- 🔊 **Text-to-speech cleans up after playback.** Listening to a message no longer leaves old audio resources around.
- 🧰 **Deleted agent profiles stay deleted right away.** Removing a saved agent now saves the change immediately.
- 💻 **Terminal and browser sessions close more cleanly.** Leaving a session is less likely to leave old listeners or connections behind.
- 📱 **Signal message edits work more naturally.** Updated replies are now sent as edits when Signal supports it.

## [0.9.20] - 2026-08-01

### Added
Expand Down
21 changes: 21 additions & 0 deletions cptr/frontend/src/lib/apis/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,27 @@ export const updateModelConfig = (
method: 'PUT'
});

// ── Tool Approval ───────────────────────────────────────────

export type ToolApprovalPolicy = 'allow' | 'review';

export interface ToolApprovalGroup {
id: string;
tools: {
name: string;
default_approval: ToolApprovalPolicy | null;
}[];
}

export interface ToolApprovalResponse {
default_approval: ToolApprovalPolicy;
overrides: Record<string, ToolApprovalPolicy>;
groups: ToolApprovalGroup[];
}

export const getToolApproval = async (): Promise<ToolApprovalResponse> =>
fetchJSON<ToolApprovalResponse>('/api/admin/tools/approval');

// ── Tool Servers ────────────────────────────────────────────

export interface ToolServer {
Expand Down
37 changes: 37 additions & 0 deletions cptr/frontend/src/lib/apis/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,41 @@ export interface CompactChatResult {
context_usage?: ContextUsage | null;
}

export interface UsageHeatmapEntry {
date: string;
tokens: number;
messages: number;
chats: number;
models: Record<string, number>;
}

export interface UsageResponse {
totals: {
lifetime_tokens: number;
peak_daily_tokens: number;
longest_chat_seconds: number;
current_streak: number;
longest_streak: number;
models_used: number;
user_messages: number;
assistant_messages: number;
messages: number;
total_chats: number;
};
insights: {
average_tokens_per_chat: number;
average_messages_per_active_day: number;
user_message_share: number;
assistant_message_share: number;
};
heatmap: UsageHeatmapEntry[];
weekly_heatmap: UsageHeatmapEntry[];
cumulative_heatmap: UsageHeatmapEntry[];
top_models: { model_id: string; messages: number; total_tokens: number }[];
top_tools: { name: string; count: number }[];
period: { start_date: number; end_date: number; days: number };
}

// ── Queries ─────────────────────────────────────────────────

export const getChats = (
Expand All @@ -96,6 +131,8 @@ export const getChat = (chatId: string, modelId?: string) => {
return fetchJSON<ChatDetail>(`/api/chats/${chatId}${suffix}`);
};

export const getUsage = () => fetchJSON<UsageResponse>('/api/chats/usage');

export const deleteChat = (chatId: string) =>
fetchJSON<{ ok: boolean }>(`/api/chats/${chatId}`, { method: 'DELETE' });

Expand Down
3 changes: 2 additions & 1 deletion cptr/frontend/src/lib/components/Admin/Agents.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
modal = null;
}

function deleteProfile() {
async function deleteProfile() {
const currentModal = modal;
if (currentModal?.mode !== 'edit') {
modal = null;
Expand All @@ -181,6 +181,7 @@
deletedProfile = true;
}
modal = null;
await save();
}

function toggleProfile(index: number) {
Expand Down
28 changes: 0 additions & 28 deletions cptr/frontend/src/lib/components/Admin/Chat.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
let titleGenerationEnabled = $state(true);
let titleGenerationModel = $state<string | null>(null);
let contextCompactionModel = $state<string | null>(null);
let toolApprovalReviewModel = $state<string | null>(null);
let compactTokenThreshold = $state(80000);

onMount(async () => {
Expand All @@ -29,10 +28,6 @@
typeof config['chat.context_compaction.model'] === 'string'
? config['chat.context_compaction.model']
: null;
toolApprovalReviewModel =
typeof config['tool_approval.review.model'] === 'string'
? config['tool_approval.review.model']
: null;
compactTokenThreshold = Number(config['chat.compact_token_threshold']) || 80000;
} catch {
toast.error($t('admin.failedToLoadConfig'));
Expand All @@ -48,7 +43,6 @@
'chat.title_generation.enabled': titleGenerationEnabled,
'chat.title_generation.model': titleGenerationModel,
'chat.context_compaction.model': contextCompactionModel,
'tool_approval.review.model': toolApprovalReviewModel,
'chat.compact_token_threshold': Math.max(10000, Number(compactTokenThreshold) || 80000)
});
toast.success($t('settings.saved'));
Expand Down Expand Up @@ -153,28 +147,6 @@
</p>
</div>
</div>

<h3 class="text-xs text-gray-400 dark:text-gray-600 mb-2 mt-5">
{$t('admin.toolApproval')}
</h3>
<div>
<div class="flex items-center justify-between gap-3">
<span class="min-w-0 text-xs text-gray-600 dark:text-gray-400">
{$t('admin.toolApprovalReviewModel')}
</span>
<div class="shrink-0">
<ModelSelector
bind:selectedModel={toolApprovalReviewModel}
nullable
nullLabel={$t('modelSelector.currentModel')}
preferAbove={false}
/>
</div>
</div>
<p class="text-[0.6875rem] text-gray-400 dark:text-gray-600 -mt-1">
{$t('admin.toolApprovalReviewModelHint')}
</p>
</div>
</div>

<div class="shrink-0 pt-3 flex justify-end">
Expand Down
Loading
Loading