- Prompt Library dialog — reusable prompt manager accessible from chat input toolbar (Zap icon); CRUD for prompts with title, content, and category assignment (
components/prompt-library.tsx,lib/prompts-api.ts) - Category management — create, rename, and delete prompt categories; filter prompts by category with pill-style toggle buttons; default categories seeded on first launch (General, Development, Operations, Research, Writing)
- Prompt pinning — pin/unpin prompts to prioritize them at the top of the list; pinned state persisted server-side
- Drag-and-drop reorder — drag prompts to reorder within the library; sort order persisted via
POST /api/prompts/reorder - Usage tracking — tracks per-prompt usage count and last-used timestamp; displayed on each prompt card
- Import / Export — export all prompts and categories as versioned JSON; import with client-side format validation and error toast on malformed files
- Search — real-time search across prompt titles and content
- New session confirmation — "New Session" button now shows a confirm dialog before starting a new session (prevents accidental session loss)
-
SQLite tables —
prompt_categoriesandpromptstables created withIF NOT EXISTS; Drizzle ORM schema with typed columns (server/db.ts) -
CRUD service —
server/lib/prompts.ts:listCategories,createCategory,updateCategory,deleteCategory(cascade-deletes prompts),reorderCategories,listPrompts,createPrompt,updatePrompt,deletePrompt,recordUsage,reorderPrompts,exportAll,importAll -
API routes — 13 endpoints under
/api/promptsand/api/prompt-categories; all mutating routes requireAuthorization: BearerwhenCK_API_TOKENis set; 404 responses for non-existent prompt IDs on update/delete/use (server/routes/prompts.ts) -
Default category seeding — 5 default categories inserted on first database initialization
-
Slash Commands — type
/to open autocomplete menu with 18 commands; keyboard navigation (↑↓ Tab Enter Esc); arg picker for/think,/fast,/verbose; categorized by Session, Model, Tools, Agents (lib/slash-commands.ts,components/slash-menu.tsx,hooks/use-slash-menu.ts) -
Slash Command Executor — 15 client-side commands executed via Gateway RPC without sending to agent:
/model,/think,/compact,/fast,/verbose,/usage,/agents,/kill,/help,/new,/reset,/stop,/clear,/export,/focus(lib/slash-executor.ts) -
Input History — Arrow Up/Down navigates last 50 sent messages, deduplicates consecutive identical entries (
lib/input-history.ts) -
Chat Export —
/exportgenerates a Markdown file and downloads it (lib/chat-export.ts) -
Message Search —
Ctrl+F/Cmd+Fopens search bar; filters messages by text content; shows match count (components/search-bar.tsx) -
Per-Message Metadata — assistant messages display input/output tokens, cache read/write, cost, context %, and model name on hover (
lib/message-meta.ts,components/message-meta.tsx) -
JSON Auto-Detection — messages containing pure JSON render as collapsible blocks with syntax badge and pretty-print (
lib/json-detect.ts,components/json-block.tsx) -
Welcome State — empty chat shows agent avatar, name,
/hint, and 4 quick-start suggestion buttons (components/welcome-state.tsx) -
Token Estimate — input area shows approximate token count (
~N tokens) when message exceeds 100 characters -
NO_REPLY Filter — assistant messages matching
NO_REPLYare silently hidden from display (lib/silent-filter.ts) -
Speech-to-Text (STT) — microphone button records voice and inserts transcribed text; uses Web Speech API with interim + final transcript support (
lib/speech.ts) -
Text-to-Speech (TTS) — volume button on assistant messages reads content aloud; strips markdown before speaking; stop/cancel support (
lib/speech.ts) -
Pinned Messages — bookmark button on any message; pinned bar above chat with expand/collapse; persisted to localStorage per session (
lib/pinned-messages.ts,components/pinned-bar.tsx) -
Deleted Messages — trash button hides messages client-side; persisted to localStorage per session (
lib/deleted-messages.ts) -
Focus Mode — toggle in chat settings hides sidebar for distraction-free chatting
-
Tool Output Sidebar — resizable split panel; click "View full output" on any tool call to see formatted output; drag handle for width adjustment (
components/tool-sidebar.tsx) -
Session Cache — LRU cache (max 20) for pinned/deleted message state across session switches (
lib/session-cache.ts)
- Professional chat input — redesigned to match OpenClaw UI: card-style container with accent focus glow, subtle toolbar separator, compact icon buttons (
h-7 rounded-md), accent-colored send button with hover shadow - Model Selector — inline model picker in chat input toolbar; lazy-loads model catalog on open; search/filter models; shows current model with provider; switches via
sessions.patchRPC (components/model-selector.tsx) - Slash Command Button —
/icon button in toolbar opens slash menu; active state highlighted; toggle on/off - New Session in toolbar — moved
+button from header to chat input toolbar-right; hidden during streaming (replaced by Stop button); matches OpenClaw layout pattern - Full-width input — removed
max-w-3xlconstraint; input stretches to screen width - Dynamic placeholder — shows
"Message {AgentName} (Enter to send)"instead of static text; switches to"Listening…"during STT recording
- Collapsible agent groups — click agent header to toggle sessions visibility; chevron indicator (
▶/▼); collapsed groups excluded from virtualization - Collapse/Expand all — icon button next to search filter; toggles all agent groups; appears only with 2+ agents; tooltip hint
- Enhanced session items — two-line layout (label + preview/subject); relative time (
just now,5m ago); token count right-aligned; selected state with subtle shadow - Improved spacing — consistent
px-2.5padding on virtual list items and container; no more edge-clipping
- Export Button — download icon in chat input toolbar; triggers
exportChatMarkdown()to save conversation as Markdown file; hidden during streaming - Search Button — magnifying glass icon in chat input toolbar; opens search bar overlay; synced with
Ctrl+F/Cmd+Fkeyboard shortcut
- Inline session rename — double-click session label in chat header to edit; Enter to save, Escape to cancel; calls
sessions.patchRPC withlabelfield; toast feedback on success/failure
- Scroll lock — lock/unlock toggle near scroll area; when locked,
ChatContainerScrollAnchoris unmounted to pause auto-scroll during streaming; visual indicator shows lock state
- Session hover actions — hovering a session item in sidebar reveals compact and delete action buttons overlaying the time/token area; uses
group-hoverTailwind pattern; compact callssessions.compactRPC with toast feedback
- Removed duplicate Stop button — single Stop button in chat input toolbar only; header shows
"Generating…"shimmer text indicator - Removed Reset button — reset available via
/resetslash command; reduces header clutter
messageKey()— stable key generation for chat messages usingid > messageId > toolCallId > timestamp+role+index; mirrors OpenClaw UI'smessageKey()(ui/src/ui/views/chat.ts:1467); used by pin and delete features for correctness across history reloads
- Chat lib unit tests — 59 new tests across 7 files:
input-history(9),json-detect(9),silent-filter(6),pinned-messages(6),deleted-messages(6),slash-commands(15),messageKey(8); total test count 342 → 401 - Coverage config — added 6 chat lib files to
vitest.config.tscoverage include
- Shiki highlighter — replaced
Promise<any>+as anywith properAwaited<ReturnType<>>typing; zeroas anycasts remaining in codebase (lib/shiki.ts) - SlashMenu memo stability — destructured stable
useCallbackrefs fromuseSlashMenu()hook into individual deps;SlashMenumemo now works correctly instead of re-rendering every keystroke - Clipboard error handling — added rejection handler to
navigator.clipboard.writeText(); prevents unhandled promise rejection in non-HTTPS contexts - Input ref timing — captured
rawInputfrom ref beforesetInputValue('')to eliminate dependency on React batching internals - Copy timeout cleanup —
setCopied(false)timeout tracked via ref and cleared on unmount; prevents setState-after-unmount warning - Drag resize optimization — tool sidebar
handleMouseDownuseswidthRefinstead ofwidthin closure; callback stable with[]deps during drag - STT listener cleanup — speech recognition event listeners tracked and explicitly removed in
stopStt() - Agent load error toasts — failed identity, config, workspace, and model list loads now show
toast.error()instead of silentlog.warn
GatewaySessionRow— addedspawnedBy?: stringfield; tracks which session spawned a sub-agent session
- Gateway Identity card — new
GatewayIdentityCardcomponent in sidebar footer; fetchesgateway.identity.getRPC on connect; displays truncateddeviceIdandpublicKeywith full-value tooltips; click-to-copy with success confirmation - Open Config button — "Config" quick action in dashboard header; triggers
config.openFileRPC to open the gateway config file in the system's default editor - Fast Mode toggle —
PatchSessionDialoggains aZapicon +Switchtoggle forfastMode; value sent withsessions.patchRPC and synced from session state
BrowserStatus— addeddriver?: 'openclaw' | 'extension' | 'existing-session'andtransport?: 'cdp' | 'chrome-mcp'; updatedcdpPorttonumber | nullandcdpUrltostring | null | undefined; matches upstreamsrc/browser/client.tsConnectErrorDetailCodes— addedCONTROL_UI_ORIGIN_NOT_ALLOWED; matches upstreamsrc/gateway/protocol/connect-error-details.ts
ConnectErrorDetailCodes— addedAUTH_BOOTSTRAP_TOKEN_INVALID(new in v2026.3.12); matches upstreamconnect-error-details.tsisNonRecoverableAuthError— addedAUTH_BOOTSTRAP_TOKEN_INVALIDto the non-recoverable list; prevents infinite reconnect loops when gateway rejects an invalid bootstrap token; order matches upstreamui/src/ui/gateway.tsGatewaySessionRow— addedfastMode?: booleanfield; matches upstreamSessionsPatchParamsSchemaaddition in v2026.3.12SessionsPatchResult.entry— addedfastMode?: booleanfield
- Exec Approval Bell — new
ExecApprovalBellcomponent in the global status bar; real-time exec approval queue powered byexec.approval.requested/exec.approval.resolvedgateway events - Approve / Deny actions — per-request allow-once, allow-always, and deny buttons via
exec.approval.resolveRPC; auto-expire entries based on server-provided TTL - Timer dedup guard — duplicate
entry.idevents are rejected before timer creation to prevent leaked timers
UpdateAgentDialog— edit agent name and model viaagents.updateRPC; accessible from agent overview; validates non-empty changes; shows "No changes to save" toast on no-op
- Compact button — per-session compact action in session cards via
sessions.compactRPC; disabled state with "Compacting…" label during operation; success/info toast feedback
- Wake Agent — "Wake Agent" button triggers
wakeRPC ({ mode: 'now', text: 'Manual wake from ClawKernel dashboard' }) - Update Gateway — "Update Gateway" button triggers
update.runRPC with confirm dialog; shows restart countdown toast
GatewayShutdownBanner— layout-level banner displayed when the gateway broadcasts ashutdownevent; shows reason text + countdown timer until expected restart; auto-clears on successful reconnect (hello-ok); usesuseReffor timestamp tracking to handle repeated shutdown cycles correctly
- Device auth v3 —
buildDeviceAuthPayloadV3andnormalizeDeviceMetadataForAuthadded todevice-auth.ts; client now signs with v3 payload format (includesplatformanddeviceFamily); server falls back to v2 if v3 verification fails - Display name — connect params now send
displayNamefromopts.clientName(defaults toClawKernel); previously hardcodedWebClaw - Snapshot type realigned — removed 7 phantom fields (
agents,sessions,channels,config,skills,cron,models); added 6 upstream fields (stateVersion,uptimeMs,configPath,stateDir,sessionDefaults,authMode);presenceandhealthnow required (matchingSnapshotSchema) GatewayHelloOktype corrected —server,features,snapshot,policymarked required;authsub-fields (deviceToken,role,scopes) required whenauthis present;policyincludesmaxPayloadandmaxBufferedBytes- Presence type expanded — added 8 upstream fields (
deviceFamily,modelIdentifier,reason,tags,text,deviceId,roles,scopes);tsnow required ChannelAccountSnapshotparity — removed 5 speculative fields (credentialSource,audienceType,audience,webhookPath,webhookUrl); added 3 upstream fields (busy,activeRuns,lastRunActivityAt); matchesChannelAccountSnapshotSchemaexactlyupdateAvailabletype — removed erroneous| nullunion; upstream usesType.Optional(Type.Object(...))only
- Cron event — replaced dead
cron.status/cron.jobshandlers with singlecronhandler; re-fetches via RPC on event (matching upstream broadcast pattern atserver-cron.ts:359) - Device pairing events —
pairing-bell.tsxnow subscribes to livedevice.pair.requested/device.pair.resolvedevents in addition to polling - Shutdown + update.available events — new store fields
gatewayShutdownandgatewayUpdateAvailable;gatewayShutdowncleared on reconnect - Dead handler cleanup — removed 4 event handlers for events upstream never broadcasts (
sessions,config,channels,skills) - Presence event — correctly unwraps
{ presence: Array<...> }envelope (matchingpresence-events.ts:11)
isNonRecoverableAuthError— added 3 missing upstream error codes:PAIRING_REQUIRED,CONTROL_UI_DEVICE_IDENTITY_REQUIRED,DEVICE_IDENTITY_REQUIRED; prevents infinite reconnect loops on these errorsConnectErrorDetailCodes— expanded from 4 to 7 codes to match upstreamconnect-error-details.tsGatewayRequestErrorclass — matches upstreamui/src/ui/gateway.ts(gatewayCode+details)- Connect params — sends
caps: ['tool-events']capability;instanceIdincluded in client info
chat.injectparams — corrected from{ sessionKey, role, content }to{ sessionKey, message }(matchingChatInjectParamsSchema)- Event handlers use injected
set—handleCronEventno longer callsuseGatewayStore.setState()directly presenceArrayToRecord— replacedarr.indexOf(entry)fallback (O(n²)) with loop counter (O(n))- Navigator reference —
getBrowserNavigator()called once and shared between auth payload and connect params; prevents platform divergence in v3 signature verification - Pairing bell cleanup — simplified
unsubsarray to singleunsubvariable with?.()cleanup - Agent overview — added
.catch()to fire-and-forgetagents.listrefresh inonUpdated compactingKeyref guard —useCallbackdependency usescompactingRefinstead of stale closure overcompactingKeystate
- DM policy enum —
allow/deny→pairing/allowlist/open/disabled(matchingDmPolicySchemaatzod-schema.core.ts:313) - Group policy enum —
allow/mention/deny→open/disabled/allowlist(matchingGroupPolicySchemaatzod-schema.core.ts:315)
@noble/ed25519— updated to^2.3.0
- Usage page — new route
/usage; nav item (ChartColumn) in sidebar; lazy-loaded withPageErrorBoundary - Manual fetch workflow — page loads idle; user selects date range (or preset: Today, 7d, 30d) and clicks Refresh; empty state with guidance message; matches OpenClaw's pattern
- Date range controls — start/end date inputs, preset buttons, timezone toggle (local/UTC), chart mode toggle (tokens/cost)
- Summary cards — 9 metric tiles: sessions, messages, tool calls, errors, unique tools, active agents, avg latency, avg duration, cache hit rate
- Daily usage chart — bar chart with tokens/cost toggle; stacked token breakdown (input, output, cache read, cache write)
- Activity heatmap — weekday and hourly heatmap panels showing token distribution and session intensity
- Session table — virtualized list (
@tanstack/react-virtual) with redesigned card layout: agent name (primary), session label, channel/model tags, icon stat chips (messages, tools, errors, duration), tokens/cost, last active; sort by recent/tokens/cost/errors; copy session key on hover - Session detail panel — click any session to expand: summary cards (messages, tool calls, errors, duration), top tools list, model mix breakdown, usage over time chart (per-turn/cumulative + total/by-type toggles with stacked color bars), conversation logs (role filter, search, expand/collapse all), system prompt breakdown (stacked bar + expandable skills/tools/files cards with chars-to-tokens estimation)
- Time series chart — CSS bar chart rendering per-turn and cumulative token usage; stacked type breakdown (output rose, input amber, cache-write emerald, cache-read cyan); fetched via
sessions.usage.timeseriesRPC - Conversation logs — fetched via
sessions.usage.logsRPC; role-based color coding (user blue, assistant emerald, tool amber, toolResult purple); expandable long messages; filter by role + text search - Context breakdown — system prompt weight analysis from
contextWeightdata; percentage-of-input calculation; expandable cards for skills, tools, and injected workspace files - Insights panels — 6 insight categories: models, providers, agents, channels, tools, peak error days; each shows ranked list with tokens/cost/count
- Detailed breakdown tables — model table and agent table with full token/cost totals
- Client-side filtering — faceted filters (agent, channel, provider, model, tool) + free-text search with
useDeferredValue; filter options ranked by token volume - Export — JSON snapshot export of current filtered view with proper DOM attachment for Firefox compatibility
- Race condition guards —
requestIdReffor main fetch,sessionDetailRequestIdReffor session detail; prevents stale response rendering - Missing cost warning — banner shown only after fetch when usage entries lack cost metadata
- Gateway compatibility — automatic fallback for legacy gateways that don't support
dateInterpretationparams; per-gateway key remembered in localStorage
SessionUsageTimePoint— 9-field type matching OpenClaw'ssession-usage-timeseries-types.ts(timestamp, input, output, cacheRead, cacheWrite, totalTokens, cost, cumulativeTokens, cumulativeCost)SessionUsageTimeSeries—{ sessionId?, points }for time series RPC responseSessionLogEntry—{ timestamp, role, content, tokens?, cost? }for conversation log entriesSessionContextWeight— full system prompt report type (systemPrompt, skills, tools, injectedWorkspaceFiles)UsageSummaryStats— 17-field computed overview stats typeUsageSessionRow— display-ready session row with resolved agent name, labels, and computed fields
analytics.ts— session filtering, facet option building, overview stats aggregation, daily points, activity heatmap, 6 insight builders (model, provider, agent, channel, tool, peak error day), session row sortingutils.ts— token/cost/latency/duration/date formatters, date range presets, model/agent row builders, context percent calculation, gateway compatibility helpers, error message extraction
tests/unit/usage-utils.test.ts— unit tests forbuildModelRows,buildAgentRows,filterSessionsByPeriod, formatters, date range utilitiestests/unit/usage-analytics.test.ts— unit tests forbuildUsageOverviewStats,buildUsageFacetOptions,filterUsageSessions,buildUsageDailyPoints,buildUsageActivityHeatmap, insight builders- Coverage whitelist —
src/app/usage/utils.tsandsrc/app/usage/analytics.tsadded tovitest.config.tsinclude list
@xyflow/react(^12.10.1),dagre(^0.8.5),@types/dagre(^0.7.54) — added for hierarchy graph rendering and auto-layout
- Agent Hierarchy view — interactive graph visualization of agent fleet using ReactFlow + dagre auto-layout; toggle between Cards and Hierarchy views when 2+ agents exist
- 5 layout modes — Compact (adaptive columns), Balanced (split columns), Delegation (parent/child clarity), Channel (binding analysis), Workspace (ownership clusters); persisted in localStorage; popover selector with descriptions
- Node types — Gateway hub (agent count), Agent nodes (status dot, model, session/token stats, default badge), Channel nodes (icon + account IDs), Workspace nodes (path + owning agents)
- Edge types — Gateway→Agent (solid), Agent→Agent delegation (animated dashed green), Channel→Agent bindings (blue), implicit default route (dashed muted), Agent→Workspace (amber)
- Agent detail dialog — click any agent node to open a full detail panel with identity, activity stats, configuration (model, tool profile, workspace), channel bindings, and delegation tree (parent + sub-agents)
- Layout persistence — dragged node positions saved to localStorage; Reset button to recompute layout; positions cleared on layout mode switch
- Fit-to-view — auto-fits on mount; ReactFlow controls (zoom, pan) with themed styling
computeAgentSessionStats— extracted session stat computation (count, activeCount, tokens, lastActive) toutils.ts; used by both cards view and hierarchy view, replacing the inline duplicate inindex.tsxformatTokens,formatAgo,shortPath,channelIcon— shared formatting helpers extracted toutils.ts; used by hierarchy view and detail dialog
subagentsfield — addedsubagents?: string[] | { allowAgents?: string[] }toParsedConfigagent list entry type; matches OpenClawtypes.agents.tsschema for delegation configuration
@xyflow/react(^12.10.1),dagre(^0.8.5),@types/dagre(^0.7.54) — added for hierarchy graph rendering and auto-layout
- View mode toggle — header toolbar gains a Cards/Hierarchy toggle button (visible when 2+ agents); hierarchy view replaces the cards+tabs layout with the full-height graph
- Session stats refactor — inline
sessionsByAgentIdcomputation inindex.tsxreplaced with sharedcomputeAgentSessionStatscall; field names normalized (all→count,active→activeCount)
- Unclickable layout buttons — Popover trigger and Reset button were independently
absolute-positioned inside<ReactFlow>, which captures pointer events on its internal pane; wrapped both in a singledivwithz-50 pointer-events-autoto lift above the event pane - Double dagre layout computation —
extractPositions()redundantly calleddagre.layout()even though every layout function already called it before passing the graph; removed the duplicate call - Misleading
_handleLayoutModeChangeprefix — renamed tohandleLayoutModeChange; underscore prefix conventionally signals unused variables but this callback is actively used
- Vitest test runner —
vitest+happy-dom+@testing-library/react+@vitest/coverage-v8added. - Test config — new
vitest.config.tswith@alias resolution and focused coverage include list for core business-logic files. - Comprehensive test suite — added
tests/tree:- Unit tests: cron, chat utils, gateway client/store, sessions utils, tool policy, device auth, formatting, text direction, agent status/utils, config utils.
- Integration tests:
use-chathook behavior.
- NPM scripts —
test,test:watch,test:coverageadded topackage.json.
- GitHub Actions — CI now runs
npm testafterknipand beforebuild.
- Coverage artifacts — added
coverageto.gitignore.
GatewayClientOptions— addedconnectFallbackMs?: numberto support deterministic connect-fallback timing in tests.GatewayClient.sendConnectSoon()— fallback timer now respectsopts.connectFallbackMs(defaults to existing constant when not provided).
stripThinkingTagsexport — made public for direct unit testing of parser edge cases.
- handleLoadMore streaming guard — early return when
chat.runId !== nullprevents message list corruption during active streaming (was: load-more replaced optimistic + stream placeholder messages → duplicate messages) - handleRetry busy guard — added
isBusyRef.currentcheck; prevents parallelchat.sendcalls during active streaming - handleAbort per-frame re-render — uses
runIdRef.currentinstead ofchat.runIdclosure; deps reduced to[client, selectedSession]; eliminates callback re-creation on every streaming delta - handleRetry consolidation — refactored to call
executeSendinstead of duplicating try/catch/request logic; removed fragilefinallyblock (double setState on error path) - Blob URL leak on image compression failure —
previewhoisted above try block;URL.revokeObjectURL(preview)called in catch - Settings schema migration —
localStoragesettings merged withDEFAULT_CHAT_SETTINGSconstant; missing fields from older schema get correct defaults instead ofundefined
- Thinking block extraction —
extractThinkingnow collects all thinking blocks and joins with\n\n(was: only first block,breakon match) - Stateful regex footgun — removed
/gflag from module-levelFILE_BLOCK_REconstant; added at call sites vianew RegExp(source, 'g') - Floating-point quality guard — compression loop guard changed from
> 0.3to> 0.31; avoids one extra compression pass from0.30000000000000004 > 0.3 - generateId uniqueness — fallback (no
crypto.randomUUID) now usescrypto.getRandomValueshex; final fallback addsMath.random()suffix toDate.now()for same-millisecond uniqueness - replaceAll with /g regex — changed redundant
replaceAll(new RegExp(..., 'g'))toreplace(new RegExp(..., 'g'))
- Timer leak across connection cycles — compaction/fallback timer handles stored in module-level variables;
clearStatusTimers()called indisconnect(); previous timers cleared before scheduling new ones - Silent eager-fetch errors — added
log.warnfor non-scope/permission errors inconnect()catch block - Stale run indicator — increased
STALE_RUN_MAX_AGE_MSfrom 30s to 120s; matches Gateway's minimum agent timeout for tool-heavy runs - Event handler indirection —
STORE_EVENT_HANDLERSuses direct function references instead of arrow wrappers
- Reconnect jitter —
secureRandomUnitfallback changed from deterministic0.5toMath.random(); prevents thundering herd whencrypto.getRandomValuesunavailable - Binary encoding APIs — reverted
codePointAt/fromCodePointtocharCodeAt/fromCharCodeindevice-identity.ts; correct API for binary byte data (0-255)
- Removed
openBrowser— deleted auto-browser-open feature (--open/-oCLI flag,CK_OPEN_BROWSERenv,spawnimport, Windowscmd.execode path)
- Complexity reduction —
stripThinkingTagsrewritten from regex state machine to manual parser (smaller functions, no global regex state); component JSX ternaries pre-computed as variables - Type safety —
window.__CK_CONFIG__access replaced with typedgetRuntimeConfig()helper;navigatoraccess viagetBrowserNavigator()withNavigatorWithUADatatype;(navigator as any)eliminated - Index keys eliminated — array index keys replaced with content-based stable keys in
bubble.tsx,session-sidebar.tsx; IIFE pattern refactored to pre-computedkeyedImages/keyedFiles - Base64 overhead comment — documented
TARGET_SIZE * 1.37as// Base64 overhead ratio (~4/3)
extractAgentId/sessionLabel— deleted fromchat/utils.ts;sessionLabeladded tosessions/utils.ts; imports updated inuse-chat.tsandchat/index.tsx- Unused
_settingsparam — removed fromgroupMessages;ChatSettingsimport removed fromchat/utils.ts
- Removed 31 low-value comments (obvious restatements, redundant separator labels, self-documenting component names) across 16 files; preserved all security, protocol, and domain rationale comments
- Web Search page — new route
/search; nav item (Search icon) in sidebar; wrapped withPageErrorBoundary - Provider Status Bar — 4 tiles from
config.get: Active Provider (accent only when that provider's key is configured), Model, Cache TTL, Keys Configured (n/5 from config-stored keys only; full env-var detection) - Provider Cards — 5 cards for all supported providers:
brave · perplexity · grok · gemini · kimi; configured status fromconfig.get; active badge; env var name shown; Perplexity card notes OpenRouterbaseUrloverride - Model Selector — shown when active provider has a model field; Perplexity: 3 preset buttons (sonar / sonar-pro / sonar-reasoning-pro); Grok / Gemini / Kimi: free-form text input with documented default as placeholder; saves via
config.patchmerge; re-syncs when config refreshes externally - Search Playground — agent dropdown (from gateway store) + session dropdown (filtered by agent); query input + result count (1 / 3 / 5 / 10); CLI equivalent preview with copy button; runs search via
chat.send(notchat.inject— which does not trigger agent processing); streams response via Gatewaychatbroadcast events; result panel shows status badge · provider · agent · duration and renders response as markdown (same renderer as/chat) - No-provider warning banner — shown when zero API keys are set in config; includes setup guide link
PROVIDER_LISTconstant insearch/types.ts— single source of truth for the 5-provider ordered list; used by provider cards, warning banner, and status bar- Load error state — inline error panel when
config.getfails on initial load (instead of blank page);loadErrortracked inuseSearchConfig - Gateway types —
WebSearchProvider,WebSearchConfig,PlaygroundStateadded tosrc/app/search/types.ts
- Browser page — new route
/browser; nav item (Globe) in sidebar - Browser Status card — probes
browser.request GET /on load; shows Running · CDP Ready · Profile · Browser tiles; disabled state (browser control off) with docs link - Request Panel — method selector (GET/POST/DELETE) · path input · query JSON textarea · body JSON textarea (POST only) · Send button →
browser.request; preset buttons for common routes - Response viewer — shows success (200 OK) or error with JSON-prettified body; updates on every send
- History — last 20 requests (newest-first) with method · path · status · duration; click any entry to restore into panel
- Gateway types —
BrowserStatusadded togateway/types.ts
- Audio page — new route
/audiowithPageErrorBoundary; nav item (Volume2) in sidebar - TTS Status card — enabled toggle via
tts.enable/tts.disable; auto-mode display (read-only; Enable → always, Disable → off); provider fallback chain; API key status for OpenAI, ElevenLabs, Edge TTS - TTS Providers section — provider cards from
tts.providers; expand to show models and voices; "Set Active" viatts.setProvider; active provider highlighted - Voice Sample Lab — text presets + textarea →
tts.convert { text }→ shows provider, format, and server-side output path (browser playback requires backend) - Wake Word card — triggers list from
voicewake.get; inline add/remove + save viavoicewake.set { triggers } - Talk Config card — read-only display from
talk.config: voiceId, modelId, outputFormat, interruptOnSpeech, voiceAliases, seamColor; empty state with setup guide link - Slash commands accordion — reference panel for
/ttsslash commands (collapsed by default) - Gateway types —
TtsAutoMode,TtsStatus,TtsProvider,TtsProvidersResult,TtsConvertResult,TalkConfigPayload,TalkConfigResult,VoiceWakeResultadded togateway/types.ts
- Global skills page —
skills.statusper agent; groups by source (Workspace · Built-in · Installed · Other) - Stats bar — 5 tiles: Total · Ready · Needs Setup · Blocked · Disabled
- Availability badges — Ready (green) / Needs Setup (amber) / Blocked (red) / Disabled (muted)
- Grid + list view — toggle between card grid and compact list; collapsible source groups
- Enable / Disable — inline toggle per skill via
skills.update { skillKey, enabled } - Install dependencies — per-install-option buttons via
skills.install { name, installId, timeoutMs: 120s } - API key input — inline form for missing
envrequirements viaskills.update { skillKey, apiKey } - Agent selector — dropdown to view skills for any configured agent (multi-agent setups)
- Search + filter — text search across name/description/key; status filter (All / Ready / Needs Setup / Blocked)
- Detail panel — slide-over with full description, status toggle, requirements, missing items, install options, API key form, homepage link, source path
- Routing section — displays default model + fallback chain + image model + image fallbacks from
config.get - Aliases table — model ID → alias mapping from
agents.defaults.models - Provider status — per-provider cards showing API key configured/missing from config
- Available models — full catalog from
models.listgrouped by provider: name · ID · context window · reasoning · vision badges - Model search — client-side filter across name/ID/provider
ModelCatalogEntrytype added to gateway types (id · name · provider · contextWindow · reasoning · input)
- 6 metric tiles: Gateway status, Latency (color-coded: green < 50ms, orange < 200ms, red > 200ms), Agents, Sessions, Channels, Cron (active/failing count)
- Today's Cost banner — fetches
usage.cost(last 1 day) on connect; shows total cost + token count with link to/usage - Gateway Latency tile — round-trip ms measured via
healthprobe on connect - Cron Summary tile — enabled job count + failing job count from store; status color reflects failing state
- Quick Actions — "New Agent" button (reuses
CreateAgentDialog) + "Open Chat" navigation button in dashboard header
- Toast with agent name + 120-char message preview when a chat
finalevent arrives while the user is not on/chat; auto-dismiss after 6s; "Open" action navigates to/chat
- 12h/24h toggle stored in
localStorageviauseSyncExternalStore; click the status bar clock to switch; applied to dashboard session timestamps
- Channel Status Grid — card per channel with connection status dot (green/yellow/red), account pills, last error display, auto-refresh every 30s
- Channel Settings — DM policy (pairing/allow/deny) and Group policy (allow/mention/deny) selectors via
config.patch - Token Setup — inline token input for Telegram, Discord, Slack with show/hide toggle; saves via
config.patch→ gateway auto-reconnects - WhatsApp/Signal QR Login — modal with
web.login.start→ QR code display →web.login.wait(120s timeout) → success/error feedback - Device Pairing Management — pending requests (approve/reject) + paired devices table (rotate token, revoke token, remove device) via
device.pair.*anddevice.token.*APIs - DM Pairing Queue — contacts awaiting approval per channel; approve adds to
allowFromviaconfig.patch - Pairing Bell — global bell icon in status bar with pending request count badge; popover dropdown with approve/reject actions; polls
device.pair.listevery 15s (visibility-aware); bounce animation on new requests - Channel Setup Wizard — 3-step dialog (Choose → Configure → Done); "Add Channel" button on page header; shows all 5 supported channels (Telegram, Discord, Slack, WhatsApp, Signal) with Configured/Needs setup badge; token input + QR trigger; post-setup checklist per channel with docs links
- Enable/Disable Toggle — per-channel power button with confirm dialog;
config.patch→ root + account-levelenabled; toast "gateway restarting…"; disabled cards at 60% opacity + "Disabled" label - New route:
/channelswith sidebar navigation - New UI primitive:
Selectcomponent (radix-uibased)
- Global Cron Hub — dedicated page listing all cron jobs across all agents with real-time stats bar (scheduler status, total/enabled/failing counts, next wake timer)
- Job Cards — collapsed view with status dot, human-readable schedule, agent label, next-run countdown, delivery warning badge; quick actions (enable/disable, run now, edit, delete)
- Expanded Job Detail — configuration panel, delivery config, 4 execution tiles (last run, next run, duration, status with consecutive-error count), prompt preview, paginated run history
- Create Job Wizard — 5-step dialog (Basics → Schedule → Payload → Delivery → Review); 11 schedule presets + custom cron/interval/one-shot; agent selector; model override + thinking level; announce delivery with best-effort flag
- Inline Edit Form — flat layout with schedule, payload, delivery, and flag editors; opens below the card header
- Failure Guide — pattern-matched error diagnosis with actionable fix steps; covers delivery-target, auth, model, timeout, and network errors; technical details collapsible
cronToHuman()formatter — human-readable schedule labels (0 8 * * *→ "Daily at 8:00 AM"); respects 12h/24h preference- Run History — per-job paginated list with status icons, timestamps, duration, model, session ID; expandable detail view with summary, session key, and delivery status
- URL deep-linking —
?job=<id>scrolls to and highlights a job;?show=errorsauto-expands the first failing job - Sorting & filtering — sort by next run / last updated / name (asc/desc); filter by all / enabled / disabled; search by name
- Shared cron formatters extracted to
src/lib/cron.ts—formatSchedule,formatRelative,formatDuration,cronToHuman,buildFailureGuide - New route:
/cronwith sidebar navigation
- Hono server — new
server/index.tscompiled via esbuild tobin/server.mjs(12.7 KB);bin/clawkernel.mjsnow spawns the Hono server instead of running its ownhttp.createServer; config passed viaCK_*environment variables; dev mode auto-reads~/.clawkernel.jsonwhen env vars are absent - SQLite database —
better-sqlite3+ Drizzle ORM;~/.clawkernel.dbcreated on startup with WAL journal mode; three tables:preferences(key-value UI settings),token_alarms,usage_history - API routes:
GET /api/health— server health ({ ok, version, uptime })GET /api/version— checksregistry.npmjs.org/clawkernel/latestfor updates; 1-hour in-memory cache; returns{ current, latest, updateAvailable, isDismissed }POST /api/version/dismiss— persists dismissed version in preferences table; Zod-validated input ({ version: string })POST /api/gateway/restart— runsopenclaw gateway restartviachild_process.execFilewith 35s timeoutGET /api/prefs— returns managed preferences as key → value mapPATCH /api/prefs— updates preferences; Zod.strict()schema rejects unrecognized keys; allowed keys:auto_restart_gateway,dismissed_update_versionPOST /api/channels/setup— stub (501) pending CLI API verification
- API auth — optional
CK_API_TOKENenv var; when set, all mutating endpoints (POST/PATCH) requireAuthorization: Bearer <token>header; GET endpoints remain public; auth status shown in server startup banner - Async static file serving —
fs/promises(readFile+stat) in request handler; sync I/O only at startup; path traversal protection viapath.resolve+startsWith(DIST)guard - Dev fallback page — when
dist/index.htmlis missing (e.g.npm run dev:serverbefore build), serves a minimal HTML page with clear instructions instead of crashing - Update Banner — dismissible banner in app layout when a newer ClawKernel version is available on npm; persists dismissed version server-side; 1-hour localStorage cache on the client; uses theme
primarycolor tokens - Restart Bar — announcement bar using theme
warningcolor tokens; triggered viauseRestartBarStore.getState().show()from any component after config changes requiring manual restart; callsPOST /api/gateway/restart; auto-hides on success; dismiss button for manual close - Dev experience:
npm run devruns both Vite (:5173) and Hono (:4174) viaconcurrently; Vite proxies/api/*to the Hono backend- Vite plugin injects
window.__CK_CONFIG__from~/.clawkernel.jsonduring dev (production injection done bybin/server.mjs) - Dev mode: Hono prints a single dim line (
API server ready on ... (proxied by Vite)) instead of the full production banner - Dev mode: non-API requests to
:4174redirect to Vite:5173— prevents accidental use of the wrong port and losing HMR
- TypeScript server config —
tsconfig.server.jsonadded to project references;tsc -bandnpm run typechecknow validate both frontend and server code - Biome + Knip —
npm run checkandnpm run knipnow includeserver/directory engines.nodeupdated from>=18to>=20— aligns withbetter-sqlite3v12 pre-built binary support- Dependencies:
hono,@hono/node-server,better-sqlite3,drizzle-orm,zod(runtime);@types/better-sqlite3,drizzle-kit,esbuild,tsx,concurrently(dev)
config.patchhash-conflict retry —isHashConflict()now matches all three Gateway error variants, including"config changed since last load; re-run config.get and retry"(the most common stale-hash message, which doesn't contain the word "hash"). All threeconfig.patchcall sites (channel-card,channel-settings,setup-wizard) usepatchConfigWithRetryand auto-retry once with a freshbaseHashon conflict.
formToDeliverymode=none — previously returnedundefined, which was absent in JSON and left an existing delivery intact on update; now correctly returns{ mode: 'none' }so the Gateway clears delivery when the user selects "No delivery"staggerMspreserved on edit —CronScheduletype,formToSchedule, andjobToForm(viascheduleToForm) all carrystaggerMs; editing a job with a non-zero stagger no longer silently drops the valueuseCronRunsrace condition —setLoading(false)/setLoadingMore(false)moved to afinallyblock; a stale-jobreturninsidetryno longer leaves the loading spinner stuck indefinitely- Wizard dead network call — removed a
useEffectthat calledagents.liston wizard open but discarded the result; agent list is already available from the gateway store snapshot cron.listredundant param — removedincludeDisabledfrom the request; theenabledfilter param is the canonical API, and sending both is redundant (verified againstserver-methods/cron.ts)- Wizard review — unconditional ellipsis — payload preview now appends
…only whenpayloadText.length > 80; short messages no longer show a trailing ellipsis - Unused import — removed
AgentsListResultimport fromcreate-job-wizard.tsx(leftover after the dead network call was removed)
- Agent Cron tab now uses shared
cronToHuman()formatter with 12h/24h support - Gateway types updated:
CronDelivery,CronJobsListResult,CronRunsResult,CronDeliveryStatus, pagination support forcron.listandcron.runs - Dashboard refactored from monolithic component into feature-based structure:
components/,hooks/,types.ts MetricTileextracted as reusable componentSessionsCardandPresenceCardextracted as standalone components- Status bar clock clickable to toggle 12h/24h format; respects stored preference
- Pairing Bell added to global status bar — always visible
cronToHuman(lib/cron.ts) — extracted 7 named pattern-matcher functions (matchEveryNMinutes,matchEveryNHours, …,matchSingleWeekday); main function now delegates via a matchers array — complexity 28 → 4JobCard(cron/job-card.tsx) — extractedstatusDotClass,lastStatusClass,lastStatusLabel,tileStatusClass; removes all nested status ternaries from render — complexity 32 → ~12AgentChannels / ChannelCard(agent-channels.tsx) — extractedchannelBorderClass,iconBoxClass,barClass,dotClass; eliminates repeated 3-branch ternaries — complexity 23 → ~8AgentActivity(agent-activity.tsx) — extractedfetchJobRunsasync helper; flattens Promise chain from 5 nesting levels to 2; resolves S2004 (nested functions > 4 levels)ChatBubble(chat/components/bubble.tsx) — extractedresolveDisplayContent; removes complex multi-&&guard from render function — complexity 17 → ~10ChatPagekeyboard handler (chat/index.tsx) — extractedhandleEscapeKey(ctx)with typedEscapeContext; removes 4-level nesting fromuseEffect— complexity 17 → 3AgentComparison / getAgentData(agent-comparison.tsx) — extractedcomputeAgentStatus; replaces 3-level nested ternary with 4 clear early-return branches — complexity 18 → ~12AgentOverview / isDirty(agent-overview.tsx) — IIFE(() => {...})()converted touseMemowith explicit dependency array — complexity 19 → ~14DmPairingQueue(channels/dm-pairing-queue.tsx) — extractedcollectDmRequests(channels); moves triple-nested loop out of component body — complexity 16 → ~6sendRequest(browser/hooks/use-browser.ts) — extractedtryParseJson(raw, errorMessage); removes two try/catch blocks from the main request function — complexity 16 → ~11parseIdentityMd(agents/dialogs/edit-identity-dialog.tsx) — replaced 6 sequentialif (label === '...')assignments with aIDENTITY_FIELD_MAPlookup table — complexity 17 → ~5buildSessionTree(sessions/utils.ts) — extractedfindParentKey(s, keySet); reduces 3-level nesting to 2 — complexity 17 → ~12useChatToastnavigate (hooks/use-chat-toast.ts) —{ void navigate('/chat') }simplified to{ navigate('/chat') }— removes S3735 void operator warningreadonlyprops — all component props types and interfaces across 84 files now explicitly mark every property asreadonly; prevents accidental mutation and makes data flow intent clear at the type level- Re-export syntax (
audio/types.ts,browser/types.ts) —import { X } from '…'+export { X }pairs replaced with directexport { X } from '…' - Redundant type assertions removed — 11 unnecessary
as SomeTypecasts removed acrossagents/,chat/,cron/,audio/files; TypeScript already inferred the correct types Number.parseInt(tts-settings-card.tsx) —parseInt(…, 10)replaced withNumber.parseInt(…, 10)
uniqueAgentssort (use-sessions-page.ts) —.sort()replaced with.sort((a, b) => a.localeCompare(b)); previous call produced locale-inconsistent ordering across environments
flushQueueReftype mismatch (use-chat.ts) —flushQueueRefis typed as() => voidbutflushQueueisasync; assignment now wraps with() => { void flushQueue() }to discard the Promise intentionally and satisfy the type contractuseChatToastnavigate in void context (use-chat-toast.ts) —onClick: () => navigate('/chat')returned thePromise<void>from react-router v7'snavigate; fixed to() => { void navigate('/chat') }to explicitly discard the return value
normalizeAgentIdregex precedence (agents/utils.ts) —/^-|-$/ghas ambiguous operator precedence; replaced with/(?:^-|-$)/gto make intent explicitparseIdentityMdregex precedence (edit-identity-dialog.tsx) —/^[*_]+|[*_]+$/ghas the same ambiguity; replaced with/(?:^[*_]+|[*_]+$)/g
job-cardclick wrapper (job-card.tsx) — stopPropagation<div>now includesonKeyDown+role="none"; removed twobiome-ignorea11y suppressions that were masking the missing keyboard handler
GatewayClientreadonly members (gateway/client.ts) — five class members (pending,listeners,onOpen,onClose,onMessage) are never reassigned after construction; markedreadonlyto prevent accidental mutation and surface the invariant in the type system
PromptInputclick wrapper (prompt-input.tsx) — click-to-focus<div>now includes a properhandleKeyDown(focuses textarea on Enter/Space) +role="none"; removedbiome-ignorea11y suppressions;handleChangewrapped inuseCallback([onValueChange])for stable identity; Context value wrapped inuseMemoto avoid a new object reference on every renderConfirmDialogvoid operator (confirm-dialog.tsx) —void onConfirm()simplified toonConfirm();onConfirmis typed() => void | Promise<void>so the explicitvoidoperator was redundant
-
CI/CD pipeline (
.github/workflows/ci.yml) — matrix on Node 20 & 22, SLSA Level 2 provenance via--provenance, concurrency cancel-in-progress for PRs -
CLI: server banner now prints config file path (
~/.clawkernel.json) so first-time users know where config is stored -
README:
npx clawkernelas primary Quick Start;## CLI Optionssection with all flags; Development section separated from user flow; npm version badge -
package.json: 13 targeted keywords for npm/GitHub discoverability; description leads with product name
- CSP
connect-src:ws://localhost:* wss:→ws: wss:— allows any self-hosted WebSocket origin loadConfig(): re-validates saved URL scheme on every start — rejects malformed or tampered config- Setup wizard: URL prompt loops until valid
ws:///wss://scheme is entered - Setup wizard: Gateway Token prompt shows
(optional — leave blank if auth.mode is none)hint - Setup wizard: terminal cleared after completion (
\x1b[2J\x1b[H) — server banner appears on a clean screen; scrollback buffer preserved handleSendcallback: stabilized viaisBusyRef+runIdRef— eliminates ~30 fps recreation during streaming- Chat history reload: follows OpenClaw
shouldReloadHistoryForFinalEvent— reloads only when final event carries no valid assistant message HISTORY_PAGE_SIZE = 200constant replaces three hardcodedlimit: 200occurrences- Content type guard:
(c: any)→(c: ChatMessageContent)with'text' in cnarrowing useSessionsPage: removed derivedlistWindowResetKeystring and always-truthyifguard; replaced with explicit individual depsbiome.json: five a11y rules promoted from"off"→"warn"(useButtonType,noSvgWithoutTitle,useKeyWithClickEvents,noStaticElementInteractions,noLabelWithoutControl)ImageLightbox: restructured asrole="dialog" aria-modal="true"with separate backdrop<button tabIndex={-1}>; globalwindowEscape handler viauseEffect— fires regardless of focus position- Drop zone:
<div>→<section aria-label="Chat area">— semantically correct landmark element - Shiki theme toggle:
MutationObserverondocumentElement.class— dark/light switch now triggers live re-highlight
- Windows
--openflag:spawn('start', [url])→spawn('cmd', ['/c', 'start', '', url])—startis acmd.exebuilt-in - Two
await import('@/stores/gateway-store')dynamic imports → directuseGatewayStore.getState().client— module was already statically imported main.tsx:getElementById('root')!non-null assertion → explicit null check with descriptive error message- Gap recovery: silent
.catch(() => {})→.catch((err) => log.warn(...)) - Raw
AlertDialogin chat (8 lines, 9 imports) →<ConfirmDialog>shared component agent-files.tsxtabs:<div role="button">containing<button>→ outer<div>+ sibling<button>(label) +<button>(close)bubble.tsx: clickable<img>→<button type="button">witharia-labelandfocus-visibleringsources-panel.tsx: backdrop<div onClick>→<button type="button" tabIndex={-1}>error-boundary.tsx: decorative SVGs missingaria-hidden="true"; reload button missingtype="button"- 24
<button>elements across 14 files missingtype="button"— prevents accidental form submission in all browsers - CI tag trigger:
tags: ['v*']→tags: ['[0-9]*']— calendar versions never matchedv*
- A11y fixes make all interactive elements keyboard-accessible — no mouse-only interactions remain
ClawKernel CLI — first public release as an npm package.
- Zero-dependency static file server using Node.js built-ins only (
http,fs,path,readline,stream,child_process) - Interactive setup wizard on first run — prompts for Gateway WebSocket URL, auth token, OpenClaw home directory, and dashboard port
- Config persisted at
~/.clawkernel.json— survives restarts, editable with--reset - Runtime config injection:
window.__CK_CONFIG__injected intoindex.htmlat serve time — no rebuild needed to change settings - CLI flags:
--port/-p,--host,--open/-o,--reset,--help/-h NO_COLORand non-TTY detection — clean output in CI and piped environments- SPA fallback — all non-asset routes serve
index.html - Immutable cache headers for hashed Vite assets;
no-storefor HTML - Graceful shutdown on
SIGINTandSIGTERM - Cross-platform browser launch (
open/xdg-open/cmd /c start) - Ctrl+C and Ctrl+D during wizard exit cleanly with no unhandled-rejection warnings
STRUCTURE.md— architecture documentation at project root- Biome for lint + format (replaces ESLint); Knip for dead code detection
- Project renamed from internal codename Mission Control to ClawKernel
- Removed calendar, notifications, settings, terminal, and logs pages — scope focused on core Gateway operations
- Config file saved with
0o600permissions — readable only by the current OS user - Path traversal prevention — static file handler validates all paths against
DISTboundary .envand all secrets excluded from published package ("files": ["dist/", "bin/"])X-Content-Type-Options: nosniffon all responses
Sessions management and Cron job scheduler.
- Full session list across all agents with real-time state
- Quick filters: All, Active, Idle, Compacted
- Indexed session store for O(1) lookups by session key
- Deferred search with
useDeferredValue— UI stays responsive during fast typing - Sort by: last active, session key, kind
- Virtualized flat list via
@tanstack/react-virtual— handles thousands of sessions without layout jank - Agent grouping view — sessions collapsed under their parent agent with expand/collapse
- Session card: status dot, model badge, token usage, session key, relative timestamps
- Session preview on hover with last message excerpt
- Patch session dialog — edit label, description, and log level live
- Send message dialog — inject a message into any session without opening chat
- Delete session with confirmation dialog
- Bulk delete — multi-select sessions and delete in a single action
- Session history dialog — full message history for any session in a slide-in panel
- Auto-refresh toggle with configurable interval
- Stats bar: total sessions, active count, idle count
- Cron job scheduler per agent: create, edit, and delete recurring jobs
- Session target:
main(shared session) orisolated(fresh session per run) - Wake mode:
now(immediate) ornext-heartbeat(aligned to agent heartbeat) - Run history panel per job — expandable list of past executions with status and timing
- Delete job dialog with confirmation
- Real-time activity feed for each agent
- Per-event payload inspector — expandable JSON viewer
- Filter by event type
saveRawConfigWithRetry— hash-conflict safe wrapper for full raw config writes; integrated into agent binding and cloning workflows
Real-time chat with streaming, tool calls, and media.
- Real-time streaming chat with RAF-throttled rendering at ~30 fps
- Full message history with paginated
Load More(200 messages per page) - Streaming bubble with animated shimmer during generation
- Tool call rendering — grouped tool calls with collapsible input/output viewer per call
- Thinking block display — collapsible reasoning section (toggle via settings popover)
- Image attachments: drag-and-drop onto chat area, clipboard paste (
Ctrl+V), file picker — JPEG / PNG / WebP / GIF - Image compression before upload with
createImageBitmapfallback for browser compatibility - Image lightbox — click any image to view full size; Escape or backdrop click to close
- Source citations panel — inline
[n]links open a slide-in panel with full source list and clickable URLs - Retry: re-send the last user message without retyping
- Abort: cancel an in-progress generation mid-stream
- Session sidebar: browse and switch sessions, preview last message on hover, manual refresh
- New session button — starts a fresh context for the current agent
- Settings popover: toggle thinking block visibility
- Compaction indicator — shown when the gateway has compacted the context window
- Fallback indicator — shown when the gateway is operating in degraded mode
- Context meter — circular arc showing token usage vs. model limit; pulses red above 80%
- Processing indicator — animated dots during tool execution
- RTL / LTR text direction auto-detection per message
- Keyboard shortcuts:
Escapeclears attachments / closes lightbox / closes sources panel DOMPurifysanitization with strict allowlist — external images blocked, all anchors forcedrel="noreferrer noopener"- Shiki syntax highlighting — 13 languages, lazy-loaded WASM only when code blocks are present
- LRU markdown cache (200 entries, 50 KB max) — avoids re-parsing identical streamed content
- WeakMap text cache for fast message content extraction
- Message queue — incoming events buffered during render to prevent dropped frames
Full agent management with 9 dedicated tabs.
- Agent list panel with live status indicators (idle, active, running) and model labels
- Each agent isolated under a
TabErrorBoundary— a crash in one tab never affects others - 9 agent tabs:
- Overview — model selector, system prompt editor, memory settings, token usage stats, quick actions (send message, open new session), Danger Zone (clear all sessions, reset workspace)
- Files — in-browser code editor with multi-tab support, dirty-state dot indicator, save / discard, folder tree navigation, file-type emoji icons
- Tools — full tool catalog with enable/disable toggles, plugin-aware grouping, per-tool permission display; unsaved-changes warning on tab navigation
- Skills — skill library with section grouping, enable/disable toggles, install options; unsaved-changes warning
- Channels — active channel bindings with type, kind, and metadata display
- Cron — recurring job scheduler per agent (detailed in [2026.2.23])
- Sessions — agent-scoped session list with send message, patch, delete, and preview
- Bindings — raw binding configuration viewer
- Activity — real-time event feed per agent (detailed in [2026.2.23])
- Create agent dialog — ID, model, system prompt, memory configuration
- Delete agent dialog with confirmation
- Clone agent dialog — selective field copy: identity, tools, skills, channels, cron
- Agent comparison view — diff two agents side by side
- Edit identity dialog — update name, description, emoji, and system prompt live
config-utils.ts— patch utility for atomic config writes with hash-conflict auto-retrynormalizeAgentId— sanitizes agent IDs for consistent lookup
Project foundation — WebSocket client, routing, and design system.
- React 19 + TypeScript (strict) + Vite 7 project bootstrap
- React Router v7 with lazy-loaded routes — Dashboard, Agents, Chat, Sessions
PageErrorBoundaryon every route — unhandled errors show a recovery UI instead of a blank screenPageSkeletonloading state for lazy-loaded routescreateLoggerstructured logging with named scopes — replaces allconsole.*calls throughout
- Persistent WebSocket connection to OpenClaw Gateway (
ws:///wss://) - Ed25519 device identity — keypair generated once, challenge-nonce authentication per session
- Exponential backoff with jitter on disconnect — prevents thundering herd on gateway restart
- Sequence gap detection — detects missed events and triggers automatic session refresh
- Zustand store as single source of truth: agents, sessions, identities, active runs, channels
- Stable
instanceIdper browser tab — prevents duplicate event subscriptions on hot reload
- Dark and light theme with toggle button; Material Palenight High Contrast dark palette
- Responsive collapsible sidebar — collapses to icon-only on narrow viewports
- shadcn/ui component library built on Radix UI primitives
- Tailwind CSS v4 with CSS custom properties for theming
ConfirmDialogshared component — consistent destructive-action confirmation across the app- Sonner toast notifications for user-facing feedback
- Status bar — persistent bottom bar showing gateway connection state, model info, and run count
- Ed25519 challenge-nonce authentication — no long-lived credentials transmitted after the initial handshake
- CSP meta tag:
default-src 'self',connect-src ws://localhost:* wss:,wasm-unsafe-evalfor Shiki WASM,frame-ancestors 'none' - All anchor tags forced to
rel="noreferrer noopener" target="_blank"— prevents tab-napping
For unreleased changes, see the repository.