Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c7a8055
pref(runtime):验收重建第一步
phantom5099 May 8, 2026
8135772
Merge remote-tracking branch 'refs/remotes/upstream/main' into rebuil…
phantom5099 May 9, 2026
c49581d
Merge remote-tracking branch 'refs/remotes/upstream/main' into rebuil…
phantom5099 May 9, 2026
953137c
Merge remote-tracking branch 'refs/remotes/upstream/main' into rebuil…
phantom5099 May 9, 2026
47afbc6
pref(runtime):验收重建完成
phantom5099 May 9, 2026
bef9833
fix(runtime):修复plan模式下创建todo失败
phantom5099 May 9, 2026
3b4dcef
fix(runtime):将codebase工具添加到plan模式白名单中
phantom5099 May 9, 2026
915fb47
fix(runtime):修复不同 content_contains 或 required 属性被合并
phantom5099 May 9, 2026
e8ddb70
fix(runtime):增加repeat自我纠偏机制
phantom5099 May 9, 2026
887e071
fix(runtime):增加工具调用时长防止超时中断
phantom5099 May 9, 2026
65cb6e5
Merge remote-tracking branch 'upstream/main' into rebuild-verify
phantom5099 May 9, 2026
c0557fd
fix(runtime):删除noprogress
phantom5099 May 9, 2026
b4ed86d
fix(runtime):删除冗余文件
phantom5099 May 9, 2026
6ae3b5f
fix(runtime):补充漏交测试
phantom5099 May 9, 2026
f0f997c
Update provider.go
phantom5099 May 9, 2026
3c8e0b4
fix(runtime):删除多余字段
phantom5099 May 9, 2026
9ff4bd0
Merge remote-tracking branch 'origin/rebuild-verify' into rebuild-verify
phantom5099 May 9, 2026
1464dfe
fix(runtime):修复错误信息显示
phantom5099 May 10, 2026
23426aa
Merge branch 'main' into rebuild-verify
phantom5099 May 10, 2026
1e81287
Merge remote-tracking branch 'upstream/main' into rebuild-verify
phantom5099 May 10, 2026
14de90a
Merge remote-tracking branch 'origin/rebuild-verify' into rebuild-verify
phantom5099 May 10, 2026
41966d2
Merge remote-tracking branch 'upstream/main' into rebuild-verify
phantom5099 May 10, 2026
241241e
fix(runtime): restore tool timeout backoff state
xgopilot May 10, 2026
f97a4a4
Merge branch 'main' into rebuild-verify
phantom5099 May 10, 2026
2e98c14
pref(prompt):加强提示词提示力度
phantom5099 May 10, 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
6 changes: 2 additions & 4 deletions docs/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ tool_timeout_sec: 20
generate_start_timeout_sec: 90

runtime:
max_no_progress_streak: 5
max_repeat_cycle_streak: 3
max_turns: 90
hooks:
Expand Down Expand Up @@ -108,9 +107,8 @@ context:

| 字段 | 说明 |
|------|------|
| `runtime.max_no_progress_streak` | 连续“无进展”轮次提醒阈值,默认 `5`;达到 `limit-1` 起会向模型注入纠偏提示,不会直接终止运行 |
| `runtime.max_repeat_cycle_streak` | 连续“重复调用同一工具参数”提醒阈值,默认 `3`;达到阈值后触发重复循环提醒,不会直接终止运行 |
| `runtime.max_turns` | 单次 Run 的最大推理轮数上限,默认 `40`;达到上限后直接终止并返回明确 stop reason |
| `runtime.max_repeat_cycle_streak` | 连续“相同工具签名 + 相同结果指纹 + 相同子目标”阈值,默认 `3`;达到阈值后先注入重复循环提醒,提醒后仍重复则终止为 `repeat_cycle` |
| `runtime.max_turns` | 单次 Run 的最大推理轮数上限,默认 `90`;达到上限后直接终止并返回明确 stop reason |
| `runtime.hooks.enabled` | hooks 总开关;关闭后不执行 runtime hooks |
| `runtime.hooks.user_hooks_enabled` | user hooks 开关;关闭后不加载 `runtime.hooks.items` |
| `runtime.hooks.default_timeout_sec` | user hook 默认超时秒数,需 `> 0` |
Expand Down
4 changes: 3 additions & 1 deletion docs/stop-reason-and-decision-priority.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
- `max_turn_exceeded`
- `verification_failed`
- `accepted`
- `missing_completion_signal`
- `accept_check_failed`
- `todo_not_converged`
- `todo_waiting_external`
- `no_progress_after_final_intercept`
- `repeat_cycle`
- `max_turn_exceeded_with_unconverged_todos`
- `max_turn_exceeded_with_failed_verification`
- `verification_config_missing`
Expand Down
11 changes: 4 additions & 7 deletions internal/cli/gateway_runtime_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -1703,19 +1703,16 @@ func convertRuntimeSnapshot(snapshot agentruntime.RuntimeSnapshot) gateway.Runti
RunID: strings.TrimSpace(snapshot.RunID),
SessionID: strings.TrimSpace(snapshot.SessionID),
Phase: strings.TrimSpace(snapshot.Phase),
TaskKind: strings.TrimSpace(snapshot.TaskKind),
UpdatedAt: snapshot.UpdatedAt,
Todos: convertRuntimeTodoSnapshot(snapshot.Todos),
Facts: map[string]any{
"runtime_facts": snapshot.Facts.RuntimeFacts,
},
Decision: map[string]any{
"status": strings.TrimSpace(snapshot.Decision.Status),
"stop_reason": strings.TrimSpace(snapshot.Decision.StopReason),
"missing_facts": snapshot.Decision.MissingFacts,
"required_next_actions": snapshot.Decision.RequiredNextActions,
"user_visible_summary": strings.TrimSpace(snapshot.Decision.UserVisibleSummary),
"internal_summary": strings.TrimSpace(snapshot.Decision.InternalSummary),
"status": strings.TrimSpace(snapshot.Decision.Status),
"stop_reason": strings.TrimSpace(snapshot.Decision.StopReason),
"summary": strings.TrimSpace(snapshot.Decision.Summary),
"details": append([]string(nil), snapshot.Decision.Details...),
},
SubAgents: map[string]any{
"started_count": snapshot.SubAgents.StartedCount,
Expand Down
1 change: 0 additions & 1 deletion internal/cli/gateway_runtime_bridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,6 @@ func TestGatewayRuntimePortBridgeListSessionTodosAndSnapshot(t *testing.T) {
RunID: "run-1",
SessionID: "session-2",
Phase: "acceptance",
TaskKind: "workspace_write",
Decision: agentruntime.DecisionSnapshot{Status: "continue", StopReason: "unverified_write"},
SubAgents: agentruntime.SubAgentSnapshot{StartedCount: 1, CompletedCount: 1, FailedCount: 0},
},
Expand Down
15 changes: 7 additions & 8 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1721,7 +1721,6 @@ func TestValidateSnapshotPropagatesCompactError(t *testing.T) {
},
},
Runtime: RuntimeConfig{
MaxNoProgressStreak: 3,
MaxRepeatCycleStreak: 3,
},
Context: ContextConfig{
Expand Down Expand Up @@ -1830,7 +1829,7 @@ func TestParseCurrentConfigRoundTripRuntimeConfig(t *testing.T) {
t.Parallel()

snapshot := testDefaultConfig().Clone()
snapshot.Runtime.MaxNoProgressStreak = 5
snapshot.Runtime.MaxRepeatCycleStreak = 5

data, err := marshalPersistedConfig(snapshot)
if err != nil {
Expand All @@ -1841,8 +1840,8 @@ func TestParseCurrentConfigRoundTripRuntimeConfig(t *testing.T) {
if err != nil {
t.Fatalf("parseCurrentConfig() error = %v", err)
}
if parsed.Runtime.MaxNoProgressStreak != 5 {
t.Fatalf("expected max_no_progress_streak=5, got %d", parsed.Runtime.MaxNoProgressStreak)
if parsed.Runtime.MaxRepeatCycleStreak != 5 {
t.Fatalf("expected max_repeat_cycle_streak=5, got %d", parsed.Runtime.MaxRepeatCycleStreak)
}
}

Expand All @@ -1854,7 +1853,7 @@ selected_provider: openai
current_model: gpt-4.1
shell: bash
runtime:
max_no_progress_streak: -2
max_repeat_cycle_streak: -2
`)

parsed, err := parseCurrentConfig(raw, StaticDefaults().Context, StaticDefaults().Memo)
Expand All @@ -1866,9 +1865,9 @@ runtime:
if err := parsed.ValidateSnapshot(); err != nil {
t.Fatalf("ValidateSnapshot() error = %v", err)
}
if parsed.Runtime.MaxNoProgressStreak != DefaultMaxNoProgressStreak {
t.Fatalf("expected default max_no_progress_streak=%d, got %d",
DefaultMaxNoProgressStreak, parsed.Runtime.MaxNoProgressStreak)
if parsed.Runtime.MaxRepeatCycleStreak != DefaultMaxRepeatCycleStreak {
t.Fatalf("expected default max_repeat_cycle_streak=%d, got %d",
DefaultMaxRepeatCycleStreak, parsed.Runtime.MaxRepeatCycleStreak)
}
}

Expand Down
64 changes: 63 additions & 1 deletion internal/config/context_budget_migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ func MigrateContextBudgetConfigContent(raw []byte) ([]byte, bool, []string, erro
if verificationChanged {
changed = true
}
legacyRuntimeChanged := migrateLegacyRuntimeConfigFields(doc)
if legacyRuntimeChanged {
changed = true
}
legacyMemoChanged := migrateLegacyMemoConfigFields(doc)
if legacyMemoChanged {
changed = true
}

if !changed {
return raw, false, nil, nil
Expand All @@ -146,6 +154,60 @@ func MigrateContextBudgetConfigContent(raw []byte) ([]byte, bool, []string, erro
return out, true, notes, nil
}

// migrateLegacyRuntimeConfigFields 清理 runtime 下已废弃且会导致严格解析失败的历史字段。
func migrateLegacyRuntimeConfigFields(doc map[string]any) bool {
runtimeValue, ok := doc["runtime"]
if !ok {
return false
}
runtimeMap, ok := migrationStringMap(runtimeValue)
if !ok {
return false
}

changed := false
for _, key := range []string{"max_no_progress_streak"} {
if _, exists := runtimeMap[key]; exists {
delete(runtimeMap, key)
changed = true
}
}
if !changed {
return false
}
doc["runtime"] = runtimeMap
return true
}

// migrateLegacyMemoConfigFields 清理 memo 下已移除且可安全丢弃的历史字段。
func migrateLegacyMemoConfigFields(doc map[string]any) bool {
memoValue, ok := doc["memo"]
if !ok {
return false
}
memoMap, ok := migrationStringMap(memoValue)
if !ok {
return false
}

changed := false
for _, key := range []string{"extract_recent_messages"} {
if _, exists := memoMap[key]; exists {
delete(memoMap, key)
changed = true
}
}
if !changed {
return false
}
if len(memoMap) == 0 {
delete(doc, "memo")
} else {
doc["memo"] = memoMap
}
return true
}

// migrateVerificationConfig 清理已废弃的 verification 字段,并将安全的旧 command string 收敛成 argv。
func migrateVerificationConfig(doc map[string]any) (bool, error) {
runtimeValue, ok := doc["runtime"]
Expand All @@ -166,7 +228,7 @@ func migrateVerificationConfig(doc map[string]any) (bool, error) {
}

changed := false
for _, key := range []string{"enabled", "default_task_policy", "final_intercept", "max_retries", "hooks"} {
for _, key := range []string{"enabled", "default_task_policy", "final_intercept", "max_retries", "hooks", "max_no_progress"} {
if _, exists := verificationMap[key]; exists {
delete(verificationMap, key)
changed = true
Expand Down
91 changes: 91 additions & 0 deletions internal/config/context_budget_migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,97 @@ runtime:
}
}

func TestMigrateContextBudgetConfigContentRemovesLegacyRuntimeNoProgressField(t *testing.T) {
t.Parallel()

input := []byte(strings.TrimSpace(`
runtime:
max_no_progress_streak: 5
max_repeat_cycle_streak: 3
`) + "\n")

out, changed, notes, err := MigrateContextBudgetConfigContent(input)
if err != nil {
t.Fatalf("MigrateContextBudgetConfigContent() error = %v", err)
}
if !changed {
t.Fatal("expected migration change")
}
if len(notes) != 0 {
t.Fatalf("expected no migration notes, got %v", notes)
}

text := string(out)
if strings.Contains(text, "max_no_progress_streak") {
t.Fatalf("expected max_no_progress_streak removed, got:\n%s", text)
}
if !strings.Contains(text, "max_repeat_cycle_streak: 3") {
t.Fatalf("expected max_repeat_cycle_streak preserved, got:\n%s", text)
}
}

func TestMigrateContextBudgetConfigContentRemovesLegacyVerificationNoProgressField(t *testing.T) {
t.Parallel()

input := []byte(strings.TrimSpace(`
runtime:
verification:
max_no_progress: 3
verifiers:
test:
timeout_sec: 30
`) + "\n")

out, changed, notes, err := MigrateContextBudgetConfigContent(input)
if err != nil {
t.Fatalf("MigrateContextBudgetConfigContent() error = %v", err)
}
if !changed {
t.Fatal("expected migration change")
}
if len(notes) != 0 {
t.Fatalf("expected no migration notes, got %v", notes)
}

text := string(out)
if strings.Contains(text, "max_no_progress") {
t.Fatalf("expected max_no_progress removed, got:\n%s", text)
}
if !strings.Contains(text, "timeout_sec: 30") {
t.Fatalf("expected verifier config preserved, got:\n%s", text)
}
}

func TestMigrateContextBudgetConfigContentRemovesLegacyMemoExtractRecentMessagesField(t *testing.T) {
t.Parallel()

input := []byte(strings.TrimSpace(`
memo:
auto_extract: true
extract_recent_messages: 4
extract_timeout_sec: 9
`) + "\n")

out, changed, notes, err := MigrateContextBudgetConfigContent(input)
if err != nil {
t.Fatalf("MigrateContextBudgetConfigContent() error = %v", err)
}
if !changed {
t.Fatal("expected migration change")
}
if len(notes) != 0 {
t.Fatalf("expected no migration notes, got %v", notes)
}

text := string(out)
if strings.Contains(text, "extract_recent_messages") {
t.Fatalf("expected extract_recent_messages removed, got:\n%s", text)
}
if !strings.Contains(text, "auto_extract: true") || !strings.Contains(text, "extract_timeout_sec: 9") {
t.Fatalf("expected supported memo fields preserved, got:\n%s", text)
}
}

func TestMigrateContextBudgetConfigFileCreatesBackup(t *testing.T) {
t.Parallel()

Expand Down
10 changes: 0 additions & 10 deletions internal/config/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ import (
)

const (
DefaultMaxNoProgressStreak = 5
DefaultMaxRepeatCycleStreak = 3
DefaultMaxTurns = 90
)

// RuntimeConfig 定义 runtime 层的可调参数。
type RuntimeConfig struct {
MaxNoProgressStreak int `yaml:"max_no_progress_streak,omitempty"`
MaxRepeatCycleStreak int `yaml:"max_repeat_cycle_streak,omitempty"`
MaxTurns int `yaml:"max_turns,omitempty"`
Verification VerificationConfig `yaml:"verification,omitempty"`
Expand All @@ -32,7 +30,6 @@ type RuntimeAssetsConfig struct {
// defaultRuntimeConfig 返回 runtime 配置的静态默认值。
func defaultRuntimeConfig() RuntimeConfig {
return RuntimeConfig{
MaxNoProgressStreak: DefaultMaxNoProgressStreak,
MaxRepeatCycleStreak: DefaultMaxRepeatCycleStreak,
MaxTurns: DefaultMaxTurns,
Verification: defaultVerificationConfig(),
Expand All @@ -52,7 +49,6 @@ func defaultRuntimeAssetsConfig() RuntimeAssetsConfig {
// Clone 复制 runtime 配置,避免调用方共享可变状态。
func (c RuntimeConfig) Clone() RuntimeConfig {
return RuntimeConfig{
MaxNoProgressStreak: c.MaxNoProgressStreak,
MaxRepeatCycleStreak: c.MaxRepeatCycleStreak,
MaxTurns: c.MaxTurns,
Verification: c.Verification.Clone(),
Expand All @@ -66,9 +62,6 @@ func (c *RuntimeConfig) ApplyDefaults(defaults RuntimeConfig) {
if c == nil {
return
}
if c.MaxNoProgressStreak <= 0 {
c.MaxNoProgressStreak = defaults.MaxNoProgressStreak
}
if c.MaxRepeatCycleStreak <= 0 {
c.MaxRepeatCycleStreak = defaults.MaxRepeatCycleStreak
}
Expand All @@ -82,9 +75,6 @@ func (c *RuntimeConfig) ApplyDefaults(defaults RuntimeConfig) {

// Validate 校验 runtime 配置是否满足最小约束。
func (c RuntimeConfig) Validate() error {
if c.MaxNoProgressStreak <= 0 {
return errors.New("max_no_progress_streak must be greater than 0")
}
if c.MaxRepeatCycleStreak <= 0 {
return errors.New("max_repeat_cycle_streak must be greater than 0")
}
Expand Down
Loading
Loading