141 Commits

Author SHA1 Message Date
程序员阿江(Relakkes)
261a85ab76 Restore desktop project-skill discovery and align empty-session composer
The desktop app previously lost project-level skills in two places: the
settings browser only queried user skills, and the slash-command picker for a
fresh session depended on CLI init state that does not exist before the first
turn. This change makes the skills APIs cwd-aware, falls back to project skill
loading before CLI init, syncs sidebar session deletion with open tabs, and
aligns the empty-session composer styling so the pre-message session UI stays
consistent.

Constraint: Fresh desktop sessions need slash-command discovery before the CLI websocket emits system/init
Constraint: Only repository code should be committed; build artifacts, installs, and /tmp smoke fixtures stay out of git
Rejected: Rebuild slash-command listings from full getCommands() in the sessions API | introduced auth-gated command dependencies unrelated to local skill discovery
Rejected: Unify EmptySession and ChatInput into one full component now | higher regression risk for normal chat interactions than a visual-only hero variant
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep pre-message skill discovery keyed to session workDir so project-level .claude/skills remain visible before the first turn
Tested: bun test src/server/__tests__/sessions.test.ts; cd desktop && bun run test -- --run src/__tests__/skillsSettings.test.tsx src/components/layout/Sidebar.test.tsx src/__tests__/pages.test.tsx src/pages/ActiveSession.test.tsx; cd desktop && bun run lint
Not-tested: Manual desktop smoke test against the rebuilt .app bundle in the local GUI
2026-04-20 13:36:19 +08:00
程序员阿江(Relakkes)
76823047f4 Add a switchable dark theme for the desktop workspace
This introduces a persisted light/dark appearance setting, maps the desktop shell onto semantic theme tokens, and reworks the highest-traffic chat/settings surfaces so the new dark mode is usable without regressing the original light theme.

The same pass tightens markdown rendering for chat replies by improving inline code, table overflow handling, and safe external-link behavior so dark-mode content stays legible in real conversations.

Constraint: Preserve the existing light theme while adding a user-selectable dark theme in Settings > General
Constraint: Avoid introducing new dependencies for styling or markdown handling
Rejected: Replacing the light palette with a single dual-purpose palette | would risk broad visual regressions across the existing desktop UI
Rejected: Implementing dark mode only for shell chrome | leaves chat markdown, diffs, and permission flows visually broken
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: New desktop UI should use semantic theme variables instead of hard-coded color literals so both themes stay aligned
Tested: bun run lint; bun run test; bun run build; browser review of theme switching, provider/permission states, and chat surfaces
Not-tested: Prototype-style pages with remaining hard-coded colors (scheduled/session control mock surfaces) were not fully normalized in this change
2026-04-20 10:23:19 +08:00
Relakkes Yang
9ea81aaaa9 release: v0.1.2 2026-04-19 18:33:00 +08:00
Relakkes Yang
3735ee803f fix: allow dismissing completed session tasks 2026-04-19 18:27:21 +08:00
Relakkes Yang
fb011588f9 fix: correct windows tab bar overflow alignment 2026-04-19 17:56:42 +08:00
程序员阿江(Relakkes)
45e4e447fa Enable signed desktop self-updates for release testing
The desktop app already had Tauri updater plumbing, but the
release pipeline was not emitting signed updater artifacts and
the UI exposed only a thin auto-check path. This change restores
a working updater release path, rotates to a new updater public
key, and adds a shared update flow with manual check/install
controls for testing.

Constraint: Original updater private key is unavailable, so a new public key had to be embedded and old installs cannot trust new signatures
Constraint: Must not add new dependencies or require main-branch rollout before validation
Rejected: Keep the old pubkey and skip signing | would leave release builds unable to publish valid updater artifacts
Rejected: Add a manual download fallback flow | user explicitly deferred that work
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Preserve the new updater private key and password outside the repo; losing them again will break future in-app updates for installed builds
Tested: desktop unit test for updater store; desktop TypeScript no-emit; desktop production build
Not-tested: end-to-end updater install against a real GitHub Release on this branch
2026-04-19 16:54:45 +08:00
程序员阿江(Relakkes)
c904178518 Cut desktop version 0.1.1 so release tags match the merged Windows fixes
The Windows desktop fixes are already merged to main, but the desktop package
and Tauri app metadata still reported 0.1.0. Bumping both version sources to
0.1.1 keeps the app bundle metadata, CI artifact naming, and release tag in
sync for the next desktop release.

Constraint: The release workflow reads desktop version metadata directly from the repository
Rejected: Tag v0.1.1 without updating version files | release artifacts would still identify themselves as 0.1.0
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep desktop/package.json and desktop/src-tauri/tauri.conf.json version fields aligned for every desktop release
Tested: Verified version fields updated to 0.1.1 in both desktop metadata files
Not-tested: Release workflow execution after tag push
Related: Release v0.1.1
2026-04-19 15:25:08 +08:00
程序员阿江(Relakkes)
b7396e4f65 Expose raw CLI startup details in desktop error cards so Windows failures are diagnosable
The chat UI translated CLI startup failures to a generic localized string and
hid the server-provided detail entirely. That made remote Windows debugging
guesswork even when the backend had already captured the real stderr payload.

This change keeps the localized summary but renders the raw startup detail
underneath when it differs, and adds a regression test covering the Git Bash
missing case.

Constraint: Windows Server repro is happening remotely, so the immediate value is better diagnostics rather than guessing a root cause locally
Rejected: Leave the generic translation only | obscures actionable stderr and slows every remote repro cycle
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: For translated startup errors, preserve backend detail somewhere visible unless it duplicates the summary
Tested: cd desktop && bunx vitest run src/components/chat/MessageList.test.tsx
Tested: cd desktop && bun run lint
Not-tested: Full desktop interaction flow on Windows Server 2022
Related: GitHub issue #62
2026-04-19 14:12:22 +08:00
程序员阿江(Relakkes)
e0b74cecae Restore Windows custom titlebar controls so desktop builds can manage the window again
The Windows desktop shell was rendering custom minimize, maximize, and close
buttons without the Tauri capabilities those commands require. The app was also
starting with decorated windows and then trying to disable decorations at
runtime, which is a weaker setup than the platform-specific config Tauri
expects for custom chrome.

This change grants the missing window permissions, moves Windows and macOS
window chrome settings into platform-specific Tauri config files, removes the
runtime decoration toggle, and adds a regression test that proves the custom
controls invoke the Tauri window API.

Constraint: Windows build validation will happen on the remote branch instead of this macOS workspace
Rejected: Keep runtime set_decorations(false) on Windows | leaves window chrome behavior dependent on post-start native mutation
Rejected: Ship only the capability fix | lower-risk hotfix, but it preserves the fragile cross-platform window config
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Directive: Keep custom titlebar behavior in config and capabilities; avoid moving Windows decoration changes back into runtime setup
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test
Not-tested: Native Windows click/drag behavior on an actual packaged build from this local machine
Related: GitHub issue #62
2026-04-19 13:59:31 +08:00
程序员阿江(Relakkes)
f958bf7e0d Allow desktop releases to publish without platform signing infrastructure
The current release lane is aimed at manual downloads and command-line usage, not notarized distribution or in-app auto-updates. This change removes Apple certificate import and updater signing from GitHub Actions, and forces release builds to disable updater artifacts so unsigned release bundles can still be produced consistently.

Constraint: Repository does not have working Apple signing certs or updater signing keys
Rejected: Keep signing requirements and document the secrets problem | blocks every release on infra the project does not plan to maintain
Rejected: Remove the GitHub release workflow entirely | still need a repeatable packaging path for downloadable artifacts
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If signed distribution or in-app updates are added later, restore signing in workflow together with validated secrets and release metadata
Tested: git diff --check; YAML parse of release-desktop workflow; manual inspection that updater artifacts are disabled via tauri.release-ci.json and signing env/steps are removed
Not-tested: Live GitHub Actions run after workflow update
2026-04-18 21:38:13 +08:00
程序员阿江(Relakkes)
c444e439b3 chore: update cluade model name 2026-04-18 17:52:55 +08:00
程序员阿江(Relakkes)
9d29d8e32c Keep desktop replies stable across turns and fix OAuth callback routing
The desktop chat store was dropping the previous assistant draft when a new user turn began because streaming text was cleared before it was flushed into message history. At the same time, the desktop OAuth flow was using an unregistered /api/haha-oauth/callback redirect URI, which caused provider authorization failures. This change flushes visible assistant drafts before starting a new turn, restores the OAuth redirect to the registered localhost callback, and adds a root callback handler while keeping the legacy API callback path compatible.

Constraint: The OAuth provider only accepts the registered localhost /callback redirect URI
Rejected: Keep the desktop-specific /api/haha-oauth/callback path | provider rejects unsupported redirect URIs
Rejected: Rely on message_complete to persist visible assistant text | next user turn can begin before that event arrives
Confidence: high
Scope-risk: moderate
Directive: Any UI-visible assistant draft must be flushed into messages before a new user turn resets streaming state
Tested: bun test src/server/__tests__/haha-oauth-service.test.ts src/server/__tests__/haha-oauth-api.test.ts src/server/__tests__/conversation-service.test.ts
Tested: cd desktop && bun run test src/stores/chatStore.test.ts
Tested: cd desktop && bun run lint
Not-tested: Manual end-to-end desktop OAuth login after reinstall
2026-04-18 17:01:53 +08:00
程序员阿江(Relakkes)
d83efc1b04 Prevent desktop OAuth sessions from drifting out of sync
Desktop auth was reporting stale local token files as logged in, could inherit a parent OAuth token after logout, and kept polling even when browser launch failed. This change validates stored tokens through the refresh path, clears inherited OAuth env before spawning the CLI, fetches subscription metadata on initial login, and moves polling start until after the browser opens. Regression tests cover the server and desktop store paths.

Constraint: Desktop CLI must bypass macOS Keychain by injecting env OAuth when available
Rejected: Keep status endpoint file-based only | reports expired sessions as logged in
Confidence: high
Scope-risk: moderate
Directive: Official desktop auth state must be derived from ensureFreshTokens rather than oauth.json presence alone
Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/haha-oauth-service.test.ts src/server/__tests__/haha-oauth-api.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test
Not-tested: Manual end-to-end desktop OAuth flow against the live provider
2026-04-18 01:45:18 +08:00
程序员阿江(Relakkes)
4b4567bb80 fix(desktop): Official 卡片微调 — cursor-pointer 跟 hover 同步, 登录区加 top padding
Code review follow-ups on 33df639:
- cursor-pointer 从 inner header 挪回 outer div, 跟 hover border 保持同一元素, 避免
  hover 到卡片 border 区域时 cursor 先恢复默认再变 pointer
- login section pt-2 → pt-3, 跟下方内容留足视觉呼吸

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 20:37:31 +08:00
程序员阿江(Relakkes)
33df639c04 feat(desktop): embed ClaudeOfficialLogin into Claude Official card
- Official 卡片改成 flex-col 布局,原 header 内容包在可点击 row
- 激活状态下在 header 下方展示 <ClaudeOfficialLogin> (login / logout button)
- 未激活状态保持原"点击整块切换"UX 不变

Card root 不再带 onClick 以免 login/logout button 的 click 冒泡触发切换。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 20:35:07 +08:00
程序员阿江(Relakkes)
8ae68c1805 fix(desktop): ClaudeOfficialLogin — i18n、错误反馈、冗余清理
Code review follow-ups on ae5437a:
- I1: 使用 useTranslation 替代硬编码中文, 新增 settings.claudeOfficialLogin.* 双语 key
- I2: shellOpen 失败时 console.error + 写入 store.error, 不再静默吞
- M1: disabled={isLoading || false} → disabled={isLoading}
- M2: onClick={() => logout()} → onClick={logout}
- M3: 首次 fetchStatus 失败时显示 error 而非永远 "加载中…"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 20:33:08 +08:00
程序员阿江(Relakkes)
ae5437af43 feat(desktop): add ClaudeOfficialLogin component
未登录时显示引导文案 + 登录按钮(点击 Tauri shell.open 打开浏览器)。
已登录时显示 subscription type + 登出按钮。Polling 由 store 驱动,
浏览器完成 OAuth 后 2s 内 UI 自动更新。

使用 Tailwind + CSS custom properties 对齐项目现有组件风格。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 20:28:29 +08:00
程序员阿江(Relakkes)
e9ae8c93ae fix(desktop): haha-oauth store — logout 停 polling + recursive setTimeout 避免 overlap
Code review follow-ups on f10396e:
- logout() 首行调 stopPolling, 避免登出后 poll timer 还在跑
- setInterval 改为 recursive setTimeout, fetchStatus >2s 时不会产生 parallel 请求

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 20:26:47 +08:00
程序员阿江(Relakkes)
f10396e5bf feat(desktop): frontend API client + zustand store for haha OAuth
- api/hahaOAuth.ts: start/status/logout 3 个方法,基于现有 api client
- stores/hahaOAuthStore.ts: 状态管理 + 2s 间隔轮询 /status

port 从 getBaseUrl() 动态 parse,避免 Tauri 每次启动端口变化时失效。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 20:21:15 +08:00
程序员阿江(Relakkes)
28b1ccf0d2 fix(desktop): 移除 canonical .app 重签名,避免 Keychain ACL 失配导致 403
症状: 桌面端激活 Claude Official provider 后所有请求报
403 "Request not allowed",Tauri 原生产物(target/.../bundle/macos/)
能跑,但脚本输出的 canonical 产物(build-artifacts/macos-arm64/)报错。

根因: 脚本里的 sign_app_bundle() 用 `codesign --force --deep --sign -`
对 sidecar 和外层 .app 重签,想统一 signing identifier,但这会改变
sidecar binary 的 code signature hash。macOS Keychain ACL 按 hash
识别 caller —— 重签后的 sidecar 对 Keychain 来说是"陌生 binary",
弹"允许访问"对话框但 sidecar 无 UI 弹不出,被静默拒绝,CLI 读不到
OAuth token,SDK 构造空 Authorization,Anthropic 返回 403。

修复: 移除 sign_app_bundle 及其调用。Tauri 的 --no-sign flag 只是不做
Developer ID 签名,ad-hoc 签名 Tauri 自己已经做好了,再签一次纯属
多此一举,还破坏了 Tauri 签好的 Keychain ACL 关系。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 21:13:43 +08:00
程序员阿江(Relakkes)
d0cf2544d2 fix(desktop): DMG Finder 美化失败时降级为默认布局而非整脚本 fail
macOS Sequoia+ 对 Finder AppleScript 做了限制,"toolbar visible"
"statusbar visible" 之类的 container window 属性不再支持,会返回
-10006 错误。在 set -e 下导致整个 build-macos-arm64.sh 炸掉,canonical
目录里没有 DMG。

美化失败不是 blocker —— DMG 本身是可用的,只是用户双击打开看到的
是 Finder 默认排布。这里:
- osascript 非零退出只 warn,不让 set -e 中断脚本
- 顺手把 hdiutil detach 也加了 -force fallback,osascript 可能
  把 volume 窗口打开了导致 normal detach 因 "Resource busy" 失败

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 21:02:57 +08:00
程序员阿江(Relakkes)
bea8153d17 fix(desktop): build 脚本加 fresh-build 保险,避免 sidecar 缓存复用
修 src/ 后 build 出来的 .app 里 sidecar 还是旧代码的诡异问题 ——
Bun.build/Tauri bundler 某层缓存把 desktop/src-tauri/binaries/ 里的
旧 sidecar binary 复用进了新 .app,即便删掉 .app 也救不回来,因为
binary 源目录不在 .app 里。

加 3 道保险:
- 硬删 sidecar 源 binary + Tauri bundle + 前端 dist 目录
- 显式跑 bun run build + bun run build:sidecars(set -e 会捕获失败)
- tauri build 用 --config 覆盖 beforeBuildCommand 为 no-op,避免
  build:sidecars 被重复执行浪费 ~10s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 20:40:46 +08:00
程序员阿江(Relakkes)
ec3be47108 fix(desktop): provider 激活后联动重置聊天页 currentModel
切换模型供应商后聊天页底部选择器仍显示上一个 model, 要手动点一下才切过去。
根因: settings.json 的 model 字段 (explicit current model) 不会在
activateProvider/activateOfficial 时被清除, 但新 provider 的模型列表里
往往没这个旧 id, ModelSelector 就卡在"显示旧名字 + radio 不选中"的状态。

providerStore.activateProvider 成功后主动 setModel(provider.models.main)
+ settingsStore.fetchAll() 刷新 availableModels / currentModel。
activateOfficial 对应重置到 'claude-opus-4-6' (与后端 DEFAULT_MODEL 一致)。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 19:45:02 +08:00
程序员阿江(Relakkes)
0b4c742d55 fix(desktop): 修复桌面端多个渲染和权限问题
1. 修复 Tauri CSP 导致 Emotion CSS-in-JS 样式失效的问题
   - Tauri 编译时自动给 style-src 添加 nonce,使 unsafe-inline 失效
   - 添加 dangerousDisableAssetCspModification 阻止 Tauri 修改 style-src
   - 解决 DiffViewer 背景色/缩进等样式丢失

2. 修复 CodeViewer 代码块内容不显示的问题
   - react-shiki 异步加载 WASM 期间返回 null,代码区域空白
   - 添加 CodeArea 组件,纯文本 fallback + MutationObserver 检测加载完成

3. 修复 Bypass 权限模式切换不生效的问题
   - CLI 的 bypassPermissions 需要启动时带 --dangerously-skip-permissions
   - 运行中切换被 CLI 静默拒绝,前端不知情
   - 改为重启 CLI 子进程以正确的参数启动

4. 修复 stream_event tool input JSON 解析失败时 DiffViewer 不渲染
   - content_block_stop 的 JSON parse 失败时暂存到 pendingToolBlocks
   - 由后续 assistant 消息补发完整的 tool input
2026-04-16 10:50:02 +08:00
程序员阿江(Relakkes)
870e03d88b docs(desktop): 全面更新桌面端文档,替换真实截图,修复 DMG 布局
- 重写 02-architecture.md:修正 Rust 函数签名、补充 AdapterSidecar/Updater 插件、更新目录结构
- 重写 03-features.md:补充 ComputerUse/ToolInspection/ImageGallery 等新组件和页面
- 精简 04-installation.md:仅保留 macOS/Windows,新增 Web UI 模式说明
- 精简 index.md:移除过时配图说明表格
- 删除 8 张 AI 渲染概念图,替换为 9 张真实应用截图
- 新增 desktop/README.md:开发/构建指南 + macOS 常见问题
- 修复 DMG 构建脚本:通过 AppleScript 控制图标布局(App 左/Applications 右)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 10:25:36 +08:00
程序员阿江(Relakkes)
ec436986c5 feat(computer-use): 添加 Windows 平台支持
Computer Use 原本仅限 macOS,现在扩展支持 Windows:
- 新增 win_helper.py,用 win32gui/psutil/pyperclip/screeninfo 替代 macOS 专有 API
- 服务端按平台选择 helper 脚本、venv 路径 (bin→Scripts)、Python 命令 (python3→python)
- 前端权限区域仅在 macOS 上显示,Windows 无需 TCC 权限
- 跨平台测试验证两个 helper 的命令集和协议一致性

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 10:03:27 +08:00
Relakkes Yang
5d243b657a feat(desktop): Windows 自定义标题栏 & 服务端改进
- Windows 上隐藏原生菜单栏和窗口装饰,前端渲染自定义窗口控制按钮(最小化/最大化/关闭)
- macOS 保持原生菜单栏不变(条件编译)
- Sidebar 在 Windows 上不再添加 macOS 红绿灯的额外顶部间距
- 新增 Windows 构建脚本和 NSIS 安装钩子
- 服务端 CORS、会话服务、对话服务改进及测试补充

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 21:29:35 +08:00
程序员阿江(Relakkes)
5016bc0049 chore: 移除启动时自动检测认证并跳转设置页的逻辑 2026-04-15 14:43:32 +08:00
程序员阿江(Relakkes)
5fdf50436c test: 更新测试用例适配 provider 隔离与新 API
- providers-real.test.ts 重写适配 ModelMapping 对象格式
- e2e 测试适配 cc-haha/settings.json 路径
- settings.test.ts 验证写入隔离
- models.ts 小调整
2026-04-15 14:19:04 +08:00
程序员阿江(Relakkes)
0e60cadb13 chore(desktop): 构建改进与 UI 完善
- build-sidecars.ts macOS 编译后自动 ad-hoc codesign
- build-macos-arm64.sh 构建流程优化
- tauri.conf.json 配置更新
- i18n 中英文翻译补充
- Settings 页面与 uiStore 调整
2026-04-15 14:18:55 +08:00
程序员阿江(Relakkes)
166fe5b676 fix: Computer Use 点击功能修复
- ensureRuntimeFiles() 改为始终同步源文件而非仅缺失时复制
- pre-authorized apps 增加 tier: 'full',与现有 allowedApps 合并而非替换
- normalizeOsPermissions() 将 screenRecording: null 视为非阻塞
- config 路径改为 cc-haha/computer-use-config.json
- mac_helper.py 改进鼠标点击与坐标处理
2026-04-15 14:18:37 +08:00
程序员阿江(Relakkes)
b002447436 fix: provider 隔离与 Claude 官方认证修复
- provider 配置写入 ~/.claude/cc-haha/settings.json,不再污染原版 settings.json
- 子进程 env 剥离 PROVIDER_ENV_KEYS,防止残留 key 传递
- bin/claude-haha 在 CC_HAHA_SKIP_DOTENV=1 时用 --env-file=/dev/null 阻止 Bun 自动加载 .env
- managedEnv.ts 增加 getCcHahaSettingsEnv() 读取隔离配置
- 新增 /api/providers/auth-status 端点,启动时检测认证状态
- AppShell 启动时无认证则跳转 provider 设置页
2026-04-15 14:18:25 +08:00
程序员阿江(Relakkes)
23b31b397a Prevent local team sessions from dropping members and stalling adapters
This bundles the pending desktop/server team-session fixes with the local adapter recovery changes already in the worktree. The team path now keeps teammate membership stable under concurrent spawns, surfaces real teammate identities in the desktop UI, and allows direct interaction with member transcripts. The adapter changes recover automatically when stale thinking signatures invalidate an existing session.

Constraint: Team config writes can happen concurrently while multiple reviewers spawn in parallel

Constraint: Desktop member views must follow mailbox/transcript semantics rather than hijacking teammate runtime sessions

Rejected: Keep relying on config.json alone for member discovery | in-process teammates can be lost after concurrent writes

Rejected: Open teammate sessionIds as normal desktop sessions | would attach a second CLI instead of the running teammate

Confidence: medium

Scope-risk: moderate

Reversibility: clean

Directive: Preserve locked team-file mutation for any future teammate registration path and keep teammate labels sourced from member names before agent types

Tested: bun test src/server/__tests__/teams.test.ts src/server/__tests__/team-watcher.test.ts

Tested: cd desktop && bun run test --run src/stores/chatStore.test.ts src/pages/ActiveSession.test.tsx

Tested: cd desktop && bun run lint

Tested: cd desktop && bun run build

Not-tested: Manual end-to-end validation against a live Agent Teams run in the desktop app
2026-04-14 17:27:07 +08:00
程序员阿江(Relakkes)
9eb3be4b11 perf: optimize recent projects loading with parallel git queries and caching
目录选择器每次打开都串行执行大量 git 子进程导致卡顿,现改为并行查询 + 前后端双层 30s 缓存,选择新目录时自动失效缓存。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:02:35 +08:00
程序员阿江(Relakkes)
8888e1c3fb chore: comprehensive code review fixes — security, perf, leaks, quality, docs
- security: XSS sanitization with DOMPurify in Markdown/Mermaid/PermissionDialog;
  path whitelist in filesystem API; fake keys in test/config files
- perf: fine-grained Zustand selectors in Sidebar/StatusBar/ContentRouter;
  50ms throttle on streaming deltas; React.memo + useMemo in MessageList;
  useRef for frequent keyboard shortcut state; AbortController 30s timeout
- leaks: WS session TTL timers (5-min cleanup on close); batch splice for
  sdkMessages/stderrLines; folderPath validation in cronScheduler
- quality: optimistic update rollback in settingsStore; error state in
  providerStore/teamStore; i18n for all hardcoded English strings
- docs: desktop architecture and features docs updated; VitePress nav fixed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 22:27:17 +08:00
程序员阿江(Relakkes)
a0fa8751b3 ui(desktop): localize macOS menu items to Chinese (关于/设置)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:57:46 +08:00
程序员阿江(Relakkes)
301e0c3a33 feat(desktop): route macOS native About menu to in-app Settings page
Replace the default macOS about dialog with a custom menu that emits
a Tauri event, navigating to the Settings > About tab within the React app.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:30:57 +08:00
程序员阿江(Relakkes)
bd604429ec feat(desktop): add About page in settings with app info and author links
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:12:46 +08:00
程序员阿江(Relakkes)
8617a4b9c9 feat: scheduled task IM notification + UI fixes
- Add notification config to CronTask (enabled + channels)
- New notificationService: push task results to Feishu/Telegram on completion
- Feishu uses Schema 2.0 interactive card for proper Markdown rendering
- Telegram uses Bot API sendMessage with Markdown parse mode
- NewTaskModal: notification toggle + channel checkboxes with config validation
- Fix: API layer now passes notification field through on task creation
- Fix: View conversation button uses tabStore instead of legacy setActiveView
- Fix: bypass permission dialog shows selected folder path instead of ~

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:42:44 +08:00
程序员阿江(Relakkes)
8c54c48514 ui(desktop): enlarge default window to 1440×960 and show welcome hero on empty session tabs
14" MacBook-friendly default size (was 1280×800). New session tabs now display
the same icon + title welcome screen as the landing page until the first message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 14:15:33 +08:00
程序员阿江(Relakkes)
bfafd26fb6 fix: batch resolve 5 issues from Feishu bug tracker
- fix(desktop): Settings Agents/Skills page layout overflow at narrow widths
  Shift header grid breakpoint md→xl, summary cards sm→3col, add min-w-0/truncate

- fix(desktop): TabBar tab style — remove border-r separators, use bg-only active state
  Drop ::after indicator and inter-tab borders; active tab distinguished by surface bg

- feat(desktop): IM Adapters page — horizontal tabs with Feishu first, Telegram second
  Replace vertical stack with tablist; add ImTabButton with aria-selected + focus-visible

- fix(server): bundled IM session cwd resolves to / instead of selected project
  Root cause: preload.ts chdir(CALLER_DIR) inherits '/' from Tauri-spawned sidecar.
  Fix: pin CALLER_DIR and PWD to session workDir in conversationService spawn env.
  Add diagnostic logs at sessionService, ws/handler, and conversationService.

- fix(feishu): StreamingCard shows duplicate "thinking" indicators
  Merge static loading element into streaming_content; renderedText() controls state.

- test(adapters): add 5 ImageBlockWatcher edge-case unit tests (reset, relative path,
  multi-image chunk, malformed data URI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:36:14 +08:00
程序员阿江(Relakkes)
3e3c2bc116 experiment(desktop): auto-spawn adapter sidecar on launch + restart on save
Wires the new claude-sidecar adapters mode into the actual desktop UX:

* On app launch, Tauri main process now spawns the adapter sidecar right
  after the server sidecar comes up. The sidecar reads ~/.claude/adapters.json
  and connects whichever of Feishu / Telegram has credentials configured;
  if neither does, it warns + skips + exits cleanly (treated as expected).
* When the user saves credentials in the existing AdapterSettings page,
  the frontend store invokes restart_adapters_sidecar after the PUT
  /api/adapters succeeds. Tauri kills the old child and spawns a new one,
  which picks up the fresh config and establishes the WebSocket connection
  to Feishu / Telegram immediately — no app restart needed.

End-to-end behavior is now: install app → open → configure credentials in
settings → click save → IM bot is live.

Implementation
==============

* desktop/src-tauri/capabilities/default.json: replace the stale
  binaries/claude-server allowlist with binaries/claude-sidecar across
  shell:allow-execute, shell:allow-spawn, plus a new shell:allow-kill
  entry needed for the restart path. (P2 changed externalBin to
  claude-sidecar but missed updating capabilities, which is why the prior
  bundle worked at all in dev mode but would have failed in production.)

* desktop/src-tauri/src/lib.rs:
  - new AdapterState that holds an Option<CommandChild>
  - start_adapters_sidecar() spawns `claude-sidecar adapters --feishu --telegram`
    with ADAPTER_SERVER_URL env var pointing at the dynamic server port
    (converted http://→ws:// since WsBridge does `new WebSocket(url)`
    directly without protocol translation)
  - spawn_and_track_adapters_sidecar() handles spawn + state insertion
  - stop_adapters_sidecar() kills + clears state
  - new #[tauri::command] restart_adapters_sidecar that calls stop+spawn
  - sidecar Terminated events are info-logged, not treated as errors,
    so the credential-missing path doesn't show up as a crash
  - setup() spawns the adapter sidecar after server startup completes
  - RunEvent::Exit cleanup also kills adapter sidecar

* desktop/src/stores/adapterStore.ts: after every successful PUT
  /api/adapters, dynamic-import @tauri-apps/api/core and call
  invoke('restart_adapters_sidecar'). Wrapped in try/catch so non-Tauri
  test environments fall through quietly. Triggers on every config
  change (including pairing code generation, paired-user removal) by
  design — keeps the rule simple and guarantees any save takes effect.

* desktop/src/pages/AdapterSettings.tsx: removed the stale "Server URL"
  text input. The field defaulted to ws://127.0.0.1:3456 but the actual
  server uses a dynamic port chosen at startup. Even when filled in
  correctly, loadConfig() in adapters/common/config.ts gives env var
  priority over file value, so this UI control had zero effect inside
  the desktop app. Standalone-mode adapter users can still edit the
  field directly in adapters.json if they need to.

Bundle size: unchanged at 88 MB .app / 37 MB DMG. The Rust changes
add only a few KB to the desktop main binary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 00:55:44 +08:00
程序员阿江(Relakkes)
e58ec4f649 experiment(desktop): add adapters mode to claude-sidecar (Feishu + Telegram)
The Feishu and Telegram IM adapters used to be standalone Bun processes
that the user had to launch manually with bun + .ts source on disk —
which meant they were effectively unreachable from the bundled desktop
app, since the user wouldn't have bun or the source tree.

This adds them as a third mode of the merged claude-sidecar binary:

  claude-sidecar adapters --app-root <path> [--feishu] [--telegram]

The launcher pre-checks credentials via the same `loadConfig()` the
adapters use internally, then conditionally `await import()`s each
enabled adapter whose creds are present. Adapters with missing creds
are warned and skipped, so a partially-configured user (e.g. only
Feishu set up, no Telegram bot token) still gets the working adapter
started cleanly instead of having Telegram's top-level
`process.exit(1)` kill the whole process.

Adapter source code is unchanged — the adapters still self-start at
top-level via Lark.WSClient.start() / grammy bot.start(). Their
SIGINT handlers also still register independently. The only thing
gating runtime is whether bun's static-import follows the dynamic
specifier into adapters/feishu/index.ts and adapters/telegram/index.ts,
which it does.

Bundle impact
=============

  metric          P0+P2 only   +adapters mode   delta
  claude-sidecar  66 MB        68 MB            +2 MB
  .app total      87 MB        88 MB            +1 MB
  .dmg            37 MB        37 MB            0 MB

Both adapter SDKs (@larksuiteoapi/node-sdk and grammy) statically
inline into the binary at a +2 MB cost, fully absorbed by DMG
compression. Compared to the alternative (a separate ~60 MB sidecar
binary per adapter, or even one combined ~60 MB adapter binary)
this is essentially free.

Verification
============

* `claude-sidecar server` regression test still passes (boots, /api/sessions
  → 200, CronScheduler runs)
* `claude-sidecar cli --version` returns 999.0.0-local
* `claude-sidecar adapters` (no flags) → exit 2 with usage error
* `claude-sidecar adapters --feishu --telegram` (no creds) → both warned and
  skipped, exit 1
* `claude-sidecar adapters --feishu` (FEISHU_APP_ID=cli_fake creds) →
  Feishu adapter boots, Lark client `client ready`, attempts API connect,
  fails with 400 from feishu API and gracefully retries (correct
  behavior — fake creds)
* `claude-sidecar adapters --telegram` (TELEGRAM_BOT_TOKEN=fake:token) →
  grammy bot.start() called, getMe API hits with 404, throws GrammyError
  (correct — fake creds)
* `bun test adapters/` → 299 pass / 0 fail
* `bun test src/` → 358 pass / 45 fail / 2 errors, identical to baseline

Scanner change
==============

desktop/scripts/scan-missing-imports.ts now also walks adapters/ in
addition to src/, so any future feature() gated stubs in the adapter
tree get auto-stubbed. As of this commit, adapters/ has 0 missing
imports — all clean.

Next step (UI integration, not done here)
==========================================

To actually wire this into the desktop UX, the Tauri main process needs:
- A "Configure IM adapters" settings page (App ID/Secret, bot token,
  allowed users) that writes ~/.claude/adapters.json
- A "Start/stop Feishu" / "Start/stop Telegram" toggle that spawns
  `claude-sidecar adapters --feishu` (or both) as a managed sidecar,
  monitors lifecycle, restarts on crash

The runtime infrastructure is now in place — that work is purely UI +
Rust spawn glue and can be done independently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 00:44:35 +08:00
程序员阿江(Relakkes)
3c5549e221 experiment(desktop): merge server + cli into one sidecar binary
Replaces the two ~65MB bun-compiled sidecar binaries with a single
~66MB merged binary. The bun runtime + shared dependency code (anthropic
SDK, MCP SDK, ws, undici, etc) was previously duplicated across both —
merging eliminates that duplication entirely.

Combined with the previous P0 commit (static-import inlining + drop
src/ + node_modules/ from Resources), this brings the macOS .app from
the original 435MB baseline down to 87MB (-80%), and the DMG from 113MB
to 37MB (-67%).

Final breakdown of the 87MB .app:
  Contents/MacOS/claude-sidecar         66MB   (was 57+57=114MB)
  Contents/MacOS/claude-code-desktop    18MB   (Tauri Rust main)
  Contents/Resources/icon.icns           2MB
  + plist + frameworks                  ~1MB

This is essentially the floor — bun runtime + Tauri main + minimum
overhead. Going lower would require swapping toolchains.

Implementation
==============

* desktop/sidecars/claude-sidecar.ts (new): single entrypoint that
  takes a positional mode argument ("server" or "cli") then dispatches
  via `await import('../../src/server/index.ts').startServer()` or
  `await import('../../src/entrypoints/cli.tsx')`. Same env / argv setup
  pattern as the old launchers.
* desktop/sidecars/server-launcher.ts + cli-launcher.ts: deleted.
* desktop/scripts/build-sidecars.ts: compiles only claude-sidecar now.
* desktop/src-tauri/tauri.conf.json: externalBin → ["binaries/claude-sidecar"]
* desktop/src-tauri/src/lib.rs: spawns sidecar with leading "server"
  positional arg.
* src/server/services/conversationService.ts resolveBundledCliPath /
  resolveCliArgs: when current process is claude-sidecar, reuses the
  same exe and spawns it with leading "cli" positional arg. Backward
  compat path for old claude-server / claude-cli pair preserved for the
  bin/claude-haha dev mode.

Verification
============

* claude-sidecar cli --version → 999.0.0-local (Claude Code) ✓
* claude-sidecar cli --help → full Commander spec ✓
* claude-sidecar server --port N → HTTP listening, CronScheduler running ✓
* All three above run in /tmp with no src/ or node_modules/ on disk
* bun test on src/ → 358 pass / 45 fail / 1 error, identical to baseline
  (44 fails are pre-existing on main, unrelated to this change)
* Full DMG round-trip via build-macos-arm64.sh succeeds; new .app
  installs cleanly in /Volumes/

Bundle size summary (vs original baseline)
==========================================

  metric              baseline   final      delta
  .app total          435 MB     87 MB      -348 MB (-80%)
  .dmg                113 MB     37 MB      -76 MB  (-67%)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 00:10:47 +08:00
程序员阿江(Relakkes)
662485c3eb experiment(desktop): static-import sidecars + drop src/ + node_modules/ from bundle
Cuts the macOS .app from 435MB → 152MB (-65%) and the DMG from 113MB → 60MB
(-47%) by inlining src/server and src/entrypoints/cli into the bun-compiled
sidecar binaries instead of dynamic-importing them from disk at runtime.

Architectural change
====================

Before:
  desktop/sidecars/server-launcher.ts → bun build --compile (≈57MB shell)
    └─ at runtime: dynamic file:// import of <appRoot>/src/server/index.ts
       which transitively requires ALL of src/ + the entire root node_modules/
       to be shipped as Resource. tauri.conf.json copied 254M of node_modules
       and 47M of src/ into Contents/Resources/app/ on every build.

After:
  desktop/sidecars/server-launcher.ts → bun build --compile (≈65MB)
    └─ uses `await import('../../src/server/index.ts')` with a literal
       specifier so bun's bundler walks the whole graph statically and
       inlines everything into the binary.

  Same treatment for cli-launcher.ts → src/entrypoints/cli.tsx.

Resolver gymnastics
===================

This fork carries dozens of ant-internal feature() gated require/import
calls referencing modules that simply don't exist on disk
(cachedMicrocompact, devtools, proactive, coordinator, etc). Bun's resolver
walks the static dep graph BEFORE bun:bundle macro DCE, so even though
the dead branches never execute at runtime, they still fail to resolve
at compile time.

Two complementary mechanisms:

1. desktop/scripts/scan-missing-imports.ts walks src/, regex-greps every
   relative import / require / type-import specifier, and writes a Proxy
   noop stub for any target that doesn't exist on disk. Stubs are tagged
   with "@generated stub from scan-missing-imports" for idempotency. Text
   resources (.md / .txt / .json) get appropriate format-specific stubs.
   Runs as a pre-step inside build:sidecars.

2. desktop/scripts/build-sidecars.ts adds an `external: [...]` list for
   bare-package optional deps not in package.json (OTLP exporters,
   @aws-sdk/*, @anthropic-ai/{bedrock,vertex,foundry,mcpb}-sdk,
   @azure/identity, fflate, turndown, sharp, react-devtools-core).
   These remain runtime imports, fail benignly when their gating env
   var or feature flag is off.

Tauri side
==========

- desktop/src-tauri/tauri.conf.json: dropped all `resources` entries.
  Was 7 entries totaling ≈301MB. Now `{}`.
- desktop/src-tauri/src/lib.rs `resolve_app_root` no longer calls
  BaseDirectory::Resource (the app/ resource dir doesn't exist anymore);
  instead returns the directory of the current sidecar exe. The launchers
  still accept --app-root for backward compat with conversationService's
  CLI subprocess spawn.

Optimisations
=============

- bun build now uses minify whitespace+identifiers+syntax. Saved another
  ≈16MB across both binaries (server: 72MB→65MB, cli: 75MB→66MB).

Bonus fix
=========

src/services/remoteManagedSettings/index.ts had a typo importing
'./securityCheck.jsx' instead of '.js'. Bun's runtime resolver tolerated
it; bun build didn't.

Verification
============

- Both binaries boot successfully in /tmp with no src/ or node_modules/
  on disk. Verified `claude-cli --version` returns the build version,
  `claude-cli --help` prints the full Commander spec, and claude-server
  starts CronScheduler + listens on the requested port.
- bun test on src/ shows 358 pass / 45 fail / 2 errors vs main baseline
  of 359 / 44 / 2 — net 0 new failures (1 different flake direction).
  All 44 baseline failures pre-exist on main and are unrelated.
- Full DMG round-trip via build-macos-arm64.sh succeeds; new bundle
  installs cleanly in /Volumes/.

Bundle size summary
===================

  metric              baseline   after P0   delta
  Resources/app/      301 MB     0 MB       -301 MB
  MacOS/claude-server  57 MB     65 MB      +8 MB
  MacOS/claude-cli     57 MB     66 MB      +9 MB
  MacOS/claude-code-desktop  18 MB  18 MB   —
  ─────────────────────────────────────────────
  .app total          435 MB     152 MB     -283 MB (-65%)
  .dmg                113 MB     60 MB      -53 MB (-47%)

Generated stub files (173 of them under src/) are committed for
reproducibility — the scanner is idempotent and will re-create them
identically on every build, but tracking them avoids dirty working trees
on first compile.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 00:05:39 +08:00
程序员阿江(Relakkes)
fc7d6d487c fix(desktop): collapse session task bar by default
Task card was expanded by default and overlapped the main chat area
whenever todos appeared. Start collapsed and reset expanded state on
clearTasks so new sessions also begin collapsed; users can click the
header to expand when needed.
2026-04-11 11:42:16 +08:00
程序员阿江(Relakkes)
635a966c3e fix(desktop): unblock rollout with reliable session and IM flows
This folds together the desktop-side fixes needed before broader rollout.
Session resume no longer deadlocks waiting on init, Mermaid and inline image
output render inside chat, task and sub-agent state stay visible during
execution, local build/release paths are safer, and Feishu/Telegram now expose
lightweight mobile commands (/help, /status, /clear) without adding a new
adapter-specific protocol.

Constraint: Desktop releases must publish updater artifacts from non-draft GitHub releases
Constraint: IM commands need short, phone-friendly responses and low operational complexity
Rejected: Add a dedicated IM command API surface | re-used existing slash commands and session/task REST endpoints to keep adapters thin
Rejected: Wait for task_update push events in WebUI | added low-risk polling because the current frontend ignores that event path
Confidence: medium
Scope-risk: broad
Reversibility: clean
Directive: Keep IM command replies terse and mobile-first, and merge local fallback slash commands when server-provided lists are partial
Tested: cd desktop && bun x vitest run src/components/chat/MermaidRenderer.test.tsx src/components/markdown/MarkdownRenderer.test.tsx
Tested: cd desktop && bun x vitest run src/components/chat/composerUtils.test.ts src/pages/ActiveSession.test.tsx src/stores/chatStore.test.ts
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/conversations.test.ts --test-name-pattern "SDK init arrives only after the first user turn" --timeout 60000
Tested: cd adapters && bun test common/ feishu/ telegram/
Tested: cd adapters && bunx tsc --noEmit
Not-tested: Full GitHub Actions release run on all three desktop platforms
Not-tested: Local DMG packaging end-to-end on Apple Silicon
Not-tested: Real Feishu/Telegram device sessions against a live adapter process
2026-04-10 16:41:59 +08:00
程序员阿江(Relakkes)
7983cce478 feat(desktop): add CI/CD release pipeline, auto-updater, and installation guide
- Add GitHub Actions workflow for 5-platform builds (macOS ARM/x64, Linux x64/ARM64, Windows x64)
- Integrate tauri-plugin-updater with signing key and UpdateChecker UI component
- Add version management release script (scripts/release.ts)
- Add installation guide with Gatekeeper/SmartScreen bypass instructions
2026-04-10 11:00:46 +08:00
程序员阿江(Relakkes)
98a24f99b4 feat(proxy): add multi-protocol proxy for OpenAI-compatible providers
Add a protocol-translating reverse proxy that allows using OpenAI-compatible
API providers (DeepSeek, OpenRouter, Groq, etc.) with Claude Code.

The proxy intercepts Anthropic Messages API requests from the CLI, transforms
them to OpenAI Chat Completions or Responses API format, forwards to the
upstream provider, and transforms streaming/non-streaming responses back.

Key features:
- Request transform: Anthropic Messages → OpenAI Chat/Responses
- Response transform: OpenAI → Anthropic (streaming SSE + non-streaming)
- Provider-agnostic reasoning support (reasoning_content, thinking_blocks,
  reasoning fields from DeepSeek, OpenAI o-series, GLM-5, Groq, etc.)
- Event queue pattern for correct Anthropic SSE event ordering
- Two-step test: ① connectivity check ② full proxy pipeline validation
- Desktop UI: API format selector, two-step test results display
- License attribution for cc-switch (MIT, Jason Young)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 00:33:26 +08:00
程序员阿江(Relakkes)
85969318b9 refactor(desktop): remove i18n from spinner verbs, use English only
Drop Chinese spinner verb array and locale-based selection. StreamingIndicator
now uses English status verbs directly without going through the i18n system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 22:56:55 +08:00