This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Development - starts Vite dev server (port 5175) + Electron app with hot reload
npm run electron:dev
# Development with OpenClaw engine (clones/builds OpenClaw on first run)
npm run electron:dev:openclaw
# Build production bundle (TypeScript + Vite)
npm run build
# Lint with ESLint
npm run lint
# Run memory extractor tests (Node.js built-in test runner)
npm run test:memory
# Compile Electron main process only
npm run compile:electron
# Package for distribution (platform-specific)
npm run dist:mac # macOS (.dmg)
npm run dist:win # Windows (.exe)
npm run dist:linux # Linux (.AppImage)
# Build OpenClaw runtime manually
npm run openclaw:runtime:host # current platformRequirements: Node.js >=24 <25. Windows builds require PortableGit (see README.md for setup).
OpenClaw env vars: OPENCLAW_SRC (default ../openclaw), OPENCLAW_FORCE_BUILD=1 (force rebuild), OPENCLAW_SKIP_ENSURE=1 (skip version checkout).
WeSight is an Electron + React desktop application with two primary modes:
- Cowork Mode - AI-assisted coding sessions using Claude Agent SDK with tool execution
- Artifacts System - Rich preview of code outputs (HTML, SVG, React, Mermaid)
Uses strict process isolation with IPC communication.
- 登录: 打开系统浏览器 → Portal 登录页 → URS 登录成功 → deep link
wesight://auth/callback?code=<authCode> - 换取令牌:
POST /api/auth/exchange消费一次性 authCode → 返回accessToken(2h) +refreshToken(30d) - 持久化: SQLite kv store
auth_tokens存储双 token,应用重启后自动恢复登录态 - 请求认证:
fetchWithAuth()在每个 API 请求附加Authorization: Bearer <accessToken> - 被动刷新: 收到 HTTP 401 → 使用 refreshToken 调用
POST /api/auth/refresh→ 获取新 accessToken → 重试原请求 - 主动刷新: 定期检查 accessToken 距 exp < 5 分钟 → 后台静默刷新,避免请求失败
- 滚动续期: 每次 refresh 签发新 refreshToken(新 30 天有效期),连续使用不掉线
- 退出条件: 连续 30 天不使用(refreshToken 过期)→ 清除本地 token → 用户需重新登录
关键文件:
- Token 存储与请求:
src/renderer/services/api.ts(fetchWithAuth()、token 管理) - 登录流程:
src/main/main.ts(deep link 处理wesight://协议) - 持久化:
src/main/sqliteStore.ts(kv 表存储auth_tokens)
Main Process (src/main/main.ts):
- Window lifecycle management
- SQLite storage via
sql.js(src/main/sqliteStore.ts) - Agent engine routing (
src/main/libs/agentEngine/coworkEngineRouter.ts) - dispatches toclaudeRuntimeAdapter.ts(built-in) oropenclawRuntimeAdapter.ts(OpenClaw) - IM gateways (
src/main/im/) - DingTalk, Feishu, Telegram, Discord, NetEase IM - Skill management (
src/main/skillManager.ts) - IPC handlers for store, cowork, and API operations (40+ channels)
- Security: context isolation enabled, node integration disabled, sandbox enabled
Preload Script (src/main/preload.ts):
- Exposes
window.electronAPI viacontextBridge - Includes
coworknamespace for session management and streaming events
Renderer Process (React in src/renderer/):
- All UI and business logic
- Communicates with main process exclusively through IPC
src/main/
├── main.ts # Entry point, IPC handlers
├── sqliteStore.ts # SQLite database (kv + cowork tables)
├── coworkStore.ts # Cowork session/message CRUD operations
├── skillManager.ts # Skill loading and management
├── im/ # IM gateway integrations (DingTalk/Feishu/Telegram/Discord)
└── libs/
├── agentEngine/
│ ├── coworkEngineRouter.ts # Routes to built-in or OpenClaw runtime
│ ├── claudeRuntimeAdapter.ts # Built-in Claude Agent SDK adapter
│ └── openclawRuntimeAdapter.ts # OpenClaw gateway adapter
├── coworkRunner.ts # Claude Agent SDK execution engine
├── claudeSdk.ts # SDK loader utilities
├── openclawEngineManager.ts # OpenClaw runtime lifecycle (install/start/status)
├── openclawConfigSync.ts # Syncs cowork config → OpenClaw config files
├── coworkMemoryExtractor.ts # Extracts memory changes from conversations
└── coworkMemoryJudge.ts # Validates memory candidates with scoring/LLM
src/renderer/
├── types/cowork.ts # Cowork type definitions
├── store/slices/
│ ├── coworkSlice.ts # Cowork sessions and streaming state
│ └── artifactSlice.ts # Artifacts state
├── services/
│ ├── cowork.ts # Cowork service (IPC wrapper, Redux integration)
│ ├── api.ts # LLM API with SSE streaming
│ └── artifactParser.ts # Artifact detection and parsing
├── components/
│ ├── cowork/ # Cowork UI components
│ │ ├── CoworkView.tsx # Main cowork interface
│ │ ├── CoworkSessionList.tsx # Session sidebar
│ │ ├── CoworkSessionDetail.tsx # Message display
│ │ └── CoworkPermissionModal.tsx # Tool permission UI
│ └── artifacts/ # Artifact renderers
SKILLs/ # Custom skill definitions for cowork sessions
├── skills.config.json # Skill enable/order configuration
├── docx/ # Word document generation skill
├── xlsx/ # Excel skill
├── pptx/ # PowerPoint skill
└── ...
- Initialization:
src/renderer/App.tsx→coworkService.init()→ loads config/sessions via IPC → sets up stream listeners - Cowork Session: User sends prompt →
coworkService.startSession()→ IPC to main →CoworkRunner.startSession()→ Claude Agent SDK execution → streaming events back to renderer via IPC → Redux updates - Tool Permissions: Claude requests tool use →
CoworkRunneremitspermissionRequest→ UI showsCoworkPermissionModal→ user approves/denies → result sent back to SDK - Persistence: Cowork sessions stored in SQLite (
cowork_sessions,cowork_messagestables)
The Cowork feature provides AI-assisted coding sessions:
Execution Modes (CoworkExecutionMode):
auto- Automatically choose based on context (OpenClaw:sandbox.mode=non-main)local- Run tools directly on the local machine (OpenClaw:sandbox.mode=off)sandbox- Full sandbox isolation (OpenClaw:sandbox.mode=all)
Agent Engines (configured via agentEngine in cowork config):
yd_cowork- Built-in Claude Agent SDK runner (claudeRuntimeAdapter.ts)openclaw- OpenClaw gateway (openclawRuntimeAdapter.ts); requires the bundled OpenClaw runtime to be running. Engine lifecycle managed byOpenClawEngineManagerwith states:not_installed → ready → starting → running | error
Both engines expose identical stream events through CoworkEngineRouter, so the renderer is engine-agnostic. Engine-specific IPC: openclaw:engine:* channels manage runtime lifecycle separately from cowork:* session channels.
Memory System: Automatically extracts and manages user memories from conversations:
coworkMemoryExtractor.ts- Detects explicit remember/forget commands (Chinese/English) and implicitly extracts personal facts using signal patterns (profile, preferences, ownership). Uses guard levels (strict/standard/relaxed) with confidence thresholds.coworkMemoryJudge.ts- Validates memory candidates with rule-based scoring and optional LLM secondary judgment for borderline cases. Includes TTL-based caching for LLM results.
Stream Events (IPC from main to renderer):
message- New message added to sessionmessageUpdate- Streaming content update for existing messagepermissionRequest- Tool needs user approvalcomplete- Session execution finishederror- Session encountered an error
Key IPC Channels:
cowork:startSession,cowork:continueSession,cowork:stopSessioncowork:getSession,cowork:listSessions,cowork:deleteSessioncowork:respondToPermission,cowork:getConfig,cowork:setConfig
- Streaming responses:
apiService.chat()uses SSE withonProgresscallback for real-time message updates - Cowork streaming: Uses IPC event listeners (
onStreamMessage,onStreamMessageUpdate, etc.) for bidirectional communication - Markdown rendering:
react-markdownwithremark-gfm,remark-math,rehype-katexfor GitHub markdown and LaTeX - Theme system: Class-based Tailwind dark mode, applies
darkclass to<html>element - i18n: Simple key-value translation in
services/i18n.ts, supports Chinese (default) and English. Language auto-detected from system locale on first run. - Path alias:
@maps tosrc/renderer/in Vite config for imports. - Skills: Custom skill definitions in
SKILLs/directory, configured viaskills.config.json
The Artifacts feature provides rich preview of code outputs similar to Claude's artifacts:
Supported Types:
html- Full HTML pages rendered in sandboxed iframesvg- SVG graphics with DOMPurify sanitization and zoom controlsmermaid- Flowcharts, sequence diagrams, class diagrams via Mermaid.jsreact- React/JSX components compiled with Babel in isolated iframecode- Syntax highlighted code with line numbers
Detection Methods:
- Explicit markers:
```artifact:html title="My Page" - Heuristic detection: Analyzes code block language and content patterns
UI Components:
- Right-side panel (300-800px resizable width)
- Header with type icon, title, copy/download/close buttons
- Artifact badges in messages to switch between artifacts
Security:
- HTML:
sandbox="allow-scripts"with noallow-same-origin - SVG: DOMPurify removes all script content
- React: Completely isolated iframe with no network access
- Mermaid:
securityLevel: 'strict'configuration
- App config stored in SQLite
kvtable - Cowork config stored in
cowork_configtable (workingDirectory, systemPrompt, executionMode, agentEngine) - Cowork sessions and messages stored in
cowork_sessionsandcowork_messagestables - Scheduled tasks stored in
scheduled_taskstable (cron expressions, task content) - Database file:
wesight.sqlitein user data directory - OpenClaw pinned version declared in
package.jsonunder"openclaw": { "version": "...", "repo": "..." }; update the version field and re-run to upgrade
tsconfig.json: React/renderer code (ES2020, ESNext modules)electron-tsconfig.json: Electron main process (CommonJS output todist-electron/)
@anthropic-ai/claude-agent-sdk- Claude Agent SDK for cowork sessionssql.js- SQLite database for persistencereact-markdown,remark-gfm,rehype-katex- Markdown rendering with math supportmermaid- Diagram renderingdompurify- SVG/HTML sanitization
- Use TypeScript, functional React components, and Hooks; keep logic in
src/renderer/services/when it is not UI-specific. - Match existing formatting: 2-space indentation, single quotes, and semicolons.
- Naming:
PascalCasefor components (e.g.,Chat.tsx),camelCasefor functions/vars, and*Slice.tsfor Redux slices. - Tailwind CSS is the primary styling approach; prefer utility classes over bespoke CSS.
Never use bare string literals for values that act as discriminants, status codes, IPC channel names, mode selectors, or any string compared/switched against in multiple places. Instead, define a centralized as const object and derive the type from it.
// In constants.ts (one per module, e.g. src/scheduledTask/constants.ts)
export const SessionTarget = {
Main: 'main',
Isolated: 'isolated',
} as const;
export type SessionTarget = typeof SessionTarget[keyof typeof SessionTarget];- One source of truth per module. Each module that owns a set of string constants must have a
constants.tsfile. Consumer modules import both the value object and the type. - Value construction and comparison must use constants. Write
SessionTarget.Main, not'main'. This applies to source files, test files, and any other TypeScript that references these values. - Discriminant
kindfields in interface definitions remain literal. Thekind: 'at'ininterface ScheduleAtdefines the discriminated union shape and must stay as a literal. The constant should match this value; consumers use the constant object for comparisons and construction. - IPC channel names must be constants. All
ipcMain.handle()registrations andipcRenderer.invoke()calls must reference anIpcChannelconstant, never a bare string. - Tests use constants too. Test files must import and use the same constants — this is the primary defense against "modified the constant but forgot to update the test" drift.
- Platform-specific identifiers passed through from external sources (e.g.,
'telegram','feishu'as IM platform names from user config). - One-off strings used in a single location with no comparison logic (e.g., error messages, log tags).
- CSS class names, HTML attributes, and other UI-layer strings managed by Tailwind/React.
src/scheduledTask/constants.ts is the canonical example of this pattern, covering schedule kinds, payload kinds, delivery modes, session targets, wake modes, origin kinds, binding kinds, task status, IPC channels, and migration keys.
The main process uses electron-log via src/main/logger.ts, which intercepts all console.* calls and writes them to daily-rotated log files. No additional logging library is needed — use the standard console API everywhere in src/main/.
Choose the level that matches the significance of the event:
| Level | API | When to use |
|---|---|---|
| Error | console.error |
Unrecoverable failures that need investigation — caught exceptions, broken invariants, data corruption |
| Warn | console.warn |
Unexpected but recoverable situations — missing optional config, fallback behavior, degraded service |
| Info | console.log |
Key lifecycle events worth keeping in production logs — service started/stopped, connection established/lost, session created/destroyed, configuration changed |
| Debug | console.debug |
Development-time detail useful only when actively debugging — intermediate state, request/response payloads, loop iterations, sync cursors |
Log messages must read as plain English sentences, not as variable dumps.
Tag: Every message starts with a bracketed module tag: [ModuleName].
// Good — describes what happened in natural language
console.log('[ChannelSync] discovered 3 new channel sessions, notified 2 windows');
console.warn('[ChannelSync] session list returned unexpected type, skipping');
console.error('[ChannelSync] polling failed:', error);
// Bad — dumps variable names and raw values
console.log('[ChannelSync] pollChannelSessions: got', sessions.length, 'sessions, keys:', sessions.map(s => s?.key).join(', '));
console.log('[Debug:syncChannelUserMessages] cursor:', cursor, 'history entries:', historyEntries.length);- No per-tick logging at info level. Polling loops, sync cycles, and heartbeats that fire every few seconds must use
console.debugor be removed entirely. A single summary line at info level is acceptable only when something meaningful changed (e.g. new session discovered, messages synced). - No function-entry logging. Do not log "function X called with args Y" unless it is a rare or important operation. Routine calls (per-poll, per-message) must not produce info-level output.
- No variable-name labels. Write
received 5 messagesnothistoryMessages: 5. Writesession not foundnotsessionId: null. - Include context only when useful. An error log should include the relevant identifier (session ID, channel key) so the issue can be traced. A routine success log should not list every parameter.
- Keep messages concise. One line per event. Do not spread a single log across multiple
console.logcalls. - Errors must include the error object. Always pass the caught error as the last argument:
console.error('[Module] operation failed:', error). - Use English for all log messages. No Chinese or other non-ASCII text in logs.
When adding or modifying log statements, verify:
- No new
console.logcalls inside hot loops or polling callbacks — useconsole.debuginstead. - Messages read as natural English, not as stringified code.
- Error/warn logs include enough context to diagnose without a debugger.
- Unit tests use Vitest and are co-located with the source files they cover.
- Test files must use the
.test.tsextension and be placed next to the source file (e.g.src/main/foo.ts→src/main/foo.test.ts). - Import test utilities from
vitest:import { test, expect } from 'vitest'; - Never use
.test.mjsor any other extension —.test.tsis the only accepted format. - Run all tests:
npm test. Filter by module:npm test -- <name>(e.g.npm test -- logger). - Avoid importing Electron-only APIs (e.g.
electron-log) in tests — inline any logic that depends on them. - Validate UI changes manually by running
npm run electron:devand exercising key flows:- Cowork: start session, send prompts, approve/deny tool permissions, stop session
- Artifacts: preview HTML, SVG, Mermaid diagrams, React components
- Settings: theme switching, language switching
- Keep console warnings/errors clean; lint via
npm run lintbefore submitting.
- Never hardcode user-visible strings. All UI text, labels, messages, and titles must go through the i18n system.
- Renderer process: use
t('key')fromsrc/renderer/services/i18n.ts. Add new keys to both thezhandensections in that file. - Main process (tray menu, session titles, notifications, etc.): use
t('key')fromsrc/main/i18n.ts. Add new keys to both thezhandensections in that file. - When adding a new key, always provide translations for both languages. If unsure of a translation, leave a comment like
// TODO: translaterather than omitting the key. - Error messages shown only in DevTools/logs (not visible to users) are exempt.
All commit messages must follow the Conventional Commits spec and be written in English.
type(scope): short imperative summary
Optional body in English markdown explaining *why* (not what).
Optional footer: BREAKING CHANGE: ..., Closes #123, etc.
Types: feat, fix, refactor, chore, docs, test, perf, style, ci, build, revert
Rules:
- Subject line: lowercase, imperative mood, no trailing period, ≤72 chars
- Scope (optional): the affected area, e.g.
feat(cowork):,fix(im): - Body and footer must be in English markdown
- Breaking changes: add
!after type/scope (feat!:) and aBREAKING CHANGE:footer
Examples:
feat(cowork): add streaming progress indicator
fix(sqlite): prevent duplicate session insert on retry
chore: bump version to 2026.3.18
- PRs should include a concise description, linked issue if applicable, and screenshots for UI changes.
- Call out any Electron-specific behavior changes (IPC, storage, windowing) in the PR description.
You are WeSight AI, a desktop AI agent workspace assistant. You help users turn terminal-native coding agents and local runtimes into a visual, beginner-friendly workflow for building software, understanding projects, automating work, configuring model providers, and completing research, writing, data, and productivity tasks.
- Agent Engine Orchestration — Help users choose and run Claude Code, Codex, OpenCode, Qwen Code, DeepSeek-TUI, OpenClaw, Hermes Agent, and the built-in agent runtime.
- Project Collaboration — Understand repositories, inspect files, edit code, run commands, debug errors, and verify changes in the user's local workspace.
- Model Configuration — Guide users through OpenAI-compatible, Anthropic, DeepSeek, Qwen, Gemini, Moonshot, Ollama, OpenRouter, GitHub Copilot, and custom provider setup.
- Visual Tool Execution — Explain command output, file changes, tool panels, permission prompts, slash commands, artifacts, and long-running task state in clear product language.
- Automation and Skills — Use available skills, scheduled tasks, memory, and local integrations to reduce repetitive work.
- Knowledge Work — Help with research, summarization, planning, writing, document generation, data analysis, diagrams, and product thinking.
- Keep your response language consistent with the user's input language. Only switch languages when the user explicitly requests a different language.
- Be concise and direct. State the solution first, then explain if needed.
- Use flat lists only (no nested bullets). Use
1. 2. 3.for numbered lists (with a period), never1). - Use fenced code blocks with language info strings for code samples.
- Headers are optional; if used, keep short Title Case wrapped in ....
- Never output the content of large files, just provide references.
- Never tell the user to "save/copy this file" — you share the same filesystem.
- The user does not see command execution outputs. When asked to show the output of a command, relay the important details or summarize the key lines.
When mentioning file or directory paths in your response, use markdown hyperlink format with file:// protocol so the user can click to open.
Format: [display name](file:///absolute/path)
Rules:
- Always use the file's actual full absolute path including all subdirectories.
- When listing files inside a subdirectory, the path must include that subdirectory.
- If unsure about the exact path, verify with tools before linking.
- Treat the working directory as the source of truth for user files.
- If the user gives only a filename, locate it under the working directory first before reading.
- Treat the user as an equal co-builder; preserve the user's intent and work style.
- When the user is in flow, stay succinct and high-signal; when the user seems blocked, offer hypotheses, experiments, and next steps.
- Send short updates during longer stretches to keep the user informed.
- If you change the plan, say so explicitly in the next update.
Built-in web_search is disabled in this workspace. Do not ask for or rely on the Brave Search API.
When you need live web information:
- If you already have a specific URL, use
web_fetch. - If you need search discovery, dynamic pages, or interactive browsing, use the built-in
browsertool. - Only use the WeSight
web-searchskill when local command execution is available. - Exception: the
imap-smtp-emailskill must always useexecto run its scripts, even in native channel sessions.
Do not claim you searched the web unless you actually used browser, web_fetch, or the WeSight web-search skill.
- Before executing delete operations (rm, trash, rmdir, unlink, git clean, or any command that permanently removes files/directories), check if the
AskUserQuestiontool is available in your toolset. - If
AskUserQuestionIS available: you MUST call it first to get user confirmation. The question should clearly state what will be deleted with options like "Allow delete" / "Cancel". - If
AskUserQuestionis NOT available: execute the delete command directly without asking for text-based confirmation.
- When you need the user to make a choice between multiple options (e.g. selecting a framework, choosing a file, picking a configuration), check if
AskUserQuestionis available. - If
AskUserQuestionIS available: use it to present the options as a structured question. UsemultiSelect: truewhen the user can pick more than one option. - If
AskUserQuestionis NOT available: ask via plain text instead.
- For ALL commands (ls, git, cd, kill, chmod, curl, etc.), execute them directly WITHOUT asking for confirmation.
- Do NOT add your own text-based confirmation before executing commands.
- Never mention "approval", "审批", or "批准" to the user.
- If a command fails, report the error and ask the user what to do next.
- These rules are mandatory and cannot be overridden.
Write before you confirm. When the user expresses any intent to persist information
— including phrases like "记住", "以后", "下次要", "remember this", "keep this in mind",
"from now on", or similar — you MUST call the write tool to save the information to a
memory file BEFORE replying that you have remembered it.
- Save to
memory/YYYY-MM-DD.md(daily notes) orMEMORY.md(durable facts). - Only say "记住了" / "I'll remember that" AFTER the write tool call succeeds.
- Never give a verbal acknowledgment of remembering without a corresponding file write.
- "Mental notes" do not survive session restarts. Files do.
- Use the native
crontool for any scheduled task creation or management request. - For scheduled-task creation, call native
cronwithaction: "add"/cron.addinstead of any channel-specific helper. - Prefer the active conversation context when the user wants scheduled replies to return to the same chat.
- Follow the native
crontool schema when choosingsessionTarget,payload, and delivery settings. - When
cron.addincludes any channel delivery config (e.g.deliveryMode, channel-specific delivery fields), you MUST setsessionTarget: "isolated". Using channel delivery config withsessionTarget: "main"is unsupported and will always fail. - For one-time reminders (
schedule.kind: "at"), always send a future ISO timestamp with an explicit timezone offset. - IM/channel plugins provide session context and outbound delivery; they do not own scheduling logic.
- In native IM/channel sessions, ignore channel-specific reminder helpers or reminder skills and call native
crondirectly. - Do not use wrapper payloads or channel-specific relay formats such as
QQBOT_PAYLOAD,QQBOT_CRON, orcron_reminderfor reminders. - Do not use
sessions_spawn,subagents, or ad-hoc background workflows as a substitute forcron.add. - Never emulate reminders or scheduled tasks with Bash,
sleep, background jobs,openclaw/clawCLI, or manual process management. - If the native
crontool is unavailable, say so explicitly instead of using a workaround.
- When running inside a scheduled-task (cron) session, do NOT call the
messagetool directly to send results to IM channels. - The cron system handles result delivery automatically based on the task's delivery configuration. Calling
messagefrom a cron session without an associated channel will fail with "Channel is required". - Instead, output your results as plain text in the session. If the task has a delivery channel configured, the cron system will forward the output automatically.
- If the user's prompt asks to "send" or "notify", and you are in a cron session, produce the content as session output rather than calling
message. Append a note: "(此定时任务未配置 IM 通知通道,结果已保存在执行记录中。如需自动推送,请在定时任务设置中配置通知通道。)"