Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
2573fd1
feat(): 实现 SubAgent WorkerRuntime、角色策略与 Runtime 生命周期接入
Cai-Tang-www Apr 14, 2026
9384e80
fix(subagent): resolve review conflicts and strengthen coverage
xgopilot Apr 14, 2026
cc74bd4
fix(runtime): resolve merge conflicts with main event/runtime changes
xgopilot Apr 14, 2026
730a61b
fix(runtime): align runtime core with main to resolve merge conflicts
xgopilot Apr 14, 2026
a5bf778
refactor(runtime): decouple subagent factory storage from service struct
xgopilot Apr 14, 2026
d7c1aac
feat(runtime):增加可修改死循环次数以及自我纠正提示
phantom5099 Apr 14, 2026
4098da8
fix(runtime,config): resolve PR review items and simplify streak hand…
xgopilot Apr 14, 2026
e43f70c
fix(subagent): resolve review issues for factory lifecycle and defaul…
xgopilot Apr 15, 2026
319b303
refactor(runtime,config): address review feedback on streak handling
xgopilot Apr 15, 2026
564e393
Merge pull request #15 from phantom5099/fork-pr-298-1776187797
phantom5099 Apr 15, 2026
062846d
fix(runtime): resolve main merge conflicts for subagent runtime
xgopilot Apr 15, 2026
631d8ab
refactor(runtime): align state model with main-compatible event envelope
xgopilot Apr 15, 2026
1afecba
test(runtime): align helper tests with main to remove merge conflicts
xgopilot Apr 15, 2026
727ed3f
fix(subagent): address review gaps in progress envelope and capabilit…
xgopilot Apr 15, 2026
4b8828b
Merge pull request #298 from phantom5099/main
phantom5099 Apr 15, 2026
5202821
Merge branch 'main' into feat(subagent)
phantom5099 Apr 15, 2026
9a7f69e
feat(gateway): [EPIC-GW-03] 本地控制面契约硬化与 JSON-RPC 协议切换
pionxe Apr 15, 2026
9ee41ed
fix:修复 JSON-RPC ID 校验不严的问题、Windows 路径注入防御增强和优化 Result 解码的性能,移除多余的内存分配
pionxe Apr 15, 2026
5c44480
Merge pull request #295 from Cai-Tang-www/feat(subagent)
phantom5099 Apr 15, 2026
97da88f
feat(runtime):增加工具调用重复循环熔断机制
phantom5099 Apr 15, 2026
f8bc410
fix(runtime): 修复重复循环配置校验回归并补齐熔断回归测试
xgopilot Apr 15, 2026
bd9cca6
fix(gateway): resolve remaining JSON-RPC hardening review gaps
xgopilot Apr 15, 2026
9744a2c
fix(config/runtime): 统一 MaxRepeatCycleStreak 三处守卫语义一致性并补齐测试
xgopilot Apr 15, 2026
07fa747
Merge pull request #16 from phantom5099/fork-pr-303-1776228657
phantom5099 Apr 15, 2026
dd4be72
test(runtime): 提升重复调用熔断相关覆盖率
xgopilot Apr 15, 2026
f60a3a6
Merge pull request #305 from Yumiue/main
phantom5099 Apr 15, 2026
b073953
fix(context): 修复 metadata-only 工具结果语义丢失
wynxing Apr 15, 2026
ad20555
fix(runtime): avoid duplicate metadata sanitize in tool message norma…
xgopilot Apr 15, 2026
cbd9160
Merge pull request #307 from Yumiue/codex/fix-metadata-tool-result-pr…
phantom5099 Apr 15, 2026
d3bc6fc
fix(tui):修复终端报错会导致命令行和输入区异常的问题
creatang Apr 15, 2026
36f4b6e
feat(tui):增加多模态(图片读入)
creatang Apr 15, 2026
8c6acdd
feat(tui)增加tui的slash功能(/session 会话切换)
creatang Apr 15, 2026
56a3296
fix(ci): avoid Linux clipboard build dependency
creatang Apr 15, 2026
372ab70
fix(tui): resolve multimodal review issues and add tests
xgopilot Apr 15, 2026
d5e5b7c
test(tui): improve multimodal and session-switch coverage
xgopilot Apr 15, 2026
5cc4f66
Merge pull request #12 from 1024XEngineer/codex/issue-266-header-rend…
creatang Apr 15, 2026
0dab2c2
Merge remote-tracking branch 'refs/remotes/origin/main'
phantom5099 Apr 15, 2026
7a093a6
fix(gateway): harden jsonrpc invalid-id and frame error mapping
xgopilot Apr 15, 2026
696e72f
Merge pull request #302 from pionxe/feat/gateway-epic-gw-03-jsonrpc-h…
phantom5099 Apr 15, 2026
c8354c5
Merge pull request #308 from creatang/codex/issue-266-header-render-fix
phantom5099 Apr 15, 2026
b622286
fix(context): 修复自动 compact 阈值回退并补齐模型元数据校验
wynxing Apr 15, 2026
3055d8f
fix(runtime): guard non-positive resolved auto-compact threshold
xgopilot Apr 15, 2026
afce7e3
fix(runtime): align repeat-cycle breaker semantics and tests
xgopilot Apr 15, 2026
de9db53
Merge pull request #17 from phantom5099/fork-pr-303-1776263436
phantom5099 Apr 15, 2026
047bb8b
fix(runtime): cache auto-compact threshold within run
xgopilot Apr 15, 2026
3125d08
Merge pull request #303 from phantom5099/main
phantom5099 Apr 15, 2026
0d653a3
Merge branch 'main' into codex/issue-294-auto-compact-threshold
phantom5099 Apr 15, 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
11 changes: 11 additions & 0 deletions docs/config-management-detail-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,14 @@ custom provider 来自:
- 不在 `config` 中堆兼容旧字段的逻辑
- 不把选择修正混进快照校验
- 不把 provider/catalog/runtime 语义倒灌回 `config`

## custom provider `models` 校验约束

`~/.neocode/providers/<provider-name>/provider.yaml` 中允许通过 `models` 补齐模型元数据,用于 catalog/discovery 无法提供完整 `ContextWindow` 或 `MaxOutputTokens` 的场景。

该能力的约束是:

- `models[].id` 必须非空。
- `models[].context_window` 和 `models[].max_output_tokens` 如果显式提供,必须大于 `0`。
- 重复的模型 `id` 会在加载 custom provider 时直接失败,不保留 silently drop 的宽松行为。
- 这些元数据不会写回 `config.yaml`,只在 custom provider 文件中声明,并通过现有 catalog 合并链路参与运行时解析。
21 changes: 19 additions & 2 deletions docs/context-compact.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Context Compact

## Auto Compact Failure Fallback

- 当 `context.auto_compact.input_token_threshold <= 0` 时,系统会尝试基于当前模型的 `ContextWindow` 自动推导阈值。
- 若当前 provider 选择无效、catalog snapshot 查询失败,或模型窗口元数据缺失,系统会直接回退到 `fallback_input_token_threshold`。
- 自动推导失败不会静默关闭 auto compact;runtime 仍会拿到一个可用的保底阈值。

本文档说明 NeoCode 中 context compact 的配置、执行链路和摘要约定。

## 概览
Expand All @@ -23,7 +29,9 @@ context:
micro_compact_disabled: false
auto_compact:
enabled: false
input_token_threshold: 100000
input_token_threshold: 0
reserve_tokens: 13000
fallback_input_token_threshold: 100000
```

- `manual_strategy`
Expand All @@ -39,7 +47,7 @@ context:
- `auto_compact.enabled`
控制是否启用基于 token 阈值的自动压缩;默认关闭。
- `auto_compact.input_token_threshold`
当会话累计输入 token 数达到此阈值时触发自动压缩;默认 100000。
当会话累计输入 token 数达到此阈值时触发自动压缩;默认 `0`(自动推导),推导失败时回退到 `fallback_input_token_threshold`(默认 `100000`)

## 自动压缩

Expand Down Expand Up @@ -154,3 +162,12 @@ compact 相关 runtime 事件包括:
- `trigger_mode`
- `transcript_id`
- `transcript_path`

## Auto Compact 阈值解析

- `context.auto_compact.input_token_threshold > 0` 时,直接使用显式手动阈值。
- `context.auto_compact.input_token_threshold <= 0` 时,系统会对当前选中的 provider/model 做自动推导。
- 自动推导公式为 `resolved_threshold = context_window - reserve_tokens`。
- `reserve_tokens` 默认 `13000`,用于给输出、tool call 和 system prompt 预留缓冲。
- 如果当前模型没有可用的 `ContextWindow`,或窗口值小于等于 `reserve_tokens`,则回退到 `fallback_input_token_threshold`。
- `fallback_input_token_threshold` 默认 `100000`,用于保证主链路在缺少模型窗口元数据时仍可稳定运行。
31 changes: 31 additions & 0 deletions docs/guides/adding-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,34 @@ func DefaultProviders() []ProviderConfig {
```

所有内置 provider 都通过代码集中注册。模型选择器展示的候选模型由默认模型、动态发现结果和本地缓存共同组成。

## custom provider 模型元数据补齐

对于复用 `openaicompat` 驱动的 custom provider,如果上游 `GET /models` 不能返回可靠的上下文窗口信息,可以在:

```text
~/.neocode/providers/<provider-name>/provider.yaml
```

中显式声明 `models`:

```yaml
name: company-gateway
driver: openaicompat
api_key_env: COMPANY_GATEWAY_API_KEY
models:
- id: deepseek-coder
name: DeepSeek Coder
context_window: 131072
max_output_tokens: 8192
openai_compatible:
base_url: https://llm.example.com/v1
api_style: chat_completions
```

约束如下:

- `models[].id` 必须非空。
- `models[].context_window` 和 `models[].max_output_tokens` 如果显式配置,必须大于 `0`。
- 同一个 `provider.yaml` 中重复的模型 `id` 会在加载阶段直接报错。
- 这些元数据会进入统一的 model catalog 合并链路,优先级仍为“配置模型元数据优先于 discovery/default”。
44 changes: 43 additions & 1 deletion docs/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ selected_provider: openai
current_model: gpt-5.4
shell: bash
tool_timeout_sec: 20
runtime:
max_no_progress_streak: 3

tools:
webfetch:
Expand All @@ -56,7 +58,9 @@ context:
micro_compact_disabled: false
auto_compact:
enabled: false
input_token_threshold: 100000
input_token_threshold: 0
reserve_tokens: 13000
fallback_input_token_threshold: 100000
```

### 基础字段
Expand All @@ -79,6 +83,14 @@ context:
| `context.compact.micro_compact_disabled` | 是否关闭默认启用的 micro compact |
| `context.auto_compact.enabled` | 是否启用自动压缩 |
| `context.auto_compact.input_token_threshold` | 自动压缩输入 token 阈值 |
| `context.auto_compact.reserve_tokens` | 自动阈值推导时预留 token 缓冲(`resolved_threshold = context_window - reserve_tokens`) |
| `context.auto_compact.fallback_input_token_threshold` | 自动推导失败时使用的保底阈值 |

### `runtime` 字段

| 字段 | 说明 |
|------|------|
| `runtime.max_no_progress_streak` | 连续”无进展”轮次熔断阈值,默认 `3`;streak 达到 `limit-1`(默认第 2 轮)时向模型注入一次系统级纠偏提示,达到 `limit`(默认第 3 轮)时终止运行 |

### `tools` 字段

Expand Down Expand Up @@ -134,6 +146,13 @@ openai_compatible:
api_style: chat_completions
```

## Auto Compact 失败与校验补充

- 当 `context.auto_compact.input_token_threshold <= 0` 时,如果当前 provider 选择无效、catalog snapshot 查询失败,或模型缺少可用的 `ContextWindow`,系统会回退到 `fallback_input_token_threshold`,不会静默关闭 auto compact。
- `~/.neocode/providers/<provider-name>/provider.yaml` 中的 `models[].id` 必须非空。
- `models[].context_window` 和 `models[].max_output_tokens` 如果显式配置,必须大于 `0`。
- `models` 中重复的模型 `id` 会在加载 `provider.yaml` 时直接报错。

文件路径:

```text
Expand Down Expand Up @@ -222,3 +241,26 @@ config: environment variable OPENAI_API_KEY is empty
- [添加 Provider](./adding-providers.md)
- [配置管理详细设计](../config-management-detail-design.md)
- [Context Compact](../context-compact.md)

## Auto Compact 补充说明

- `context.auto_compact.input_token_threshold > 0` 时,系统直接使用该显式阈值。
- `context.auto_compact.input_token_threshold <= 0` 时,系统会根据当前 `current_model` 对应的 `ContextWindow` 自动推导输入阈值。
- 推导公式为 `context_window - reserve_tokens`。
- `reserve_tokens` 默认 `13000`。
- 如果当前 provider/model 没有可用的 `ContextWindow` 元数据,则回退到 `fallback_input_token_threshold`。
- custom provider 可以在 `~/.neocode/providers/<provider-name>/provider.yaml` 中通过 `models` 字段补齐模型元数据,例如:

```yaml
name: company-gateway
driver: openaicompat
api_key_env: COMPANY_GATEWAY_API_KEY
models:
- id: deepseek-coder
name: DeepSeek Coder
context_window: 131072
max_output_tokens: 8192
openai_compatible:
base_url: https://llm.example.com/v1
api_style: chat_completions
```
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ module neo-code
go 1.25.0

require (
github.com/atotto/clipboard v0.1.4
github.com/charmbracelet/bubbles v1.0.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/glamour v1.0.0
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
golang.design/x/clipboard v0.7.1
golang.org/x/net v0.52.0
golang.org/x/sys v0.42.0
gopkg.in/yaml.v3 v3.0.1
Expand All @@ -18,6 +18,7 @@ require (
require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
Expand Down Expand Up @@ -57,6 +58,9 @@ require (
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
golang.org/x/image v0.28.0 // indirect
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
golang.org/x/term v0.41.0 // indirect
golang.org/x/text v0.35.0 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,16 @@ github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c=
golang.design/x/clipboard v0.7.1/go.mod h1:i5SiIqj0wLFw9P/1D7vfILFK0KHMk7ydE72HRrUIgkg=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 h1:Wdx0vgH5Wgsw+lF//LJKmWOJBLWX6nprsMqnf99rYDE=
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f h1:/n+PL2HlfqeSiDCuhdBbRNlGS/g2fM4OHufalHaTVG8=
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f/go.mod h1:ESkJ836Z6LpG6mTVAhA48LpfW/8fNR0ifStlH2axyfg=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
15 changes: 15 additions & 0 deletions internal/app/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ func BuildRuntime(ctx context.Context, opts BootstrapOptions) (RuntimeBundle, er
contextBuilder,
)
runtimeSvc.SetSkillsRegistry(buildSkillsRegistry(ctx, loader.BaseDir()))
runtimeSvc.SetAutoCompactThresholdResolver(runtimeAutoCompactThresholdResolverFunc(
func(ctx context.Context, cfg config.Config) (int, error) {
resolution, err := configstate.ResolveAutoCompactThreshold(ctx, cfg, modelCatalogs)
if err != nil {
return 0, err
}
return resolution.Threshold, nil
},
))

// 注入记忆提取钩子:当 AutoExtract 启用且 memoSvc 可用时,ReAct 循环完成后异步提取记忆。
if memoSvc != nil && cfg.Memo.AutoExtract {
Expand Down Expand Up @@ -306,3 +315,9 @@ type textGenAdapter func(ctx context.Context, prompt string, msgs []providertype
func (f textGenAdapter) Generate(ctx context.Context, prompt string, msgs []providertypes.Message) (string, error) {
return f(ctx, prompt, msgs)
}

type runtimeAutoCompactThresholdResolverFunc func(ctx context.Context, cfg config.Config) (int, error)

func (f runtimeAutoCompactThresholdResolverFunc) ResolveAutoCompactThreshold(ctx context.Context, cfg config.Config) (int, error) {
return f(ctx, cfg)
}
7 changes: 7 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Config struct {
Workdir string `yaml:"-"`
Shell string `yaml:"shell"`
ToolTimeoutSec int `yaml:"tool_timeout_sec,omitempty"`
Runtime RuntimeConfig `yaml:"runtime,omitempty"`
Context ContextConfig `yaml:"context,omitempty"`
Tools ToolsConfig `yaml:"tools,omitempty"`
Memo MemoConfig `yaml:"memo,omitempty"`
Expand All @@ -32,6 +33,7 @@ func StaticDefaults() *Config {
Workdir: DefaultWorkdir,
Shell: defaultShell(),
ToolTimeoutSec: DefaultToolTimeoutSec,
Runtime: defaultRuntimeConfig(),
Context: defaultContextConfig(),
Tools: ToolsConfig{
WebFetch: defaultWebFetchConfig(),
Expand All @@ -48,6 +50,7 @@ func (c *Config) Clone() Config {

clone := *c
clone.Providers = cloneProviders(c.Providers)
clone.Runtime = c.Runtime.Clone()
clone.Context = c.Context.Clone()
clone.Tools = c.Tools.Clone()
clone.Memo = c.Memo.Clone()
Expand All @@ -69,6 +72,7 @@ func (c *Config) applyStaticDefaults(defaults Config) {
if c.ToolTimeoutSec <= 0 {
c.ToolTimeoutSec = defaults.ToolTimeoutSec
}
c.Runtime.ApplyDefaults(defaults.Runtime)
c.Context.ApplyDefaults(defaults.Context)
c.Tools.ApplyDefaults(defaults.Tools)
c.Memo.ApplyDefaults(defaults.Memo)
Expand Down Expand Up @@ -122,6 +126,9 @@ func (c *Config) ValidateSnapshot() error {
if err := c.Tools.Validate(); err != nil {
return fmt.Errorf("config: tools: %w", err)
}
if err := c.Runtime.Validate(); err != nil {
return fmt.Errorf("config: runtime: %w", err)
}
if err := c.Context.Validate(); err != nil {
return fmt.Errorf("config: context: %w", err)
}
Expand Down
Loading
Loading