Commit Graph
234 Commits
Author SHA1 Message Date
Wujie Li 3f19290c8d fix: preserve paragraph breaks between text blocks in bridge responses (#144)
When Claude's response includes tool calls (Read, Edit, Bash, etc.),
the response text is split into multiple content blocks — one before
each tool call and one after. These blocks were joined with an empty
string (.join('')), causing all paragraphs to concatenate without
any spacing in the final Telegram message.

Changed .join('') to .join('\n\n') so paragraph breaks between text
blocks are preserved, matching how they appear in the desktop UI.
2026-03-05 11:00:06 +08:00
jasonjaclyn2017andClaude Opus 4.6 a1bde18cd1 feat: add View Plan modal and feedback textbox to plan approval UI (#164)
* feat: add "View Plan" button with fullscreen modal to ExitPlanMode UI

When Claude writes a plan in plan mode, the plan content is written to a
file on disk but the ExitPlanMode UI only shows Approve/Reject buttons —
the user never sees the actual plan without manually finding the file.

Add a "View Plan" button that opens a fullscreen Dialog modal showing
the plan content rendered as markdown. The plan content is extracted from
the tool uses array (the Write tool's input for the plan file), so no
new API endpoints are needed.

Changes in StreamingMessage.tsx:
- extractPlanContent(): walks toolUses backwards to find the last Write
  to a plans/*.md file and returns its content
- ExitPlanModeUI: accepts toolUses prop, renders "View Plan" button
  (hidden if no plan content found), opens Radix Dialog modal with
  MessageResponse markdown renderer
- Modal is 80vh tall, max-w-4xl, scrollable, with Close button

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add "Do this instead" feedback textbox to plan approval UI

When rejecting a plan, users can now type feedback and click "Do this
instead" (or press Enter) to send a custom deny message to the SDK.
This matches the Claude Code CLI behavior where users can provide
alternative instructions when rejecting a plan.

Previously, the deny message was hardcoded to "User denied permission"
— the API already supported custom messages but the client never sent
them.

Changes:
- StreamingMessage.tsx: add feedback textbox + "Do this instead" button
  to ExitPlanModeUI, wire onDenyWithMessage callback
- ChatView.tsx: pass denyMessage through handlePermissionResponse
- MessageList.tsx: update onPermissionResponse type signature
- stream-session-manager.ts: add denyMessage param to
  respondToPermission(), use it in the deny decision body

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: fetch plan content from disk so View Plan always shows latest version

Previously extractPlanContent read the Write tool's input.content, which
showed stale content when Claude later edited the plan via the Edit tool.

Now extractPlanFilePath finds the plan file path (matching both Write and
Edit tools, with Windows backslash support), and the View Plan button
fetches the file from disk on each click via /api/files/preview.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:00:02 +08:00
jasonjaclyn2017andClaude Opus 4.6 a906172807 fix: handle SDK keep_alive messages to prevent idle timeout during long operations (#163)
The stream idle timer (330s) fires during long-running Claude Code tool
executions because the SDK's keep_alive heartbeat messages are silently
dropped. This causes a false "Stream idle timeout" error even though
Claude is still actively working.

Root cause: claude-client.ts only handles 6 SDK message types in its
switch statement and has no default case, so keep_alive messages from
the SDK are never forwarded to the frontend. The idle timer in
stream-session-manager.ts never gets reset by markActive().

Fix: thread keep_alive through the existing 3-layer event pipeline:
- claude-client.ts: forward keep_alive as SSE event (via default case)
- useSSEStream.ts: add onKeepAlive callback to SSECallbacks interface
- stream-session-manager.ts: call markActive() on keep_alive to reset
  the idle timer

No visible UI change — keep_alive only resets the timer silently.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:59:43 +08:00
315aac0f04 fix: sanitize __NEXT_PRIVATE_* env vars from child processes (#161)
* fix: preserve paragraph breaks between text blocks in bridge responses

When Claude's response includes tool calls (Read, Edit, Bash, etc.),
the response text is split into multiple content blocks — one before
each tool call and one after. These blocks were joined with an empty
string (.join('')), causing all paragraphs to concatenate without
any spacing in the final Telegram message.

Changed .join('') to .join('\n\n') so paragraph breaks between text
blocks are preserved, matching how they appear in the desktop UI.

* fix: sanitize __NEXT_PRIVATE_* env vars from child processes

The bundled Next.js standalone server sets __NEXT_PRIVATE_STANDALONE_CONFIG
and __NEXT_PRIVATE_ORIGIN in process.env at runtime. These leak into every
child process spawned by the Electron main process (Claude Code CLI,
node version checks, package installs, etc.), which in turn pollutes
tmux/terminal sessions.

When other Next.js projects detect __NEXT_PRIVATE_STANDALONE_CONFIG in their
environment, they skip normal next.config loading and use CodePilot's baked-in
config instead — silently breaking builds and dev servers system-wide.

Fix: strip all __NEXT_PRIVATE_* variables when constructing env objects for
child processes. The standalone server.js already sets these internally, so
they don't need to be passed from the parent.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
2026-03-05 10:59:39 +08:00
7418andClaude Opus 4.6 113e15e2f4 feat: Discord bridge adapter — 完整实现 Discord 作为第三个桥接渠道
新增文件:
- discord-adapter.ts: 基于 discord.js v14 的适配器,支持 DM/频道消息、
  按钮交互、流式预览(edit-based)、@提及过滤(兼容角色 mention)、
  图片附件下载、!→/ 命令归一化、消息去重、typing indicator
- markdown/discord.ts: Discord 2000 字符分段,代码块围栏自动平衡
- API routes: settings CRUD(Token 掩码)+ verify(Discord REST 验证)
- DiscordBridgeSection.tsx: 设置 UI(凭据、授权、服务器策略、配置指南含 ID 获取方法)
- discord-bridge.test.ts: 22 项单元测试(chunking/auth/command/HTML 转换)

修改文件:
- bridge-manager.ts: Discord 分段投递 + channel-aware stream config
  (Discord 默认 1500ms/40chars/1900max vs Telegram 700ms/20/3900)
- BridgeLayout/BridgeSection: Discord 渠道开关和侧栏入口
- bridge/settings/route.ts: 新增 14 个 Discord 设置键
- next.config.ts: serverExternalPackages 添加 discord.js/zlib-sync
- i18n: 中英文完整翻译(30+ keys)
- docs/handover/bridge-system.md: Discord 架构文档

依赖:discord.js ^14.18.0, zlib-sync ^0.1.9

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:32:46 +08:00
7418andClaude Opus 4.6 538cc1b7fd docs: 工程质量保障体系执行计划文档
- 新建 completed/engineering-quality-assurance.md:记录 Harness Engineering
  全部 5 个已完成 Phase 和 4 个未实现 Phase 的详细内容
- 更新 exec-plans/README.md 索引

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:08:49 +08:00
7418andClaude Opus 4.6 935e3d030c fix: i18n 品牌名修正 — Claude → CodePilot
- en.ts / zh.ts: messageList.claudeChat 和 emptyDescription 中的
  "Claude" 改为 "CodePilot",与应用品牌一致

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:00:52 +08:00
7418andClaude Opus 4.6 a8e1bca9d3 chore: bump version to v0.25.1
工程化改进版本:
- 激活验证闭环(pre-commit hook + CI gate + npm test scripts)
- CLAUDE.md 改造 + ARCHITECTURE.md 架构导航
- 执行计划体系 + 技术债务追踪
- chrome-devtools MCP 集成 + UI 验证规则

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:54:53 +08:00
7418andClaude Opus 4.6 a7104aa4eb chore: 集成 chrome-devtools MCP + UI 验证规则
.mcp.json(新建):
- 添加 chrome-devtools MCP server(stdio 模式,npx chrome-devtools-mcp@latest)
- 签入仓库使团队成员 clone 后自动启用

CLAUDE.md:
- 新增"UI 改动必须用 CDP 验证"规则:
  修改组件/样式/布局后,通过 chrome-devtools MCP 截图确认渲染正确、
  检查 console 无报错、模拟交互验证、响应式布局用 device emulation 验证

目的:建立 UI 改动的自动化视觉验证闭环,
AI 修改前端代码后自动启动浏览器截图检查,而非仅靠 typecheck 判断正确性。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:50:18 +08:00
7418andClaude Opus 4.6 62dbecd5dc docs: 执行计划体系 + 技术债务追踪 + CLAUDE.md 执行计划规则
docs/exec-plans/(新建目录):
- README.md — 执行计划规范、模板、何时需要写执行计划
- active/context-storage-migration.md — 从 research/ 迁移,标注 Phase 0-3 进度状态
  (Phase 0 部分完成:sdk_cwd 上线;projects 表和 canUpdateSdkCwd 未实现)
- tech-debt-tracker.md — 4 项已知技术债务(ESLint 存量 error、内存态注册表、
  固定 50 条 fallback、Phase 0 剩余项)

CLAUDE.md:
- 新增"执行计划"区块:中大型功能必须先写执行计划再开工
- 文档区块增加 exec-plans/ 索引

ARCHITECTURE.md:
- 关键文档索引增加执行计划和技术债务条目

docs/research/README.md:
- context-storage-migration-plan 条目增加交叉引用指向执行文档

docs/CLAUDE.md:
- 增加 exec-plans/ 目录说明和文档分类规则

目的:确保 AI 后续会话能自动遵循执行计划流程,
读取进度状态而不需要重新口头解释历史。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:44:12 +08:00
7418andClaude Opus 4.6 e0c38af4fa docs: CLAUDE.md 改造为路由层 + 新建 ARCHITECTURE.md
CLAUDE.md:
- 顶部增加指向 ARCHITECTURE.md 的导航
- 新增"改动自查"区块(i18n / 数据库 / 类型 / 文档四项检查)
- 文档区块增加 ARCHITECTURE.md 索引
- 保持所有原有规则不变,仅重组结构使其更清晰

ARCHITECTURE.md(新建,~120 行):
- 技术栈概述(Electron 40 + Next.js 16 + SQLite)
- 目录结构地图(src/ 下所有一级目录及关键文件职责)
- 数据流图(用户输入 → API → SDK → SSE → 渲染 → 持久化)
- Bridge 数据流图(IM → Adapter → Router → Engine → Delivery → IM)
- 数据库 12 张表索引
- Bridge 子系统核心组件说明
- 新增功能标准触及点清单(类型/数据库/API/页面/组件/Hook/i18n/Bridge)
- 关键文档索引(handover + research)
- 技术栈速查表

目的:减少每次 Claude Code 会话的探索成本,直接跳转到正确位置。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:38:52 +08:00
7418andClaude Opus 4.6 99c4931b12 chore: 激活验证闭环 — pre-commit hook + CI gate + npm test scripts
三层质量保障闭环:

package.json:
- 新增 typecheck / test:unit / test:smoke / test:e2e / test / prepare 脚本
- 新增 lint-staged 配置(仅对暂存 .ts/.tsx 文件运行 eslint --fix)
- 新增 devDependencies: husky, lint-staged, tsx

.husky/pre-commit:
- 依次执行 lint-staged → tsc --noEmit → 单元测试
- 任一步骤失败即阻止提交

src/__tests__/e2e/smoke.spec.ts:
- 5 个 Playwright @smoke 测试覆盖核心路由 (/, /chat, /plugins, /plugins/mcp, /settings)
- 检查 HTTP 状态、Next.js 错误覆盖层、标题、console 错误
- 复用已有 helpers (goToChat, goToPlugins, goToMCP, goToSettings, collectConsoleErrors, filterCriticalErrors)

.github/workflows/build.yml:
- 新增 lint-test job (ubuntu-latest): lint → typecheck → unit tests
- 新增 pull_request 触发器
- 三个 build job 添加 needs: [lint-test],lint-test 失败则不构建
- lint 步骤暂设 continue-on-error(存量 4 个 lint error 待修复)

CLAUDE.md:
- 新增"自检命令"区块,说明 test / test:smoke / test:e2e 用途

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:33:01 +08:00
7418andClaude Opus 4.6 243724a765 feat: 飞书(Feishu/Lark)Bridge 适配器 + 发布 v0.25.0
新增飞书 IM 通道,通过 @larksuiteoapi/node-sdk WSClient 接收消息、REST Client 发送回复。

适配器核心 (feishu-adapter.ts):
- WSClient 长连接接收 im.message.receive_v1 事件
- 消息去重 (message_id LRU 1000)、授权校验、群聊策略 (open/allowlist/disabled)
- @提及检测 (bot open_id/user_id/union_id 匹配)
- 图片/文件/音频/视频/富文本(post) 下载 → base64 FileAttachment
- Typing 指示器 (Openclaw 方案: emoji reaction)
- /perm 文本命令处理权限审批

渲染策略 (markdown/feishu.ts):
- 代码块/表格 → schema 2.0 interactive card (markdown 元素)
- 纯文本 → post (md tag), 支持粗体/斜体/行内代码/链接
- 权限卡片 → schema 2.0 card + /perm 文本命令 (无按钮, 见下方决策)
- 每层降级: card → post → text

关键决策 — 权限交互无按钮:
- 飞书 card.action.trigger 回调需 HTTP webhook, WSClient 不支持
- schema 2.0 不支持 action 标签 (错误码 200861)
- schema 1.0 按钮可渲染但点击报 200340 (无 webhook 端点)
- Openclaw 用 http.createServer + Lark.adaptDefault 解决, CodePilot 桌面端无公网端点
- 最终方案: 格式化卡片 + /perm 文本命令, 用户复制发送即可审批

其他变更:
- permission-broker.ts: permissionRequestId 去重 (30s TTL)
- telegram-bot.ts: bridge 模式互斥 (globalThis 标志)
- bridge-manager.ts: 飞书渲染分发 (deliverResponse feishu 分支)
- UI: FeishuBridgeSection 设置页 + 侧边栏入口 + 通道开关
- i18n: 中英文飞书配置文案
- 交接文档: bridge-system.md 更新飞书架构、数据流、6 项设计决策

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:03:13 +08:00
7418andClaude Opus 4.6 b1a043a995 feat: Telegram 流式预览(sendMessageDraft)+ 出站渲染解耦 + 投递容错
Bridge 新增"边生成边显示"能力,利用 Telegram Bot API 9.5 的
sendMessageDraft 在私聊中以草稿形式流式展示 Claude 生成进度。
架构上抽象为通道级可选能力,便于后续扩展到其他 IM。

types.ts
- 新增 PreviewCapabilities、StreamingPreviewState 接口

channel-adapter.ts
- BaseChannelAdapter 新增三个可选方法:
  getPreviewCapabilities / sendPreview / endPreview
- sendPreview 返回 'sent'|'skip'|'degrade' 三态,
  区分瞬时失败(跳过)和永久失败(降级)

telegram-utils.ts
- 新增 sendMessageDraft() API 封装(plain text,截断到 4096)

telegram-adapter.ts
- 实现 getPreviewCapabilities: 开关检查 + 私聊/群聊检测 + 降级状态
- 实现 sendPreview: 调 sendMessageDraft,400/404 永久降级,429 仅跳过
- 实现 endPreview: 空操作
- stop() 清空 previewDegraded Set

conversation-engine.ts
- 新增 OnPartialText 回调类型
- processMessage / consumeStream 透传 onPartialText 参数
- 使用独立 previewText 变量(只累积、不因 tool_use 清零),
  避免预览文本在工具调用后回退/抖动

bridge-manager.ts
- 流式预览状态机:generateDraftId / getStreamConfig / flushPreview
  节流闭包(间隔+最小增量+trailing-edge timer),finally 清理
- flushPreview 仅在 'degrade' 时降级,'skip'/网络错误不降级
- 新增 deliverResponse() 按 channelType 分发渲染:
  Telegram → markdownToTelegramChunks + deliverRendered
  其他 IM → deliver() 纯文本(内部自动分块)
  解除 handleMessage 对 Telegram 渲染的硬编码依赖

delivery-layer.ts
- deliverRendered: chunk 失败后继续投递剩余 chunk(不中断),
  追发截断提示通知用户,返回 ok:false 标识不完整投递
- deliver: 保持原有 fail-fast 行为(短消息无需跳过)

route.ts (bridge/settings)
- BRIDGE_SETTING_KEYS 白名单追加 5 个流式配置项

docs/handover/bridge-system.md
- 更新目录结构、数据流、设计决策(#12 流式预览)、设置项表

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 23:20:47 +08:00
7418 6dfe23a9a9 fix: 修复百炼 API key 配置并发布 v0.23.1 2026-03-03 16:36:10 +08:00
7418andClaude Sonnet 4.6 5413eb55ba refactor: 简化 macOS 构建 — 只签名不公证,禁用原生热更新
CI (.github/workflows/build.yml):
- 删除 mac_notarize 输入、notarization strategy/credentials 步骤
- 双架构回归单步构建,统一 -c.mac.notarize=false
- 证书缺失时硬失败,不 fallback ad-hoc
- 上传产物移除 blockmap / latest-mac.yml
- release 收集资产移除 blockmap / yml 元数据

electron-builder.yml:
- mac.notarize 固定为 false

Electron 主进程 (main.ts):
- 移除 initAutoUpdater / setUpdaterWindow 调用和 import

Preload (preload.ts):
- 移除 updater bridge,不再暴露 electronAPI.updater

Updater (updater.ts):
- 恢复为 no-op stub

前端 AppShell 无需改动 — isNativeUpdater 运行时检测 updater 不存在,
自动走 /api/app/updates 浏览器模式(全量下载链接)。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 13:27:35 +08:00
7418andClaude Sonnet 4.6 5629da9571 fix: arm64 打包脚本改用 if/else 分支替代 EXTRA_ARGS 数组
bash 数组展开在 GitHub Actions 中不稳定,
改为 NOTARIZE 变量 + if/else 直接调用不同命令行。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 11:45:10 +08:00
7418andClaude Sonnet 4.6 db86644c7e feat: macOS 双架构分离构建 — x64 只签名,arm64 签名+条件公证
CI:
- 拆分为 x64 和 arm64 两个独立构建步骤
- x64: 签名但不公证(-c.mac.notarize=false),timeout 15min
- arm64: 签名 + 按版本号条件公证(vX.Y.0 公证,vX.Y.Z 只签名),timeout 35min
- 先跑 x64,stash 产物后跑 arm64,确保 latest-mac.yml 来自 arm64

Electron:
- electron/main.ts: darwin+x64 跳过 initAutoUpdater
- electron/preload.ts: darwin+x64 不暴露 updater bridge
- 前端 AppShell 已有运行时检测(!!window.electronAPI?.updater),
  x64 自动 fallback 到 browser 模式更新检查

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 11:35:50 +08:00
7418andClaude Sonnet 4.6 2c76b04729 feat: macOS 公证策略 — 大版本公证,小版本只签名
- workflow_dispatch 新增 mac_notarize 输入(auto/on/off)
- auto 模式:vX.Y.0 签名+公证,vX.Y.Z(Z>0) 只签名不公证
- 打包超时调整为 35 分钟,notarize=false 时传 -c.mac.notarize=false
- 凭证校验步骤仅在需要公证时执行

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 11:17:28 +08:00
7418andClaude Sonnet 4.6 0ac424f408 fix: macOS 打包步骤添加 25 分钟超时限制
避免 notarization 排队无限等待消耗 CI 额度。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 11:08:37 +08:00
7418andClaude Sonnet 4.6 f50a8879db fix: macOS 打包步骤添加 notarization 重试(最多 3 次)
Apple notarization 服务偶发网络超时,加递增间隔重试(60s/120s)。
同时启用 DEBUG=electron-notarize* 方便排查 notarization 日志。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 09:56:42 +08:00
7418andClaude Sonnet 4.6 c24dedcffe fix: CI 添加 notarization 凭证预校验步骤
在打包前用 xcrun notarytool history 验证 APPLE_ID / APP_SPECIFIC_PASSWORD / TEAM_ID
凭证组合是否有效,提前暴露凭证配置问题。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 01:30:09 +08:00
7418andClaude Sonnet 4.6 8749adaeb9 fix: CI 证书解码去除换行 + openssl 预校验
printf + tr -d 去除 base64 中可能的 \r\n,
openssl pkcs12 -noout 在签名前验证 .p12 格式和密码正确。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 01:14:42 +08:00
7418andClaude Sonnet 4.6 261d17c059 fix: CI 解码 base64 证书为 .p12 文件后传给 CSC_LINK
electron-builder 把 base64 字符串当文件路径解析导致构建失败。
改为先 base64 --decode 写入 /tmp/cert.p12,CSC_LINK 指向文件路径。
同时加 HAS_CERT 环境变量控制签名/验证步骤的条件执行。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 00:57:57 +08:00
7418andClaude Sonnet 4.6 0e38e4460e feat: macOS Developer ID 签名 + notarization + 原生热更新
签名 & 公证:
- build/entitlements.mac.plist, build/entitlements.mac.inherit.plist — Electron hardened runtime 所需权限(JIT、unsigned memory、disable library validation)
- electron-builder.yml — 启用 hardenedRuntime、entitlements、notarize: true
- scripts/after-sign.js — 环境变量 + codesign -d 双重检测,有 Developer ID 时跳过 ad-hoc;无证书时 fallback ad-hoc
- .github/workflows/build.yml — 映射签名 secrets(MAC_CERT_P12_BASE64 等),验证步骤断言 Developer ID Authority + TeamIdentifier,遍历双架构 .app

原生热更新:
- electron/updater.ts — 从 no-op 重写为完整 electron-updater 实现(autoDownload:false, IPC handlers, 启动 10s 延迟检查)
- electron/preload.ts — 恢复 updater IPC bridge
- src/components/layout/AppShell.tsx — 运行时检测 native updater,useEffect 监听 updater:status 事件,native 优先 + browser fallback

其他:
- README_CN.md — 更新自动更新功能描述
- package.json — 版本号升至 0.23.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 00:46:51 +08:00
7418andClaude Opus 4.6 ddb2c00a8c fix: Skills API 增加项目级 .claude/skills 扫描
列表接口 (route.ts):
- 新增 getProjectSkillsDir + scanProjectSkills 扫描 .claude/skills/*/SKILL.md
- 解析 YAML front matter 取 name/description,source 为 "project"
- 与 project commands 去重(commands 优先)

按名查找接口 ([name]/route.ts):
- findSkillFile 查找链路扩展:project commands → project skills → global → installed
- 支持目录名直接匹配和 front matter name 匹配
- GET 对 SKILL.md 从 front matter 取 description
- PUT 增加 cwd 参数支持

修复与 Agent SDK 的目录约定不一致问题:SDK 会加载项目级
.claude/skills,但本项目 API 之前只扫 commands 目录和用户级 skills。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:30:57 +08:00
7418andClaude Opus 4.6 5dda2ea1e3 fix: CI release 步骤处理含空格的文件名
Windows 安装包文件名 "CodePilot Setup x.x.x.exe" 含空格,
$FILES 变量展开时被 bash word splitting 拆成多个参数导致
gh release create 找不到文件。改用 bash 数组 + "${FILES[@]}"
安全展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:55:51 +08:00
7418andClaude Opus 4.6 38e7c96daa feat: Telegram Bridge 出站消息 Markdown 渲染
新增三层 Markdown → Telegram HTML 渲染管线:

markdown/ir.ts — markdown-it 解析 Markdown 为中间表示 (MarkdownIR)
  - 支持 bold/italic/strikethrough/code/code_block/blockquote/links/lists/headings/tables/hr
  - 表格使用 code-block 模式渲染为 ASCII 表格(<pre> 保留对齐)
  - HTML 内联 <br> 标签转换为换行符

markdown/render.ts — 通用标记渲染器
  - boundary tracking + LIFO stack 处理嵌套样式

markdown/telegram.ts — Telegram 专用渲染器
  - 样式映射到 Telegram HTML 标签 (b/i/s/code/pre+code/blockquote/a)
  - 文件引用保护:README.md/main.go 等不被误识别为 URL
  - render-first 分片:按渲染后 HTML 长度分片,超限时按比例重分割

delivery-layer.ts — 新增 deliverRendered() 接收预渲染 chunks
  - HTML + plain text 双通道,解析失败自动降级纯文本
  - sendWithRetry 支持 plainFallback 参数

bridge-manager.ts — Claude 回复改用 markdownToTelegramChunks + deliverRendered
  - 命令响应和错误消息保持 escapeHtml + deliver

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:38:12 +08:00
7418andClaude Opus 4.6 8526b58d59 docs: 更新 bridge 交接文档 — 图片接收能力
- 目录结构增加 telegram-media.ts
- 数据流图补充图片/相册处理路径
- 设计决策 #2 补充连续水位推进机制
- 新增决策 #9(图片接收)和 #10(DB 格式统一)
- 设置项表增加 image_enabled 和 max_image_size

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:06:48 +08:00
7418andClaude Opus 4.6 69d33df5b6 fix: Bridge 图片消息在桌面端聊天记录中回显
conversation-engine 保存用户消息时,复用桌面端 chat route 的存储格式:
- 将 base64 图片写入 .codepilot-uploads/ 目录
- 用 <!--files:[{id,name,type,size,filePath}]--> 格式嵌入消息 content
- 桌面 UI 的 MessageItem.parseMessageFiles() 可直接解析并通过
  FileAttachmentDisplay + /api/uploads 渲染图片

将 getSession() 调用提前到 addMessage 之前,确保 workingDirectory 可用。
写入失败时降级为 [N image(s) attached] 文本指示。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 12:58:50 +08:00
7418andClaude Opus 4.6 887870c343 fix: Telegram 图片接收 — offset 安全、caption 兜底、拒绝通知
P1a: caption-only 消息丢失
- poll loop 文本提取统一为 m.text ?? m.caption ?? ''
- 修复 image_enabled=false 时带 caption 的图片消息被跳过的回归

P1b: offset 越序风险
- markUpdateProcessed 改为连续水位推进(contiguous walk),
  不再直接跳到 updateId+1,避免越过尚未 flush 的相册 ID
- flushMediaGroup 成功入队后预注册所有 buffered ID 到 recentUpdateIds,
  保证 ack 时水位能连续推过整个相册

P2: 图片拒绝通知
- telegram-media: 统一返回 MediaDownloadResult { attachment, rejected, rejectedMessage }
  reject code 覆盖 too_large | unsupported_type | download_failed
  所有超限检查点(file_size/content-length/buffer)均返回可读文案
- telegram-adapter: processSingleImageMessage/flushMediaGroup 收集拒绝结果后
  直接通过 this.send() 发送通知到 Telegram,禁止静默丢弃

P3: 清理 lint warning
- 移除未使用的 escapeHtml import、QueueEntry interface
- 移除 downloadFileById 未使用的 expectedSize 参数

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 12:53:55 +08:00
7418andClaude Opus 4.6 570d26de3a feat: Telegram Bridge 图片接收能力
新增 telegram-media.ts 模块,处理 Telegram 图片下载与 base64 转换:
- selectOptimalPhoto: 选最优尺寸(长边 ≥ 1568px 的最小版本)
- downloadPhoto/downloadDocumentImage: getFile API → 下载 → base64,3 次重试 + 指数退避
- document 类型图片超 20MB 预检拒绝,提示用户按照片发送
- kill switch: bridge_telegram_image_enabled 设置项

扩展 telegram-adapter.ts poll loop:
- TelegramUpdate 类型增加 caption/photo/document/media_group_id
- 单图消息直接下载入队
- 相册消息 500ms 防抖合并后批量下载,构建单条 InboundMessage
- offset 安全:相册使用 max(updateIds) 保证不丢消息

透传 attachments 至 Claude vision pipeline:
- types.ts: InboundMessage 新增 attachments 可选字段
- conversation-engine.ts: processMessage 新增 files 参数,传入 streamClaude
- bridge-manager.ts: 允许纯图片消息通过,透传 attachments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 12:37:13 +08:00
7418andClaude Opus 4.6 c9f1b62809 fix: 模型切换串台 + 发送消息后滚动不到底部
1. page.tsx — loadSession 加 cancelled 竞态守卫,快速切换会话时
   丢弃过期的 fetch 结果,防止旧会话的 model/provider 覆盖新会话
2. ChatView.tsx — 去掉 if(modelName)/if(providerId) 守卫,改为
   无条件同步 props,空值走 localStorage → default fallback 链
3. ChatListPanel.tsx + types + route.ts + db.ts — 三个创建会话入口
   (handleFolderSelect/handleNewChat/handleCreateSessionInProject)
   统一从 localStorage 读取并传递 model + provider_id,DB 层写入
4. MessageList.tsx — ScrollOnStream 增加 messageCount prop,
   消息数增加时立即 scrollToBottom,覆盖乐观插入和 assistant 落地

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 12:01:10 +08:00
7418andClaude Opus 4.6 b5e5645702 fix: CI 产物丢失 — 改为 release job 统一上传
之前各 build job 用 --publish always 并发上传到同一个 draft release,
存在竞争条件:后完成的 job overwrite 时先删后传,如果 release job
在上传未完成时执行 --draft=false,已删除的产物就丢了(DMG 丢失)。

改为:
- 各 build job 用 --publish never,产物通过 upload-artifact 上传
- release job 用 download-artifact 收集所有产物
- gh release create 一次性创建 release 并上传全部文件

同时将 .claude/rules/ 内容合回 CLAUDE.md(rules 文件与 CLAUDE.md
优先级相同,都会全量加载,拆分不减少上下文消耗)。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 23:40:31 +08:00
7418andClaude Opus 4.6 6a148f2dba chore: bump v0.21.2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:59:19 +08:00
7418andClaude Opus 4.6 ead5cfcf8b refactor: 精简 CLAUDE.md + 分层上下文管理
根 CLAUDE.md 从 80 行压缩到 15 行,详细规则拆分到按需加载位置:

- .claude/rules/development.md — 开发规则、测试要求、commit 规范
- .claude/rules/release.md — 发版流程、纪律、Release Notes 格式、构建说明
- docs/CLAUDE.md — docs 目录导航,要求检索前先读子目录 README
- src/lib/bridge/CLAUDE.md — bridge 模块关键约定(runAdapterLoop 时序、
  bridgeModeActive 互斥、offset 安全水位、adapter 扩展方式)

目的:减少每次会话的固定上下文消耗,非相关模块的文档不加载到上下文。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:59:00 +08:00
7418andClaude Opus 4.6 d9c4ad2c95 fix: 切换聊天后模型选择不保持
两个问题:
1. 用户选择模型只写 localStorage,不写 DB。切换聊天回来后从 DB
   读到空值,模型回退到默认值
2. ChatView 的 useState 只用初始值,后续 modelName prop 异步更新
   不会同步到 currentModel state

修复:
- handleProviderModelChange 选择模型时立即 PATCH 到 DB
- PATCH /api/chat/sessions/:id 新增 model + provider_id 字段支持
- ChatView 添加 useEffect 同步 modelName/providerId prop 变化

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:55:36 +08:00
7418andClaude Opus 4.6 7e45448860 fix: 滚动条不在底部时发送消息看不到 AI 输出
use-stick-to-bottom 库仅在 isAtBottom=true 时跟随新内容自动滚动。
用户滚动上去后发送消息,流式内容渲染在视口下方不可见。

新增 ScrollOnStream 组件,监听 isStreaming 从 false→true 的变化,
强制调用 scrollToBottom(),确保流式输出开始时自动跳到底部。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 22:49:35 +08:00
7418andClaude Opus 4.6 6209c865fd refactor: 整理 docs 目录结构
- docs/handover/ — 交接文档(架构、设计决策)
- docs/research/ — 调研文档(技术方案、可行性分析)
- 各目录添加 README.md 索引,要求 AI 增删文件时更新索引
- CLAUDE.md 更新文档路径引用

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 09:41:14 +08:00
7418andClaude Opus 4.6 f5ab708a8f chore: bump v0.21.1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 00:49:47 +08:00
7418andClaude Opus 4.6 65fdeb6dac fix: bridge 创建的聊天不立即显示在 ChatList
Bridge 在服务端通过 createSession() 创建会话,无法触发浏览器端的
session-created 事件,导致 ChatListPanel 不知道有新聊天。

添加 5 秒轮询刷新,查本地 SQLite 开销可忽略,bridge 新建的会话
最多 5 秒即可出现在聊天列表。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 00:49:30 +08:00
7418andClaude Opus 4.6 6ad821d3af feat: 多 IM 远程桥接系统 + Bridge 一级导航 + bug 修复
**Bridge 系统核心(src/lib/bridge/)**
- bridge-manager: 生命周期编排、adapter 事件循环、会话级并发控制
- channel-adapter: 抽象基类 + 注册式 adapter 工厂
- channel-router: IM 地址 → CodePilot session 自动映射
- conversation-engine: 服务端消费 streamClaude() SSE 流
- permission-broker: 权限请求转发到 IM 内联按钮
- delivery-layer: 出站分片、限流、重试退避、HTML 降级
- telegram-adapter: Telegram 长轮询 + offset 安全水位 + 幂等去重
- security/: 路径校验、危险输入检测、滑动窗口限流

**Telegram 通知 Bot(src/lib/telegram-bot.ts)**
- 独立通知模式,与 bridge 模式互斥
- bridgeModeActive 使用 globalThis 存储,避免 HMR 重置导致轮询冲突

**API 路由**
- /api/bridge: GET 状态查询 + POST start/stop/auto-start
- /api/bridge/channels: 活跃通道列表
- /api/bridge/settings: bridge 设置读写
- /api/settings/telegram: 凭据管理(含 allowed_users 白名单)
- /api/settings/telegram/verify: 连接验证 + chat ID 自动检测

**UI 重构:Bridge 提升为一级导航**
- NavRail 新增 Bridge 图标导航项
- /bridge 页面:BridgeLayout hash 路由,Bridge + Telegram 两个子 tab
- BridgeSection: 总开关、状态、启停、通道开关、适配器状态、默认设置
- TelegramBridgeSection: 凭据配置 + 白名单 + Setup Guide(6 步)
- Settings 移除 Telegram 和 Bridge tab,只保留 4 个核心 tab

**Bug 修复**
- bridge-manager: runAdapterLoop 在 state.running=true 之前被调用,
  while 条件同步求值为 false 导致消费者循环不启动。修复为先设 running
  再启动 loop
- telegram-bot: bridgeModeActive 改用 globalThis 防止 HMR 丢失状态
- settings/telegram route: TELEGRAM_KEYS 补充 telegram_bridge_allowed_users

**Electron**
- 窗口关闭时 bridge 活跃则最小化到系统托盘而非退出
- 启动时通过 auto-start API 恢复桥接
- preload 暴露 bridge 相关 IPC

**文档**
- docs/bridge-system.md: 完整架构文档
- docs/chat-sdk-integration-feasibility.md: Chat SDK 集成调研
- CLAUDE.md 添加 bridge 文档链接

chore: bump v0.21.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 00:18:19 +08:00
7418andClaude Opus 4.6 f7735e0b9c fix: 修复关闭最后一个分屏时 setState-during-render 报错
- removeFromSplit 中 setSplitSessions updater 内直接调用 router.replace
  导致在渲染 AppShell 时触发 Router 组件状态更新
- 改为将导航目标存入 useRef,通过 useEffect 延迟执行 router.replace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:30:20 +08:00
7418andClaude Opus 4.6 7696cbf5c5 refactor: 右侧面板 Tasks/Files 合并为单视图 + 简化 TaskList
- RightPanel: 移除 Files/Tasks 双 Tab 切换,改为单视图布局
  - 顶部标题改为 TASKS,下方直接渲染 TaskList
  - 中间用 mx-4 非贯通分隔线 + border-border/40 降低视觉强度
  - 分隔线下方增加 FILES 小标题,再接 FileTree
- TaskList: 大幅简化
  - 移除筛选 Tab(全部/进行中/已完成)
  - 移除新增任务输入框
  - 移除 TaskCard 组件引用,改为 checkbox + 标题单行展示
  - 点击切换 completed/pending 状态
  - 勾选框使用 foreground 色(黑/白)替代蓝色
  - 空状态显示"暂无任务"
- bump version to 0.20.1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:27:30 +08:00
7418andClaude Opus 4.6 b174527719 fix: ChatList 布局优化 + 多语言适配
- ChatList 字体从 12px 放大到 13px,图标同步放大,改善点击体验
- ConnectionStatus 胶囊移到顶部 header 区域,与下方按钮左对齐
- "Threads" 改为会话列表上方的小标题,充当分割作用
- ConnectionStatus 文字(Connected/Disconnected/Checking)多语言适配
- "从 Claude CLI 导入" 改为 "从 Claude Code 导入"
- 新增 i18n key: chatList.threads, connection.connected/disconnected/checking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:13:30 +08:00
7418andClaude Opus 4.6 68bb6b60bb feat: 新增服务商 + 文件上传优化 + AskQuestion 改进
新增功能:
- 新增字节火山方舟 (Volcengine Ark) 快速配置预设
  - 支持用户输入模型名称,映射到聊天输入框模型选择器
  - 模型名称存入 extra_env 的 ANTHROPIC_MODEL
- 新增阿里云百炼 (Aliyun Bailian) 快速配置预设
  - 内置 7 个模型供用户直接选择(Qwen 3.5 Plus、Kimi K2.5、GLM-5 等)
  - 无需额外配置模型名称
- 两个服务商均使用 @lobehub/icons 品牌图标 (Volcengine, Bailian)
- ProviderManager 图标识别规则新增 volces.com/dashscope 等 URL 匹配
- PresetConnectDialog 新增 model_names 字段类型,渲染模型名称输入框
- models route 动态读取 extra_env 中的 ANTHROPIC_MODEL 展示到模型选择器

修复 & 优化:
- 移除文件上传的类型限制 (ACCEPTED_FILE_TYPES) 和大小限制 (MAX_FILE_SIZE)
  现在支持所有文件类型和任意大小,由 API 端处理限制
- AskQuestion 按钮样式对齐权限确认按钮 (h-8 px-3 text-sm font-medium)
- AskQuestion 组件渲染位置从工具调用区域移到流式文本下方
  解决长文本输出后用户找不到选择组件的问题
- Anthropic Third-party API 预设的 provider_type 从 "custom" 改为 "anthropic"
  修复第三方 Anthropic 兼容 API 无法正确生效的问题

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:44:07 +08:00
7418andClaude Opus 4.6 d872f91478 chore: bump v0.19.2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 11:45:52 +08:00
7418andClaude Opus 4.6 ad34fe387b chore: 禁用 native auto-updater,改为引导用户从 GitHub 下载
macOS 上 ad-hoc 签名无法通过 ShipIt 的 kSecCSStrictValidate 校验,
导致热更新始终失败。在获取 Apple 开发者证书之前,暂时禁用 native
auto-updater,改为 browser-mode 检查 GitHub Releases API:

- electron/updater.ts: 注释掉 electron-updater 逻辑,initAutoUpdater
  和 setUpdaterWindow 改为 no-op
- electron/preload.ts: 注释掉 updater IPC bridge,前端不再暴露
  window.electronAPI.updater
- AppShell.tsx: isNativeUpdater 硬编码为 false,移除 native IPC 订阅
  和 native check/download/quitAndInstall 逻辑;保留 browser-mode
  的 GitHub API 检查(mount 时 + 每 8 小时),发现新版本时弹窗引导
  用户跳转 GitHub Release 页面下载

设置页 UpdateCard 保持不变:「检查更新」按钮通过 /api/app/updates
查询 GitHub,有新版本时显示「查看发布」按钮跳转下载。

TODO: 获取 Apple 开发者证书后,取消注释恢复 native auto-updater。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 11:45:36 +08:00
7418andClaude Opus 4.6 819f9f401a chore: bump v0.19.1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:31:30 +08:00
7418andClaude Opus 4.6 3359b3b404 fix: macOS 热更新 code signature 校验失败(改用逐组件签名)
根因:afterPack 中使用 codesign --force --deep -s - 签名,但 --deep 标志
不可靠,无法保证正确的签名顺序,导致 Electron 内嵌的 helper apps、
frameworks、.node 原生模块未被正确签名,ShipIt 在 kSecCSStrictValidate
严格校验时失败。

修复方案:
- scripts/after-pack.js: 移除 Step 4 的 --deep 签名
- scripts/after-sign.js (新增): afterSign 钩子中从内到外逐个签名:
  1. 所有 .node/.dylib/.so 原生二进制
  2. 所有 .framework 目录
  3. 所有 Electron Helper .app
  4. 主 .app bundle
  签名完成后执行 codesign --verify --strict 验证
- electron-builder.yml: 添加 afterSign 钩子引用

使用 afterSign 而非 afterPack 的原因:afterSign 在 electron-builder 自身
签名步骤之后执行(CSC_IDENTITY_AUTO_DISCOVERY=false 时为 no-op),确保
ad-hoc 签名是创建 DMG/ZIP 产物前的最后一步,不会被后续处理覆盖或失效。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:31:13 +08:00