Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
800967e
feat(gateway): 落地 RFC#420 双产物发布与第三方接入契约
pionxe Apr 23, 2026
fa8d483
fix(gateway): 修复回滚重拣后冲突代码与文档生成脚本构建冲突
pionxe Apr 23, 2026
083d76a
fix(cli): restore UTF-8 Chinese comments in gateway commands
xgopilot Apr 23, 2026
7885f7b
chore: resolve merge conflicts with main
xgopilot Apr 23, 2026
06aa0f7
fix(makefile): align gateway docs check with generated artifact
xgopilot Apr 23, 2026
222f2df
fix(conflict): resolve Makefile merge conflict with main
xgopilot Apr 23, 2026
d5633b7
fix(conflict): align gateway doc generator path with origin main
xgopilot Apr 23, 2026
487b323
fix(conflict): align docs reference api file with origin main
xgopilot Apr 23, 2026
891823b
fix(docs): regenerate gateway rpc reference examples
xgopilot Apr 23, 2026
a519771
Merge branch 'main' into release/v2.0-split-build
pionxe Apr 23, 2026
f461648
test(gateway): improve patch coverage for launcher entry paths
xgopilot Apr 23, 2026
387ce5c
test(gateway): improve dispatcher path coverage with branch tests
xgopilot Apr 23, 2026
0dd60a9
fix(gateway): align launcher fallback contract and dispatch launch args
xgopilot Apr 24, 2026
fa0541b
Merge pull request #8 from pionxe/fork-pr-423-1776999056
pionxe Apr 24, 2026
9cf7099
fix(gateway): resolve remaining review risks and tighten checks
xgopilot Apr 24, 2026
5413113
Merge pull request #9 from pionxe/fork-pr-423-1776999056
pionxe Apr 24, 2026
57c314c
fix(conflict): resolve README merge conflict with main
xgopilot Apr 24, 2026
f32b04b
fix(conflict): align README with main and retain RFC#420 notes
xgopilot Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,92 @@ jobs:
- name: Build
run: go build ./...

- name: Gateway-only smoke
shell: bash
run: |
set -euo pipefail

socket_path="/tmp/neocode-gateway-${RANDOM}.sock"
http_port="$((18080 + RANDOM % 1000))"
http_addr="127.0.0.1:${http_port}"
gateway_bin="/tmp/neocode-gateway"
gateway_log="/tmp/neocode-gateway.log"

go build -o "${gateway_bin}" ./cmd/neocode-gateway
"${gateway_bin}" --listen "${socket_path}" --http-listen "${http_addr}" --log-level info >"${gateway_log}" 2>&1 &
gateway_pid=$!

cleanup() {
if kill -0 "${gateway_pid}" >/dev/null 2>&1; then
kill "${gateway_pid}" || true
wait "${gateway_pid}" || true
fi
rm -f "${socket_path}" "${gateway_bin}" /tmp/gateway-healthz.json /tmp/gateway-rpc.json
}
trap cleanup EXIT

for _ in $(seq 1 60); do
if curl -fsS "http://${http_addr}/healthz" > /tmp/gateway-healthz.json; then
break
fi
sleep 0.2
done
test -s /tmp/gateway-healthz.json

rpc_status="$(curl -sS -o /tmp/gateway-rpc.json -w "%{http_code}" \
-X POST "http://${http_addr}/rpc" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"smoke-1","method":"gateway.ping","params":{}}')"
if [[ "${rpc_status}" != "401" ]]; then
echo "unexpected /rpc status: ${rpc_status}" >&2
cat /tmp/gateway-rpc.json >&2
cat "${gateway_log}" >&2 || true
exit 1
fi
grep -q '"gateway_code":"unauthorized"' /tmp/gateway-rpc.json

- name: Test with coverage
run: go test ./... -covermode=atomic -coverprofile=coverage.out

- name: Install script dry-run regression (bash)
shell: bash
env:
NEOCODE_INSTALL_LATEST_TAG: v0.0.0-test
run: |
set -euo pipefail

full_output="$(bash ./scripts/install.sh --flavor full --dry-run)"
gateway_output="$(bash ./scripts/install.sh --flavor gateway --dry-run)"

echo "${full_output}" | grep -Eq '^asset=neocode_(Linux|Darwin)_(x86_64|arm64)\.tar\.gz$'
echo "${gateway_output}" | grep -Eq '^asset=neocode-gateway_(Linux|Darwin)_(x86_64|arm64)\.tar\.gz$'
echo "${full_output}" | grep -Eq '^download_url=https://github.com/1024XEngineer/neo-code/releases/download/.+/neocode_(Linux|Darwin)_(x86_64|arm64)\.tar\.gz$'
echo "${gateway_output}" | grep -Eq '^download_url=https://github.com/1024XEngineer/neo-code/releases/download/.+/neocode-gateway_(Linux|Darwin)_(x86_64|arm64)\.tar\.gz$'
echo "${full_output}" | grep -Eq '^checksum_url=https://github.com/1024XEngineer/neo-code/releases/download/.+/checksums\.txt$'
echo "${gateway_output}" | grep -Eq '^checksum_url=https://github.com/1024XEngineer/neo-code/releases/download/.+/checksums\.txt$'

- name: Install script dry-run regression (PowerShell)
shell: pwsh
env:
NEOCODE_INSTALL_LATEST_TAG: v0.0.0-test
run: |
$fullLines = & ./scripts/install.ps1 -Flavor full -DryRun
$gatewayLines = & ./scripts/install.ps1 -Flavor gateway -DryRun

$fullAsset = ($fullLines | Where-Object { $_ -like 'asset=*' } | Select-Object -First 1)
$gatewayAsset = ($gatewayLines | Where-Object { $_ -like 'asset=*' } | Select-Object -First 1)
$fullDownload = ($fullLines | Where-Object { $_ -like 'download_url=*' } | Select-Object -First 1)
$gatewayDownload = ($gatewayLines | Where-Object { $_ -like 'download_url=*' } | Select-Object -First 1)
$fullChecksum = ($fullLines | Where-Object { $_ -like 'checksum_url=*' } | Select-Object -First 1)
$gatewayChecksum = ($gatewayLines | Where-Object { $_ -like 'checksum_url=*' } | Select-Object -First 1)

if ($fullAsset -notmatch '^asset=neocode_Windows_(x86_64|arm64)\.zip$') { throw "Unexpected full asset line: $fullAsset" }
if ($gatewayAsset -notmatch '^asset=neocode-gateway_Windows_(x86_64|arm64)\.zip$') { throw "Unexpected gateway asset line: $gatewayAsset" }
if ($fullDownload -notmatch '^download_url=https://github.com/1024XEngineer/neo-code/releases/download/.+/neocode_Windows_(x86_64|arm64)\.zip$') { throw "Unexpected full download URL: $fullDownload" }
if ($gatewayDownload -notmatch '^download_url=https://github.com/1024XEngineer/neo-code/releases/download/.+/neocode-gateway_Windows_(x86_64|arm64)\.zip$') { throw "Unexpected gateway download URL: $gatewayDownload" }
if ($fullChecksum -notmatch '^checksum_url=https://github.com/1024XEngineer/neo-code/releases/download/.+/checksums\.txt$') { throw "Unexpected full checksum URL: $fullChecksum" }
if ($gatewayChecksum -notmatch '^checksum_url=https://github.com/1024XEngineer/neo-code/releases/download/.+/checksums\.txt$') { throw "Unexpected gateway checksum URL: $gatewayChecksum" }

- name: Upload coverage to Codecov
continue-on-error: true
uses: codecov/codecov-action@v5
Expand Down
53 changes: 41 additions & 12 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# .goreleaser.yaml
project_name: neocode
version: 2 # 必须声明为 v2 语法
version: 2

before:
hooks:
# 每次构建前清理模块并下载依赖
- go mod tidy
- go mod download

builds:
- env:
- CGO_ENABLED=0 # 禁用 CGO,确保生成纯静态链接的二进制文件
- id: neocode
env:
- CGO_ENABLED=0
ldflags:
- -s -w -X 'neo-code/internal/version.Version={{.Version}}'
goos:
Expand All @@ -20,27 +19,57 @@ builds:
goarch:
- amd64
- arm64
# 指定 main.go 的路径(根据 NeoCode 的实际目录调整)
main: ./cmd/neocode/main.go
# 编译出的二进制文件名
main: ./cmd/neocode/main.go
binary: neocode

- id: neocode-gateway
env:
- CGO_ENABLED=0
ldflags:
- -s -w -X 'neo-code/internal/version.Version={{.Version}}'
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
main: ./cmd/neocode-gateway/main.go
binary: neocode-gateway

archives:
- format: tar.gz
# 为 Windows 提供单独的 zip 格式
- id: neocode
ids:
- neocode
format: tar.gz
format_overrides:
- goos: windows
format: zip
name_template: >-
neocode_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}

- id: neocode-gateway
ids:
- neocode-gateway
format: tar.gz
format_overrides:
- goos: windows
format: zip
name_template: >-
{{ .ProjectName }}_
neocode-gateway_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}

checksum:
name_template: 'checksums.txt'
name_template: checksums.txt

changelog:
sort: asc
Expand Down
9 changes: 6 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
.PHONY: install-skills docs-gateway docs-gateway-check

GATEWAY_DOCS_GENERATOR := go run -tags gatewaydocgen ./scripts/generate_gateway_rpc_examples.go

install-skills:
@./scripts/install_skills.sh

docs-gateway:
@go run ./scripts/generate_gateway_rpc_examples
@$(GATEWAY_DOCS_GENERATOR)

docs-gateway-check:
@go run ./scripts/generate_gateway_rpc_examples
@git diff --exit-code -- docs/reference/gateway-rpc-api.md
@$(GATEWAY_DOCS_GENERATOR)
@go run ./scripts/check_gateway_docs
@git diff --exit-code -- docs/generated/gateway-rpc-examples.json
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ NeoCode 是一个在终端中运行的 AI 编码助手,采用 ReAct(Reason-A
## 有什么能力?
- 终端原生 TUI 交互体验(Bubble Tea)
- Agent 可调用内置工具完成文件与命令相关任务
- 支持 Provider/Model 切换(内建 `openai`、`gemini`、`openll`、`qiniu`)
- 支持 Provider/Model 切换(内建 `openai`、`gemini`、`openll`、`qiniu`、`modelscope`
- 支持上下文压缩(`/compact`),帮助长会话保持可用
- 支持工作区隔离(`--workdir`、`/cwd`)
- 会话持久化与恢复,降低重复沟通成本
Expand All @@ -35,7 +35,7 @@ NeoCode 是一个在终端中运行的 AI 编码助手,采用 ReAct(Reason-A

### 1) 环境要求
- Go `1.25+`
- 可用的 API Key(如 OpenAI、Gemini、OpenLL、Qiniu)
- 可用的 API Key(如 OpenAI、Gemini、OpenLL、Qiniu、ModelScope

### 2) 一键安装
macOS / Linux:
Expand Down Expand Up @@ -97,6 +97,7 @@ export OPENAI_API_KEY="your_key_here"
export GEMINI_API_KEY="your_key_here"
export AI_API_KEY="your_key_here"
export QINIU_API_KEY="your_key_here"
export MODELSCOPE_API_KEY="your_key_here"
```

Windows PowerShell:
Expand All @@ -105,6 +106,7 @@ $env:OPENAI_API_KEY = "your_key_here"
$env:GEMINI_API_KEY = "your_key_here"
$env:AI_API_KEY = "your_key_here"
$env:QINIU_API_KEY = "your_key_here"
$env:MODELSCOPE_API_KEY = "your_key_here"
```

按工作区启动(仅当前进程生效):
Expand Down Expand Up @@ -155,7 +157,8 @@ Gateway 转发与自动拉起说明:

## 内部结构补充

- `internal/context`:负责主会话 system prompt 的 section 组装、动态上下文注入与消息裁剪。
- `internal/context`:负责消费仓库/运行时事实并组装主会话 system prompt、动态上下文注入与消息裁剪。
- `internal/repository`:负责仓库级事实发现与裁剪,统一提供 repo summary、changed-files context 与 targeted retrieval。
- `internal/runtime`:负责 ReAct 主循环、tool 调用编排、compact 触发与 reminder 注入时机。
- `internal/subagent`:负责子代理角色策略、执行约束与输出契约。
- `internal/promptasset`:负责受版本管理的静态 prompt 模板资产,使用 `go:embed` 编译进程序,供 `context`、`runtime`、`subagent` 读取。
Expand All @@ -167,9 +170,11 @@ Gateway 转发与自动拉起说明:
- [Runtime/Provider 事件流](docs/runtime-provider-event-flow.md)
- [Session 持久化设计](docs/session-persistence-design.md)
- [Context Compact 说明](docs/context-compact.md)
- [Repository 模块设计](docs/repository-design.md)
- [Tools 与 TUI 集成](docs/tools-and-tui-integration.md)
- [Skills 设计与使用](docs/skills-system-design.md)
- [MCP 配置指南](docs/guides/mcp-configuration.md)
- [ModelScope 半引导配置](docs/guides/modelscope-provider-setup.md)
- [更新与升级](docs/guides/update.md)

## 如何参与
Expand Down Expand Up @@ -269,6 +274,20 @@ Skill 内部调用脚本 `scripts/create_issue.sh` 创建 issue。你也可以
- `wake.openUrl`:处理 `neocode://` 唤醒请求
- `gateway.event`:网关推送通知事件(notification)

## 双产物与启动兼容(RFC#420)

- 发布产物:
- `neocode`(完整客户端,含 `gateway` 子命令)
- `neocode-gateway`(Gateway-Only 入口)
- `url-dispatch` 网关不可达时的拉起优先级固定为:
- `NEOCODE_GATEWAY_BIN`
- `PATH` 中 `neocode-gateway`
- `neocode gateway`
- 第三方接入与协议文档见:
- [`docs/guides/gateway-integration-guide.md`](docs/guides/gateway-integration-guide.md)
- [`docs/gateway-rpc-api.md`](docs/gateway-rpc-api.md)
- [`docs/gateway-error-catalog.md`](docs/gateway-error-catalog.md)

## License

MIT
16 changes: 16 additions & 0 deletions cmd/neocode-gateway/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"context"
"fmt"
"os"

"neo-code/internal/cli"
)

func main() {
if err := cli.ExecuteGatewayServer(context.Background(), os.Args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "neocode-gateway: %v\n", err)
os.Exit(1)
}
}
48 changes: 48 additions & 0 deletions cmd/neocode-gateway/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"bytes"
"errors"
"os"
"os/exec"
"strings"
"testing"
)

func TestMainHelpPathDoesNotExit(t *testing.T) {
originalArgs := os.Args
defer func() {
os.Args = originalArgs
}()

os.Args = []string{"neocode-gateway", "--help"}
main()
}

func TestMainReturnsExitCodeOneOnCommandError(t *testing.T) {
if os.Getenv("NEOCODE_GATEWAY_MAIN_HELPER") == "1" {
os.Args = []string{"neocode-gateway", "--log-level", "trace"}
main()
return
}

command := exec.Command(os.Args[0], "-test.run=TestMainReturnsExitCodeOneOnCommandError")
command.Env = append(os.Environ(), "NEOCODE_GATEWAY_MAIN_HELPER=1")
var stderr bytes.Buffer
command.Stderr = &stderr

err := command.Run()
if err == nil {
t.Fatal("expected subprocess to exit with non-zero status")
}
var exitErr *exec.ExitError
if !errors.As(err, &exitErr) {
t.Fatalf("error type = %T, want *exec.ExitError", err)
}
if exitErr.ExitCode() != 1 {
t.Fatalf("exit code = %d, want %d", exitErr.ExitCode(), 1)
}
if !strings.Contains(stderr.String(), "neocode-gateway:") {
t.Fatalf("stderr = %q, want contains %q", stderr.String(), "neocode-gateway:")
}
}
53 changes: 53 additions & 0 deletions docs/gateway-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Gateway 兼容性与弃用策略

本文定义 Gateway 对外契约的版本兼容规则,适用于方法、字段、错误码与发布资产。

## 1. 兼容性分层

1. Stable(稳定层):默认向后兼容,不做破坏性改动。
2. Experimental(实验层):允许演进,但必须有显式标注与迁移说明。

当前分层:

1. Stable Core:`gateway.authenticate`、`gateway.ping`、`gateway.bindStream`、`gateway.run`、`gateway.compact`、`gateway.cancel`、`gateway.listSessions`、`gateway.loadSession`、`gateway.resolvePermission`、`gateway.event`
2. Experimental:`wake.openUrl`

## 2. 字段弃用生命周期(必须遵守)

### 2.1 标准流程

1. **v1.2 标记 Deprecated**
字段继续可用;文档、日志、响应元信息中标记 `deprecated: true`(或等效说明)。
2. **v1.3 兼容保留期**
新客户端 SHOULD 停止依赖该字段;服务端保持兼容读取/写出策略。
3. **v1.4 正式移除**
字段从请求/响应契约中删除;若客户端仍发送,返回可诊断错误(通常 `invalid_frame` 或 `unsupported_action`,视场景而定)。

### 2.2 示例

若字段 `params.legacy_x` 计划移除:

1. v1.2:文档标记 Deprecated,并在 release notes 给迁移路径。
2. v1.3:继续接受 `legacy_x`,但服务端优先使用新字段。
3. v1.4:拒绝 `legacy_x`,返回明确错误与替代字段提示。

## 3. 破坏性变更门禁

以下变更 MUST 走 RFC 流程并通过灰度窗口:

1. 删除 Stable 方法。
2. 修改 Stable 方法必填字段语义。
3. 修改稳定 `gateway_code` 含义。
4. 改变资产命名规则(下载 URL / checksum 路径)。

## 4. 双产物发布兼容承诺

1. `neocode`:保留现有主入口行为。
2. `neocode-gateway`:仅承载网关服务语义。
3. 同参条件下,`neocode gateway` 与 `neocode-gateway` MUST 行为等价(参数归一化、错误语义、关键日志字段)。

## 5. 回滚原则

1. 升级失败时 SHOULD 先回滚二进制版本,再恢复配置。
2. 回滚版本 MUST 与当前稳定协议兼容(至少同主版本)。
3. 回滚步骤必须在发布说明中提供可执行命令与验证点(`/healthz`、`/rpc` 最小请求)。
Loading
Loading