| schema_version | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| project | Kun | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| single_runtime | kun | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| themes |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| palette |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| typography |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| spacing |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| radius |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elevation |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| motion |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| z_index |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| window |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| icons |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| components |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| backgrounds |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| i18n |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| brand |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| a11y |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dont |
|
单一权威设计文档。所有屏幕、所有组件、所有视觉决策,都从这里出。
This file has two layers, on purpose:
- YAML frontmatter (
---block at the top) — machine-readable design tokens (exact hex values, font stacks, spacing scale, radius scale, shadows, motion timings, component recipes). Design agents (Stitch, Figma plugins, future codegen tools) read this and apply it verbatim. When you change a value, change it here and insrc/renderer/src/styles/*.css/src/renderer/src/index.cssso the running app and this file stay in sync. - Markdown body — the human-readable why. Design intent, principles, anti-patterns, and per-screen rules. This is what a contributor reads when they're deciding whether a new screen is on-brand.
Treat the frontmatter as the source of truth for values and the markdown as the source of truth for judgment. If they ever conflict, the frontmatter wins, and the markdown needs an update.
Kun (formerly DeepSeek GUI) is a local desktop workbench built around its namesake Kun runtime. The desktop shell is Electron; the runtime is a TypeScript package that speaks HTTP/SSE; the renderer is React 19 + Zustand 5; the visual system is TailwindCSS 3 with a hand-built token layer on top.
The product is not another chat shell. It exists to let a real agent do real work in a real project on a real machine, with the human staying in the loop on every mutating call.
Three workspaces plus connected entry points, one runtime:
| Surface | Job to be done |
|---|---|
| Code | Bound to a local repo, drives the agent through tool calls, file changes, commands, and review. |
| Design | Generates and iterates UI drafts, interactive HTML prototypes, design graphs, and a shared design system that can hand off to Code. |
| Write | A long-form writing space: Markdown files, FIM completion, selection-scoped inline agent. |
| Connect phone | Background automation: Feishu / Lark channels, webhook / relay, scheduled tasks. Internal route and storage names still use claw for compatibility. |
All product surfaces share the same Kun HTTP/SSE boundary, the same settings (API key, base URL, model), and the same visual system.
These six rules are not aspirations — they are how the product is already built. New screens must follow them, not re-interpret them.
- One runtime, one boundary. Code, Design, Write, and Connect phone all call
kun serveover127.0.0.1:port. The renderer never embeds an agent loop and never speaks a second protocol. This keeps upgrades and debugging boring. - Local-first, observable, controllable. Settings, sessions, and runtime state live on disk under the OS app-data folder. Every tool call, file change, and reasoning step is shown in the UI. The user can interrupt, approve, deny, or revert at any point.
- No agent switcher, no runtime console. The product intentionally does not surface runtime diagnostics, provider selection, or model-control panels. If a runtime detail is important, it goes in Settings, not in the main canvas.
- The renderer maps HTTP, it does not implement agent logic. Approvals, steering, compaction, fork, resume, usage — all come from Kun endpoints, never re-implemented in React.
- Stable visual identity, not visual novelty. A new screen should look like a sibling of an existing one, not a fresh experiment. New components earn their place by replacing multiple existing ones, not by adding a new style.
- Calm by default. The default surface is a near-white (or near-black) canvas with restrained surfaces, no chroma in the chrome, and a single accent that only appears on actionable elements. Status, danger, and skill are the only other colors you may reach for.
This section is the editorial companion to the YAML frontmatter above. Values in the frontmatter are the contract; values here are the why and the when.
A near-paper canvas (light) or near-charcoal canvas (dark), a single blue accent that only lights up when the user can act on something, pill-shaped chrome on a desktop title bar, generous whitespace, layered translucent surfaces that read as "glass", and text that is dense but never crowded. The product feels like a calm professional tool — closer to a code editor than to a chat app. It must not feel like a marketing site.
The renderer paints two layers behind the chrome:
- Base canvas (
--ds-bg-canvas,#fffffflight /#181818dark) is the central work area. The chat timeline, the writing editor, and the file tree all live on this canvas. - Surrounding surface (
--ds-bg-main,#f5f7falight /#101010dark) is the app shell. Sidebars, topbar, and inspectors rest on it. The contrast between canvas and surface is intentionally small — about 4% — so the eye reads them as one workspace, not two zones.
On top of those, three translucent glass surfaces stack:
ds-card/ds-surface-card— cards, list rows, popover triggers.ds-elevated/ds-surface-elevated— dialogs, dropdowns, the composer shell, anything that must lift off the page.ds-subtle/ds-surface-subtle— quiet secondary surfaces (e.g. settings tabs that are not currently active).
Glass effect is achieved with backdrop-blur-xl (24px) plus a faint
inset 0 1px 0 rgba(255,255,255,0.45) highlight on chips, and the
topbar carries a 3-stop vertical gradient
(topbar_gradient_light / topbar_gradient_dark) so the title bar
reads as a soft glass strip.
A subtle body glaze (body_glaze_light / body_glaze_dark)
sits on body::after to add a soft directional light without ever
introducing a new color.
The accent is electric blue (#0088ff light / #339cff dark).
Use it for exactly these things:
- The primary action button ("Send", "Allow", "Save").
- A focused form control's border + ring.
- Status dots that mean "this is live and doing something".
- Hyperlink-style chip labels (e.g. a feature flag toggle).
- Selection background (
--ds-selection).
Do not use accent for:
- Decorative background fills larger than a chip.
- Body text or headings.
- Disabled state — disabled elements are opacity 0.45, not recolored.
Other named colors are reserved for their semantic:
--ds-success/--ds-success-soft— completed tools, cached read, OK health pings.--ds-danger/--ds-danger-soft— failed tools, denied approvals, errors, retry badges.--ds-skill/--ds-skill-soft— anything related to a user-loaded Skill (purple is the "this came from a plugin" hue).--ds-diff-added/--ds-diff-removed— file change diff blocks. These are the only colors that may sit side-by-side on a code block.--ds-warning-soft— non-fatal warnings (e.g. token cache missing, retry-pending).
Everything else — text, borders, the canvas itself, the sidebar — stays in the neutral palette. If a screen needs more than accent plus these named semantic colors, it is probably a sign the information architecture should change first.
Three families, and only three:
- Sans (body): SF Pro Text → PingFang SC → Noto Sans SC → Helvetica
Neue → Arial. The product is bilingual (zh + en), so the cascade
covers macOS, Windows, and Linux. Set as
body { font-family: ... }inindex.css. - Display (hero, welcome): SF Pro Display, same CJK fallback. Used sparingly — only in welcome cards and modal hero copy.
- Mono: SF Mono → JetBrains Mono → IBM Plex Mono. Used for code blocks, inline code, kbd hints, command lines, model ids, and tool result detail.
The size rhythm in typography.size_rhythm is the only allowed
ladder. If you find yourself reaching for text-[15.5px] you're
probably between two rungs — pick the closer one or restructure.
Default leading is leading-relaxed for body prose, leading-5
or leading-6 for compact UI lists, and tight (leading-tight)
only for hero headings. Never leading-none except in chips.
tracking-wide is reserved for the small uppercase section labels
(text-[11px] font-semibold uppercase tracking-wide text-ds-faint)
that appear above settings groups. Nothing else uses letter-spacing.
The product uses Tailwind's default 4-px scale. Three rules:
- Card padding is
px-3 py-2(tight) orpx-4 py-3(normal).px-5 py-4is reserved for hero cards and full-screen modals. - Inline element gap is
gap-1togap-3. Beyondgap-4, you're starting a new region; use vertical margin instead. - Section spacing is
mt-3tomt-6. Anything tighter thanmt-3should begap-*on a flex parent; anything wider thanmt-6should probably be a new card or a divider.
The fixed three-pane layout sizes are part of the design system,
not an accident. Don't let a new screen override the sidebar
defaults — that's what --ds-layout-left-sidebar-width is for.
The product reads as soft but not round. Pill controls (rounded-full)
on the title bar, large rounded-xl / rounded-2xl cards in the
body, and a single oversized rounded-[28px] shell for the
composer. Smaller radii (rounded-md, rounded-lg) appear on
inline code, kbd, and icon-only buttons.
Two hard rules:
- No square corners on a clickable surface. Minimum 6px.
- No fully-rounded corners on a card surface. Cards are
rounded-xltorounded-3xl, never pill-shaped.
Three elevation tiers, in increasing depth:
- Card soft — list rows, side panels, in-page popovers. Subtle, single shadow.
- Card strong / panel — modals, dropdowns, the composer.
Deeper shadow +
backdrop-blur-xlto read as "lifted glass". - Shell — the main app shell, the welcome screen, the settings root. Largest shadow, used sparingly.
Chips and pill buttons get an inset highlight
(inset 0 1px 0 rgba(255,255,255,0.78) light) so they look pressed
out of a glass surface, not painted onto one.
Never use a colored shadow. All shadows are black or near-black with low alpha.
Motion is functional, not decorative. It exists to:
- Confirm a click (button press, focus ring swap) — 140 ms.
- Reveal a hover state (card lift, chip background) — 150 ms.
- Smooth a route or panel change — 200-300 ms.
- Indicate liveness (status dot, streaming shimmer) — looped, 1.8-2.4 s.
Two looped animations exist in the system:
pulseon status dots and the work logo.ds-shiny-texton streaming assistant text (a 2.4s linear shimmer, not a typewriter).
Everything else is one-shot. Do not animate entry/exit of dialogs beyond a 200ms opacity+scale. Do not animate hover on rows containing many cells. Do not animate the composer.
Every screen in Kun follows the same macro-grammar:
- Topbar: a translucent strip with the back button, session
title, mode switcher, and right-side action cluster. The topbar
is always draggable for window move; interactive elements
inside it must opt out with
.ds-no-drag. - Left sidebar: workspace roots (Code) / channels (Connect phone,
internal
claw) / spaces (Write). Collapsible, drag-resizable, 268 px default. - Center column: the work surface — message timeline (Code / Connect phone) or editor (Write). Never bleed into the sidebars.
- Right inspector: optional, context-driven — Changes, Todo, Browser, Plan, File, Write Assistant, and SDD Assistant. Drag-resizable, 360 px default. The Write assistant and SDD assistant both use this slot.
A new screen should fit into this grammar. If it can't, that is a signal the grammar needs to grow — and the change goes in this file first.
- The product is bilingual. Strings live under
src/renderer/src/locales/{zh,en}/and are loaded throughreact-i18next. New strings ship in both locales at the same time. - Tone is direct, helpful, and slightly opinionated. First-person plural when describing the product ("we ship", "we ship Code, Write, and Connect phone"), second person for the user. No emoji. No marketing language. Error messages are full sentences ending in punctuation; never a raw stack trace.
- The product name is "Kun" (formerly "DeepSeek GUI"). The bundled
runtime shares the name; say "Kun runtime" when the distinction matters.
The main workbenches are "Code" and "Write"; the phone/IM surface is
"Connect phone" in English and "连接手机" in zh copy. Internal code may
still say
claw, but production copy should not expose it as the product name.
Three modes: system, light, dark. The choice is in Settings →
General. system listens to prefers-color-scheme and updates
live. The theme is applied as data-theme on <html>; Tailwind
dark: variants and CSS custom properties both pick it up. UI
font scale is independent (small / medium / large) and is applied
as a CSS --ds-ui-scale zoom factor.
Every new screen must work in both themes without per-screen overrides. The token system is the contract.
Before shipping a new screen, run this checklist:
- Sits in the standard three-pane + topbar grammar (or explicitly extends it in this file).
- Uses only the four families of color (neutral, accent, status, skill/diff).
- Uses only the three font families and the size rhythm.
- Uses the radius ladder (no square clickables, no round cards).
- Uses elevation tiers, not custom shadows.
- All interactive elements have a focus ring (
ring-1 ring-accent/30). - Strings exist in both
zhandenlocale files. - No emoji, no marketing copy, no extra runtime surface.
- No agent switcher, no runtime diagnostics, no legacy CodeWhale/Reasonix import.
If any box is unchecked, fix it before merging.
┌─────────────────────────────────────────────────────────────┐
│ Renderer (React 19 + Zustand 5) │
│ AppShell → Workbench → (Code | Design | Write | Connect phone) UI│
│ │ │
│ │ window.kunGui.runtimeRequest / startSse │
│ ▼ │
│ Preload (contextBridge, contextIsolated) │
│ kunGui.* IPC surface │
│ │ │
│ ▼ │
│ Main process (Node) │
│ RuntimeHost → kunRuntimeAdapter │
│ Settings / Connect phone runtime / Terminal / Updater / Logger│
│ │ │
│ │ spawn child process + HTTP/SSE │
│ ▼ │
│ Kun (TypeScript package) │
│ serve --host 127.0.0.1 --port 18899 │
│ /health · /v1/* · SSE /v1/threads/{id}/events │
│ cache-first AgentLoop · ports & adapters · append-only log │
│ │ │
│ │ HTTPS to model API │
│ ▼ │
│ DeepSeek (or OpenAI-compatible) chat/completions │
└─────────────────────────────────────────────────────────────┘
Three lessons baked into this shape:
- The renderer does not know which runtime it talks to beyond "kun". Switching providers is not a product surface; it's a main-process concern.
- The main process does not implement agent logic. It spawns the child, forwards HTTP, and forwards SSE. It also owns GUI-only services (settings, updater, Connect phone runtime, workspace files, external editors, and Write export/completion) that the renderer can ask for.
- Kun is the agent. Loop, tool host, stores, model client, server — all in one process, behind one HTTP/SSE boundary.
The Kun package (kun/) is the single active agent
runtime. It is a TypeScript ESM package that ships its own HTTP
server and is built before the Electron app.
kun/src/
cli/ # Command-line entrypoints (serve)
contracts/ # Zod schemas and inferred types for HTTP/SSE
domain/ # Thread, Turn, Item, Event, Approval, Usage entities
ports/ # ModelClient, ToolHost, ThreadStore, SessionStore,
# ApprovalGate, EventBus, WorkspaceInspector, Clock
adapters/ # DeepSeek-compatible model client, local tool host,
# in-memory and file-backed stores, workspace inspector
services/ # Thread and turn orchestration services
loop/ # Cache-first AgentLoop, InflightTracker,
# SteeringQueue, ContextCompactor
cache/ # ImmutablePrefix, LRU cache, TTL-LRU cache
telemetry/ # Usage counter, cache telemetry
server/ # HTTP server, router, auth, SSE, response helpers,
# runtime-factory, route handlers
prompt/ # System prompt for the Kun identity
shared/ # Shared types with the GUI
Kun is structured as ports & adapters:
contracts/— the boundary. Zod schemas describe every HTTP/SSE DTO. This is what the GUI imports indirectly through its mapper (src/renderer/src/agent/kun-contract.ts).domain/— entities. Thread, Turn, Item, Event, Approval, Usage. No I/O.ports/— interfaces. The agent loop only knows aboutModelClient,ToolHost,ThreadStore,SessionStore,ApprovalGate,EventBus,WorkspaceInspector,Clock,IdGenerator. These are intentionally small.adapters/— concrete implementations. The defaultCompatModelClientspeaks thePOST {baseUrl}/v1/chat/completionsshape; the defaultLocalToolHostruns tools in-process with approval gating.services/— orchestration.ThreadServiceandTurnServiceown the lifecycle of a thread and a turn; they wire stores, models, and tools together.loop/— the agent loop. Pure orchestration over the ports.server/— the thin HTTP transport that exposes everything.
A new capability should land as a new port + adapter, never as a new server handler that reaches into the loop directly. The boundary is the test.
The loop is built around DeepSeek's native cache hit/miss telemetry. The principles:
- Immutable prompt prefix with a sha256 fingerprint. The
system prompt, tool schemas, pinned constraints, and few-shots
form the prefix; mutation goes through
setSystemPrompt,setTools,setPinnedConstraints,setFewShots, which invalidate the fingerprint.verifyImmutablePrefixis called at the start of every model step — a drift throws immediately. - Append-only session log. Every turn is a JSONL stream; the next replay skips malformed lines but keeps the rest. Indexes are atomic JSON writes.
- Bounded TTL/LRU caches. Tools, model responses, and computed fingerprints are cached with explicit eviction.
- Inflight tracking with guaranteed cleanup.
InflightTrackeris the authoritative source for SSE event pairs.run(record, work)registers an id, runs the work, and removes the id in afinally— even on abort. - Mid-turn steering.
SteeringQueuecollects user messages posted while a turn is running and injects them as user inputs at the next safe loop boundary. - Context compaction.
ContextCompactorfolds long histories into a singlecompactionitem, always preserving the pinned constraints from the immutable prefix. Soft threshold 16k tokens, hard threshold 24k tokens. - Tool pair healing. Before sending history to the model,
Kun drops orphan
tool_results and tool calls with missing results, to avoid 400/retry storms.
Cache hit rate is reported as hit / (hit + miss) using
DeepSeek's native prompt_cache_hit_tokens /
prompt_cache_miss_tokens fields. Compat fields
(cached_tokens, cache_read_input_tokens) are fallback only.
A healthy warm thread should hold ≥ 90% cache hit rate. Verified on 2026-06-02: 12 short turns warm ran 94.7% hit; 24 short turns on the same warm prefix ran 95.2% overall, 98.1% on the latest turn.
The HTTP server is built on a hand-rolled Router that supports
:id params. Bearer-token auth via
Authorization: Bearer <runtime-token>, or --insecure for
local dev only. The routes:
| Method | Path | Purpose |
|---|---|---|
| GET | /health |
unauthenticated health probe |
| GET | /v1/workspace/status?path=… |
git/branch status for a workspace |
| GET | /v1/threads?include=side |
list threads (most recent first; side hidden by default) |
| POST | /v1/threads |
create a thread |
| GET | /v1/threads/{id} |
read thread + turns |
| PATCH | /v1/threads/{id} |
update title/status/approval/sandbox/relation |
| DELETE | /v1/threads/{id} |
delete a thread |
| POST | /v1/threads/{id}/fork |
fork (relation: fork default, or side) |
| POST | /v1/threads/{id}/turns |
start a turn |
| GET | /v1/threads/{id}/turns/{turnId} |
read a turn |
| POST | /v1/threads/{id}/turns/{turnId}/steer |
queue steering text |
| POST | /v1/threads/{id}/turns/{turnId}/interrupt |
abort a turn |
| POST | /v1/threads/{id}/compact |
fold old history |
| GET | /v1/threads/{id}/events?since_seq=N |
SSE backlog + live |
| POST | /v1/approvals/{id} |
allow / deny |
| POST | /v1/user-inputs/{id} and /v1/user-input/{id} |
submit / cancel user input answers |
| POST | /v1/sessions/{id}/resume-thread |
resume a session into a thread |
| GET | /v1/usage |
cumulative token / cache / turn counters |
SSE frames use id: <seq>, event: <kind>, and JSON data:. A
late-joining client passes since_seq (or Last-Event-ID) and
receives the backlog before live events. A heartbeat is sent
every 15 s to keep idle proxies alive.
Every thread persisted under {data-dir}/threads/{id}/thread.json
carries relation metadata:
primary— top-level thread (default).fork— manual fork that switches the user away.side— "by-the-way" side conversation inherited from a parent snapshot. Excluded from the default thread listing; pass?include=sideto opt in. HasparentThreadIdset; promoting back toprimaryclears it.
The fork and side lineage also store forkedFromThreadId,
forkedFromTitle, forkedAt, and message/turn counts at fork
time. The GUI surfaces these in the sidebar.
ToolHostContext carries approvalPolicy and the tool host
gates at two layers: policy: 'never' blocks up front;
on-request / suggest / untrusted always prompt unless
the call is in the allowList. Tools that need to be scoped
to a specific mode (e.g. create_plan only inside a plan
thread) declare a shouldAdvertise(ctx) predicate that filters
both the listing and the execution.
SandboxMode (read-only / workspace-write /
danger-full-access / external-sandbox) is enforced by the
workspace inspector and the file/tool adapters.
--data-dir is the on-disk root for everything the runtime
owns:
{data-dir}/
threads/
index.json
{threadId}/
thread.json # ThreadRecord
messages.jsonl # TurnItem append-only
events.jsonl # RuntimeEvent append-only
session.json # latest AgentSession projection
Atomic JSON writes for index.json, thread.json, and
session.json. JSONL streams tolerate malformed lines (the
next replay skips them).
- Main (
src/main/) — Node process. Owns the Kun child process, settings store, updater, Connect phone runtime, file/git/editor helpers, Write services, IPC handlers, logger, GUI updater, macOS/Windows code-signing glue. - Preload (
src/preload/) —contextBridgesurface. Exposes a typedwindow.kunGuiAPI to the renderer. No Node access leaks into the renderer. - Renderer (
src/renderer/) — Chromium process. React 19 SPA. Runs Code / Design / Write / Connect phone UIs.
src/
main/
index.ts # app entry, IPC wiring, lifecycle
ipc/ # app IPC handlers and Zod schemas
runtime/ # runtime adapter (process, host, port, token)
services/ # git, workspace, editor, write-* services
settings-store.ts # JSON-backed settings store
claw-runtime.ts # Connect phone IM / webhook / scheduled-task engine (internal claw name)
claw-schedule-mcp-* # schedule MCP config + standalone server
gui-updater.ts # electron-updater integration
logger.ts # structured logger
resolve-kun-binary.ts # CLI / dev-script / packaged binary resolver
preload/
index.ts # contextBridge surface (window.kunGui)
index.d.ts # API type definitions
shared/ # types + constants shared by main and renderer
renderer/
src/
App.tsx # Suspense shell
AppShell.tsx # routes Workbench / Settings / InitialSetup
agent/ # AgentProvider interface + Kun impl
components/ # Workbench, Settings, ChangeInspector, …
design/ # Design-mode canvas, artifacts, prompts, graph, persistence
hooks/
lib/ # formatters, helpers, plan store, etc.
locales/{zh,en}/ # i18n
plan/ # Plan-mode prompt, store, panel
store/ # Zustand chat store + actions
write/ # Write-mode workspace, inline edit, RAG
window.kunGui is the only thing the renderer is allowed to call
on the system. It includes:
runtimeRequest(path, method, body)— generic JSON request to Kun.startSse(threadId, sinceSeq, streamId)/stopSse/onSseEvent— SSE subscription for a thread.getSettings/setSettings— typed settings I/O.- Workspace / file / git helpers (
pickWorkspaceDirectory,listWorkspaceDirectory,readWorkspaceFile,writeWorkspaceFile,watchWorkspaceFile,getGitBranches,switchGitBranch,createAndSwitchGitBranch). - Terminal (
createTerminalSession,writeTerminalSession,resizeTerminalSession,closeTerminalSession,onTerminalData,onTerminalExit). - Write-mode services (
exportWriteDocument,requestWriteInlineCompletion,listWriteInlineCompletionDebugEntries,clearWriteInlineCompletionDebugEntries). - Connect phone / internal Claw (
getClawStatus,runClawTask,startClawImInstallQr,pollClawImInstall,createClawTaskFromText,mirrorClawChannelMessageToFeishu,onClawChannelActivity). - Shell / notifications / updater / logger (
openExternal,showTurnCompleteNotification,getGuiUpdateState,checkGuiUpdate,downloadGuiUpdate,installGuiUpdate,onGuiUpdateState,logError,getLogPath,openLogDir).
Every method on this surface is typed in src/shared/kun-gui-api.ts
and validated at the IPC boundary by Zod schemas in
src/main/ipc/app-ipc-schemas.ts.
The main process owns the Kun child process through a
LocalHttpRuntimeAdapter:
kunRuntimeAdapter.resolveExecutable(settings)— finds the right binary or falls back to the dev script.kunRuntimeAdapter.ensureRunning(settings)— starts the child if it isn't already.kunRuntimeAdapter.stopAndWait()— graceful shutdown for app exit.kunRuntimeAdapter.getBaseUrl(settings)— base URL for the current settings.kunRuntimeAdapter.reclaimPort(port)— recover a stuck port.
runtimeRequestViaHost is the single chokepoint: it ensures the
runtime is running, then forwards the request with the bearer
token, default 15 s GET / 60 s POST timeout, and an Accept: application/json header.
App
└── AppShell (Suspense)
├── Workbench (routes: chat / design / write / claw / plugins / schedule; claw = Connect phone)
│ ├── Sidebar (left, drag-resizable, 268 px)
│ ├── Topbar (translucent glass strip)
│ ├── Center column
│ │ ├── MessageTimeline (Code / Connect phone)
│ │ ├── DesignWorkspaceView (Design)
│ │ └── WriteWorkspaceView (Write)
│ ├── Right inspector (optional, 360 px)
│ │ ├── ChangeInspector
│ │ ├── TodoPanel
│ │ ├── DevBrowserPanel
│ │ ├── PlanPanel
│ │ ├── WorkspaceFilePreviewPanel
│ │ ├── WriteAssistantPanel
│ │ ├── DesignAssistantPanel
│ │ ├── DesignImplementPanel
│ │ └── SddAssistantPanel
│ ├── PluginMarketplaceView (route = 'plugins')
│ └── ScheduleTasksView (route = 'schedule')
├── SettingsView (route = 'settings')
└── InitialSetupDialog (first-run)
A single useChatStore (Zustand) holds all renderer state. The
store is split into modules under src/renderer/src/store/:
chat-store.ts— main store, route, thread list, workbench panels, status flags.chat-store-types.ts— the store's TS surface.chat-store-app-actions.ts,chat-store-claw-actions.ts,chat-store-side-actions.ts— action creators grouped by domain (clawis the internal Connect phone domain).chat-store-runtime-helpers.ts— pure helpers around the runtime.chat-store-schedulers.ts— busy watchdog, completion poll, startup probe.
Persistence is layered:
localStorage— UI-only state (panel sizes, collapsed flags, composer model, write thread registry, code workspace roots, fork registry).electron-store(main) — settings, Connect phone config (internal Claw key), write workspace config.~/.deepseekgui/kun(Kun) — threads, events, sessions, usage.
The renderer talks to the runtime through one interface,
AgentProvider (src/renderer/src/agent/types.ts). Today the
only implementation is KunRuntimeProvider
(src/renderer/src/agent/kun-runtime.ts), which is a thin
HTTP/SSE client. Its DTOs live in
src/renderer/src/agent/kun-contract.ts and the
DTO-to-ChatBlock mapping lives in
src/renderer/src/agent/kun-mapper.ts.
getProvider() (in registry.ts) returns a single cached
instance. resetProviderCacheForTests() exists for unit tests
and must not be called outside of them.
Workbench.tsx is the central layout component. It reads the
current route from the store, lays out the left sidebar, center
surface, and optional right inspector, and lazy-loads the heavy panels
(ChangeInspector, TodoPanel, PlanPanel, WorkspaceFilePreviewPanel,
DevBrowserPanel, PluginMarketplaceView, ScheduleTasksView)
via React.lazy. Panel sizes and the selected right-panel mode are persisted to localStorage
under deepseekgui.layout.* keys.
The chat timeline is a virtualized list of ChatBlocks. Each
block kind has its own renderer:
user/assistant— markdown, with a streaming shimmer on the assistant block.reasoning— collapsible block with monospace text.tool— file_change, command_execution, tool_call, with inline detail and a "show in inspector" action.compaction— fold summary.approval— pending / allowed / denied / error states.user_input— structured question with option buttons.system— informational messages (e.g. runtime up, runtime down, model switched).
The store distinguishes the main workbench and entry routes through route
(chat, design, write, claw, plugins, schedule, workflow) plus
thread metadata. The Code / Design / Write mode switcher lives in the sidebar;
Connect phone uses the legacy claw route internally. Switching does not change
the runtime contract, only which renderer and local workflow state the store pulls in.
- Code — default mode, full agent flow, workspace roots, todo panel, changes inspector, plan panel, file preview, and dev browser.
- Design — design-thread registry isolates design sessions from Code, Write,
and Connect phone sessions. Artifacts persist under
.kun-design/, the canvas previews interactive HTML prototypes and graph outputs, and approved designs can publishDESIGN_SYSTEM.mdbefore opening a fresh Code thread. - Write — write-thread registry isolates Write sessions
from Code / Design / Connect phone sessions. Uses the same Kun but a
separate
WRITE_ASSISTANT_THREAD_TITLEnamespace. Inline completion and selected-text agent go through dedicated main-process services. - Connect phone — internal
clawchannel registry. Each IM channel has its own thread id, model, and workspace root. Runs throughClawRuntime(main process), which calls Kun over HTTP just like the renderer does.
| Data | Where | Format | Owner |
|---|---|---|---|
| Settings | OS app-data dir | JSON | JsonSettingsStore (main) |
| Session list / workbench layout | localStorage |
JSON | Renderer |
| Design thread registry | localStorage |
JSON | Renderer |
| Design artifacts | workspace .kun-design/ |
HTML / PNG / JSON / Markdown | Renderer + Kun |
| Write thread registry | localStorage |
JSON | Renderer |
| Connect phone channels | OS app-data dir | JSON | JsonSettingsStore |
| Threads / turns / events | ~/.deepseekgui/kun |
JSON + JSONL | Kun |
| Usage counters | Kun data dir | JSON | Kun |
| Skill / MCP files | Kun data dir + workspace | Markdown / JSON | Kun + renderer |
| GUI logs | OS app-data dir / log/ |
NDJSON | logger.ts |
| Inline completion debug | OS app-data dir | NDJSON | write-inline-completion-service.ts |
Default OS app-data paths (derived from the Electron productName,
which current builds still ship as DeepSeek GUI):
- macOS:
~/Library/Application Support/DeepSeek GUI - Windows:
%APPDATA%\DeepSeek GUI - Linux:
~/.config/DeepSeek GUI
Uninstalling the app does not remove app data. Documented in the README and respected by the install script.
LocalToolHost(kun/src/adapters/tool/local-tool-host.ts) holds the registered tools and their policies. Policies:auto,on-request,suggest,never,untrusted.- A tool with
shouldAdvertise(ctx)is gated at the listing layer too — this is howcreate_planstays scoped to plan threads. - Approval requests emit a
RuntimeEventof kindapproval_requested; the GUI shows the approval block and POSTs the decision to/v1/approvals/{id}. The agent loop resumes onallow, errors out ondeny.
Plan threads expose a create_plan tool. The renderer advertises
a GuiPlanContext on the active turn, the loop gates the tool,
the model writes a Markdown plan, and the renderer stores it as a
GuiPlanArtifact. The Build button promotes a plan artifact
into a new agent-mode thread, preserving the plan as the
opening turn.
Plan-mode prompt injection sits after the immutable prefix as a second system message, so the cached prefix is untouched.
ContextCompactor estimates token count, folds long histories
into a single compaction item, and always preserves the
immutable prefix's pinned constraints. Soft threshold 16k
tokens, hard threshold 24k tokens. The GUI renders the
compaction block inline with a "show replaced" detail.
- FIM short completion — debounced 650 ms, max 96 tokens, min accept score 0.52. Used while typing.
- Inspirational long completion — debounced 2.8 s, max 256 tokens, min accept score 0.36. Used at sentence/paragraph boundaries.
- RAG — write workspace Markdown files are indexed on-demand with BM25 + keyword match; relevant snippets are injected as hidden Markdown comments.
- Selected-text inline agent — selected text is captured with file path and line range, then submitted as a structured prompt. The agent returns Markdown edits the user can apply or ignore.
- Export —
write-export-service.tsconverts the current Markdown document to HTML / PDF / DOC / DOCX, preserving headings, lists, code blocks, tables, and local images.
ClawRuntime(main process) creates and reuses Kun threads for each IM channel and each scheduled task.- Feishu / Lark integration uses
@larksuiteoapi/node-sdk. Install is device-flow QR code; the renderer pollsclaw:im-install:polluntil authorized. - Webhook / relay is a small HTTP server in
ClawRuntimethat POSTs inbound webhooks into a Kun thread. - Scheduled tasks are detected from natural-language Connect phone
prompts (
claw-scheduled-task-detector.ts) and stored underclaw.scheduledTasksin settings. - A standalone
claw-schedule-mcp-serverprocess can be launched separately (--claw-schedule-mcp-server) to host the schedule tools over MCP, hiding the macOS dock icon when running headless.
electron-updater driven by gui-updater.ts. Channels:
stable, beta, nightly. The Settings page surfaces state
and check / download / install actions. macOS / Windows only;
Linux users build from source.
logger.ts writes structured NDJSON to the OS app-data log
directory. The renderer can open the log dir, and log:error
lets any UI surface report a category / message / detail
tuple. A startup trace is enabled by
DEEPSEEK_GUI_STARTUP_TRACE=1 and prints to stdout for
postmortem timing.
- Auth — every
/v1/*request carriesAuthorization: Bearer <runtime-token>unless the runtime was started with--insecure(local dev only). The token is generated and stored in settings. - Approval policy —
auto(default),on-request,untrusted,never,suggest. Per-tool policies can override. - Sandbox mode —
read-only/workspace-write(default) /danger-full-access/external-sandbox. Enforced by the workspace inspector and the file/tool adapters. - Renderer isolation —
contextIsolation: true, nonodeIntegration, nowebviewTagexposure. The renderer only sees thewindow.kunGuiAPI surface. - External links —
openExternalis the only way to leave the app; URLs are validated against an allow-list. - Markdown rendering —
rehype-hardenstrips unsafe nodes. Code blocks go throughshikiwith a fixed theme. - Settings file — written atomically, debounced, never
read on the renderer side. Legacy
codewhale/reasonixkeys are migrated tokunonce and discarded.
These are enforced by docs/AGENTS.md and reflect real product
decisions. New work must respect them.
- One live agent runtime: Kun. No second live provider, no provider switcher, no runtime diagnostics panel, no legacy CodeWhale / Reasonix process path.
- No UI surface for runtime internals. No AgentSwitcher,
no ConnectionStatusBar, no RuntimeDiagnosticsDialog, no
RuntimeInsightsPanel, no
/usageor/runtimeslash command. - Saved settings only contain
agents.kun. Old keys may only appear in migration. - Renderer does not implement agent logic. Approvals, steering, compaction, fork, resume, usage — all come from Kun endpoints, never re-implemented in React.
- No new drawing / design starter card in the core workbench.
- No emoji in production copy or as functional UI affordance.
If a feature request appears to require violating a constraint, escalate before coding.
When you need to add a new capability, follow this path. It's intentionally boring.
- Add the protocol field. New Zod schema in
kun/src/contracts/. Runnpm --prefix kun run build. - Add the agent behavior. In
kun/src/loop/,kun/src/services/, or a new port + adapter pair underkun/src/ports/andkun/src/adapters/. - Add the HTTP route. New file under
kun/src/server/routes/, registered inroutes/index.ts. - Map the endpoint / event in the GUI. Add to
src/renderer/src/agent/kun-contract.tsand the mapperkun-mapper.ts; expose the call inkun-runtime.ts. - Add settings only under
agents.kun. Anything else gets migrated to it. - Add i18n strings to both
zhandenlocale files. - If the surface needs a new visual element, add it to this file's YAML frontmatter first. Don't invent tokens in the JSX.
- Verify with
npm run typecheck && npm test && npm run build.
Minimum checks for any change to the design, runtime, or build:
npm run typecheck
npm test
npm run buildManual smoke (full list in docs/AGENTS.md):
- Code: create thread, stream reply, approve / deny, interrupt.
- Write: open workspace, request inline completion, run selected-text agent.
- Connect phone: save settings, run a manual task through a Kun thread.
- Settings → Agents: shows only Kun.
- Cache telemetry on a hot thread should stay ≥ 90% hit.
If any check fails, the change is not ready.
| Concern | File |
|---|---|
| App lifecycle | src/main/index.ts |
| Runtime adapter | src/main/runtime/kun-adapter.ts |
| HTTP forwarding | src/main/runtime/runtime-host.ts |
| Child process | src/main/kun-process.ts |
| Settings | src/main/settings-store.ts, src/shared/app-settings.ts |
| IPC | src/main/ipc/register-app-ipc-handlers.ts, src/main/ipc/app-ipc-schemas.ts |
| kunGui API | src/preload/index.ts, src/shared/kun-gui-api.ts |
| Agent provider | src/renderer/src/agent/kun-runtime.ts |
| DTO mapping | src/renderer/src/agent/kun-mapper.ts |
| App shell | src/renderer/src/AppShell.tsx |
| Workbench | src/renderer/src/components/Workbench.tsx |
| Chat store | src/renderer/src/store/chat-store.ts |
| Connect phone runtime | src/main/claw-runtime.ts |
| Write services | src/main/services/write-*-service.ts |
| Workspace/editor services | src/main/services/workspace-*.ts, src/main/services/workspace-editors.ts |
| Tokens / styles | src/renderer/src/styles/*.css, src/renderer/src/index.css |
| Agent loop | kun/src/loop/agent-loop.ts |
| Immutable prefix | kun/src/cache/immutable-prefix.ts |
| HTTP routes | kun/src/server/routes/ |
| Tool host | kun/src/adapters/tool/local-tool-host.ts |
| Model client | kun/src/adapters/model/compat-model-client.ts |
| Cache doc | docs/kun-cache-optimization.md |
| Architecture doc | docs/kun-architecture.md |
| Contribution doc | docs/kun-contributing.md |
docs/kun-architecture.md— single-runtime plan and GUI拆改范围.docs/kun-cache-optimization.md— cache hit rate measurement, stable prefix rules, tool pair healing.docs/kun-contributing.md— port & adapter / FCIS patterns, four PR archetypes.kun/README.md— CLI flags, env vars, data dir layout, HTTP API.docs/AGENTS.md— agent runtime notes (constraints enforced on contributors).README.md/README.en.md— product-level overview.
This file is the design source of truth. When the code and this file disagree, this file is wrong until you change both.