mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 19:47:10 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f976b2a5e | ||
|
|
f3a7aece9d | ||
|
|
3a3be0c9e9 | ||
|
|
9e05739969 | ||
|
|
4bb0cbc1fc |
@@ -0,0 +1,18 @@
|
||||
## 变更说明 / What
|
||||
|
||||
<!-- 简要描述这个 PR 做了什么、为什么 / Briefly describe what this PR does and why -->
|
||||
|
||||
## 关联 Issue / Related Issue
|
||||
|
||||
<!-- 例如 / e.g. Closes #161 -->
|
||||
|
||||
## 测试方式 / How to Test
|
||||
|
||||
<!-- 描述如何验证这个变更 / Describe how to verify this change -->
|
||||
|
||||
## 检查清单 / Checklist
|
||||
|
||||
- [ ] 代码已通过 `gofmt` 和 `go vet` / Code passes `gofmt` and `go vet`
|
||||
- [ ] 前端构建无报错 / Frontend builds without errors
|
||||
- [ ] 新增 UI 文案已同步所有 locale 文件 / New UI strings synced to all locale files
|
||||
- [ ] 提交信息符合 Conventional Commits 规范 / Commit messages follow Conventional Commits
|
||||
@@ -0,0 +1,90 @@
|
||||
# 贡献指南
|
||||
|
||||
> English version: [CONTRIBUTING_EN.md](./CONTRIBUTING_EN.md)
|
||||
|
||||
感谢你考虑为 cursor-byok 做出贡献!
|
||||
|
||||
## 开发环境
|
||||
|
||||
| 依赖 | 版本要求 |
|
||||
|------|---------|
|
||||
| Go | >= 1.25 |
|
||||
| Node.js | >= 20 |
|
||||
| Yarn | 1.x (classic) |
|
||||
| [Task](https://taskfile.dev) | >= 3 |
|
||||
| [Wails v3 CLI](https://v3alpha.wails.dev) | alpha.74+ |
|
||||
|
||||
Linux 额外依赖:`libgtk-3-dev`、`libwebkit2gtk-4.1-dev`(Wails 运行时需要)。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 安装前端依赖
|
||||
cd frontend && yarn install --frozen-lockfile && cd ..
|
||||
|
||||
# 启动开发模式(热重载)
|
||||
task dev
|
||||
|
||||
# 构建当前平台分发包
|
||||
task build
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
├── main.go # 入口
|
||||
├── internal/ # Go 后端(代理、转发、客户端管理等)
|
||||
├── frontend/ # Vue 3 + Vite + Tailwind 前端
|
||||
│ ├── src/
|
||||
│ │ ├── views/ # 页面
|
||||
│ │ ├── components/ # 组件
|
||||
│ │ ├── i18n/ # 国际化(zh-CN / en-US / ja-JP / ru-RU)
|
||||
│ │ └── state/ # 全局状态
|
||||
│ └── plugins/ # Vite 插件(i18n 静态扫描等)
|
||||
├── prompt/ # 内置 Agent prompt 模板
|
||||
├── proto/ # Protobuf 定义
|
||||
├── build/ # 构建配置与平台 Taskfile
|
||||
├── scripts/ # 辅助脚本(release、metrics)
|
||||
└── Taskfile.yml # 顶层任务编排
|
||||
```
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 提交信息
|
||||
|
||||
采用 [Conventional Commits](https://www.conventionalcommits.org/zh-hans/) 风格:
|
||||
|
||||
```
|
||||
feat(proxy): 支持自定义 upstream 超时
|
||||
fix(i18n): 补全日语翻译缺失 key
|
||||
release: 0.0.42
|
||||
```
|
||||
|
||||
### 代码风格
|
||||
|
||||
- Go:遵循 `gofmt` / `go vet`,不引入额外 linter 配置。
|
||||
- 前端:Vue SFC + Composition API,Tailwind 工具类优先。
|
||||
- 新增 UI 文案必须同步更新所有 locale 文件(`frontend/src/i18n/locales/`)。
|
||||
|
||||
### 分支与 PR
|
||||
|
||||
1. 从 `main` 创建功能分支:`feat/xxx`、`fix/xxx`。
|
||||
2. 保持 PR 小而聚焦,一个 PR 解决一个问题。
|
||||
3. PR 描述中说明动机和测试方式。
|
||||
|
||||
## 构建与发布
|
||||
|
||||
```bash
|
||||
# 构建全平台(仅 macOS 主机)
|
||||
task build:all
|
||||
|
||||
# 准备发布资产
|
||||
task release:prepare
|
||||
|
||||
# 发布到 GitHub Releases
|
||||
task release:github
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
提交代码即表示你同意以 [MIT License](./LICENSE) 授权你的贡献。
|
||||
@@ -0,0 +1,90 @@
|
||||
# Contributing Guide
|
||||
|
||||
> 中文版本:[CONTRIBUTING.md](./CONTRIBUTING.md)
|
||||
|
||||
Thank you for considering contributing to cursor-byok!
|
||||
|
||||
## Prerequisites
|
||||
|
||||
| Dependency | Version |
|
||||
|------------|---------|
|
||||
| Go | >= 1.25 |
|
||||
| Node.js | >= 20 |
|
||||
| Yarn | 1.x (classic) |
|
||||
| [Task](https://taskfile.dev) | >= 3 |
|
||||
| [Wails v3 CLI](https://v3alpha.wails.dev) | alpha.74+ |
|
||||
|
||||
Additional Linux dependencies: `libgtk-3-dev`, `libwebkit2gtk-4.1-dev` (required by Wails runtime).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install frontend dependencies
|
||||
cd frontend && yarn install --frozen-lockfile && cd ..
|
||||
|
||||
# Start dev mode (hot reload)
|
||||
task dev
|
||||
|
||||
# Build for current platform
|
||||
task build
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
├── main.go # Entry point
|
||||
├── internal/ # Go backend (proxy, forwarding, client management)
|
||||
├── frontend/ # Vue 3 + Vite + Tailwind frontend
|
||||
│ ├── src/
|
||||
│ │ ├── views/ # Pages
|
||||
│ │ ├── components/ # Components
|
||||
│ │ ├── i18n/ # Internationalization (zh-CN / en-US / ja-JP / ru-RU)
|
||||
│ │ └── state/ # Global state
|
||||
│ └── plugins/ # Vite plugins (i18n static scanner, etc.)
|
||||
├── prompt/ # Built-in agent prompt templates
|
||||
├── proto/ # Protobuf definitions
|
||||
├── build/ # Build configs & platform Taskfiles
|
||||
├── scripts/ # Helper scripts (release, metrics)
|
||||
└── Taskfile.yml # Top-level task orchestration
|
||||
```
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Commit Messages
|
||||
|
||||
Follow [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
feat(proxy): support custom upstream timeout
|
||||
fix(i18n): add missing Japanese translation keys
|
||||
release: 0.0.42
|
||||
```
|
||||
|
||||
### Code Style
|
||||
|
||||
- Go: follow `gofmt` / `go vet`; no additional linter config.
|
||||
- Frontend: Vue SFC + Composition API, Tailwind utility-first.
|
||||
- New UI strings must be added to ALL locale files (`frontend/src/i18n/locales/`).
|
||||
|
||||
### Branching & PRs
|
||||
|
||||
1. Create feature branches from `main`: `feat/xxx`, `fix/xxx`.
|
||||
2. Keep PRs small and focused — one problem per PR.
|
||||
3. Describe motivation and how to test in the PR description.
|
||||
|
||||
## Build & Release
|
||||
|
||||
```bash
|
||||
# Build all platforms (macOS host only)
|
||||
task build:all
|
||||
|
||||
# Prepare release assets
|
||||
task release:prepare
|
||||
|
||||
# Publish to GitHub Releases
|
||||
task release:github
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under the [MIT License](./LICENSE).
|
||||
+1
-1
@@ -8,7 +8,7 @@ info:
|
||||
description: "Cursor助手"
|
||||
copyright: "© 2026, Cursor助手"
|
||||
comments: "Cursor助手"
|
||||
version: "0.0.41"
|
||||
version: "0.0.42"
|
||||
|
||||
dev_mode:
|
||||
root_path: .
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.41</string>
|
||||
<string>0.0.42</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.41</string>
|
||||
<string>0.0.42</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0.0</string>
|
||||
<key>LSUIElement</key>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.0.41</string>
|
||||
<string>0.0.42</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>0.0.41</string>
|
||||
<string>0.0.42</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0.0</string>
|
||||
<key>LSUIElement</key>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
name: "Cursor助手"
|
||||
arch: ${GOARCH}
|
||||
platform: "linux"
|
||||
version: "0.0.41"
|
||||
version: "0.0.42"
|
||||
section: "default"
|
||||
priority: "extra"
|
||||
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"fixed": {
|
||||
"file_version": "0.0.41"
|
||||
"file_version": "0.0.42"
|
||||
},
|
||||
"info": {
|
||||
"0000": {
|
||||
"ProductVersion": "0.0.41",
|
||||
"ProductVersion": "0.0.42",
|
||||
"CompanyName": "Cursor助手",
|
||||
"FileDescription": "Cursor助手",
|
||||
"LegalCopyright": "© 2026, Cursor助手",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
!define INFO_PRODUCTNAME "Cursor助手"
|
||||
!endif
|
||||
!ifndef INFO_PRODUCTVERSION
|
||||
!define INFO_PRODUCTVERSION "0.0.41"
|
||||
!define INFO_PRODUCTVERSION "0.0.42"
|
||||
!endif
|
||||
!ifndef INFO_COPYRIGHT
|
||||
!define INFO_COPYRIGHT "© 2026, Cursor助手"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<assemblyIdentity type="win32" name="com.cursor.wuxianxubei" version="0.0.41" processorArchitecture="*"/>
|
||||
<assemblyIdentity type="win32" name="com.cursor.wuxianxubei" version="0.0.42" processorArchitecture="*"/>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||
|
||||
@@ -2,6 +2,7 @@ package forwarder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -37,8 +38,9 @@ func (tracker *appendSequenceTracker) Acquire(ctx context.Context, requestID str
|
||||
if tracker == nil || strings.TrimSpace(requestID) == "" || appendSeq <= 0 {
|
||||
return appendSequenceTicket{}, false, nil
|
||||
}
|
||||
state := tracker.state(strings.TrimSpace(requestID))
|
||||
stale, err := state.acquire(ctx, appendSeq)
|
||||
requestID = strings.TrimSpace(requestID)
|
||||
state := tracker.state(requestID)
|
||||
stale, err := state.acquire(ctx, requestID, appendSeq)
|
||||
if err != nil || stale {
|
||||
return appendSequenceTicket{}, stale, err
|
||||
}
|
||||
@@ -72,7 +74,7 @@ func (tracker *appendSequenceTracker) state(requestID string) *appendSequenceSta
|
||||
return state
|
||||
}
|
||||
|
||||
func (state *appendSequenceState) acquire(ctx context.Context, appendSeq int64) (bool, error) {
|
||||
func (state *appendSequenceState) acquire(ctx context.Context, requestID string, appendSeq int64) (bool, error) {
|
||||
for {
|
||||
state.mu.Lock()
|
||||
now := time.Now().UTC()
|
||||
@@ -83,6 +85,29 @@ func (state *appendSequenceState) acquire(ctx context.Context, appendSeq int64)
|
||||
state.ready = make(chan struct{})
|
||||
}
|
||||
state.updatedAt = now
|
||||
|
||||
// Cursor may reuse the same request_id for a later turn and restart
|
||||
// append_seqno from 1. Accept that as a sequence restart when idle so
|
||||
// tool results are not discarded as stale forever.
|
||||
if appendSeq == 1 && state.next > 1 {
|
||||
if state.processing {
|
||||
ready := state.ready
|
||||
state.mu.Unlock()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
case <-ready:
|
||||
}
|
||||
continue
|
||||
}
|
||||
prevNext := state.next
|
||||
state.next = 1
|
||||
state.processing = true
|
||||
state.mu.Unlock()
|
||||
log.Printf("forwarder reset append sequence request_id=%s previous_next=%d append_seqno=1", requestID, prevNext)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case appendSeq < state.next:
|
||||
state.mu.Unlock()
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package forwarder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAppendSequenceTrackerOrderedAcquire(t *testing.T) {
|
||||
tracker := newAppendSequenceTracker()
|
||||
ctx := context.Background()
|
||||
|
||||
ticket1, stale, err := tracker.Acquire(ctx, "req-1", 1)
|
||||
if err != nil || stale {
|
||||
t.Fatalf("seq 1: stale=%v err=%v", stale, err)
|
||||
}
|
||||
ticket1.Release()
|
||||
|
||||
ticket2, stale, err := tracker.Acquire(ctx, "req-1", 2)
|
||||
if err != nil || stale {
|
||||
t.Fatalf("seq 2: stale=%v err=%v", stale, err)
|
||||
}
|
||||
ticket2.Release()
|
||||
}
|
||||
|
||||
func TestAppendSequenceTrackerRestartFromOne(t *testing.T) {
|
||||
tracker := newAppendSequenceTracker()
|
||||
ctx := context.Background()
|
||||
|
||||
for seq := int64(1); seq <= 5; seq++ {
|
||||
ticket, stale, err := tracker.Acquire(ctx, "req-restart", seq)
|
||||
if err != nil || stale {
|
||||
t.Fatalf("seq %d: stale=%v err=%v", seq, stale, err)
|
||||
}
|
||||
ticket.Release()
|
||||
}
|
||||
|
||||
// Same request_id, Cursor restarts append_seqno from 1 for a new turn.
|
||||
ticket, stale, err := tracker.Acquire(ctx, "req-restart", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("restart seq 1 err=%v", err)
|
||||
}
|
||||
if stale {
|
||||
t.Fatal("expected sequence restart on append_seqno=1 after progress")
|
||||
}
|
||||
ticket.Release()
|
||||
|
||||
ticket2, stale, err := tracker.Acquire(ctx, "req-restart", 2)
|
||||
if err != nil || stale {
|
||||
t.Fatalf("post-restart seq 2: stale=%v err=%v", stale, err)
|
||||
}
|
||||
ticket2.Release()
|
||||
}
|
||||
|
||||
func TestAppendSequenceTrackerMidGapStillStale(t *testing.T) {
|
||||
tracker := newAppendSequenceTracker()
|
||||
ctx := context.Background()
|
||||
|
||||
for seq := int64(1); seq <= 3; seq++ {
|
||||
ticket, stale, err := tracker.Acquire(ctx, "req-gap", seq)
|
||||
if err != nil || stale {
|
||||
t.Fatalf("seq %d: stale=%v err=%v", seq, stale, err)
|
||||
}
|
||||
ticket.Release()
|
||||
}
|
||||
|
||||
_, stale, err := tracker.Acquire(ctx, "req-gap", 2)
|
||||
if err != nil {
|
||||
t.Fatalf("gap retransmit err=%v", err)
|
||||
}
|
||||
if !stale {
|
||||
t.Fatal("expected mid-sequence retransmit below next (but not 1) to stay stale")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendSequenceTrackerWaitsForInOrder(t *testing.T) {
|
||||
tracker := newAppendSequenceTracker()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
ticket1, stale, err := tracker.Acquire(ctx, "req-wait", 1)
|
||||
if err != nil || stale {
|
||||
t.Fatalf("seq 1: stale=%v err=%v", stale, err)
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
ticket2, stale, err := tracker.Acquire(ctx, "req-wait", 2)
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
if stale {
|
||||
done <- errors.New("seq 2 unexpectedly stale")
|
||||
return
|
||||
}
|
||||
ticket2.Release()
|
||||
done <- nil
|
||||
}()
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
ticket1.Release()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
t.Fatalf("waiting seq 2 failed: %v", err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("timed out waiting for ordered seq 2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendSequenceTrackerRestartWaitsUntilIdle(t *testing.T) {
|
||||
tracker := newAppendSequenceTracker()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Advance to next=3.
|
||||
for seq := int64(1); seq <= 2; seq++ {
|
||||
ticket, stale, err := tracker.Acquire(ctx, "req-idle", seq)
|
||||
if err != nil || stale {
|
||||
t.Fatalf("seq %d: stale=%v err=%v", seq, stale, err)
|
||||
}
|
||||
ticket.Release()
|
||||
}
|
||||
|
||||
// Hold seq 3 so a concurrent restart must wait.
|
||||
hold, stale, err := tracker.Acquire(ctx, "req-idle", 3)
|
||||
if err != nil || stale {
|
||||
t.Fatalf("seq 3 hold: stale=%v err=%v", stale, err)
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
ticket, stale, err := tracker.Acquire(ctx, "req-idle", 1)
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
if stale {
|
||||
done <- errors.New("restart seq 1 unexpectedly stale")
|
||||
return
|
||||
}
|
||||
ticket.Release()
|
||||
done <- nil
|
||||
}()
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
hold.Release()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
t.Fatalf("restart while busy failed: %v", err)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("timed out waiting for restart after idle")
|
||||
}
|
||||
|
||||
ticket2, stale, err := tracker.Acquire(ctx, "req-idle", 2)
|
||||
if err != nil || stale {
|
||||
t.Fatalf("post-restart seq 2: stale=%v err=%v", stale, err)
|
||||
}
|
||||
ticket2.Release()
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
-------0.0.42------
|
||||
- 修复grep或者read长时间阻塞问题 @liorxuan
|
||||
-------0.0.41------
|
||||
- 修复内存泄漏问题,该可能导致内存异常占用
|
||||
- 支持俄语增加翻译范围
|
||||
- 修复qwen-3.8-max中断问题(mimo也应该属于同一类问题)
|
||||
|
||||
Reference in New Issue
Block a user