This commit is contained in:
lpf
2026-02-19 22:53:58 +08:00
parent e14bddd546
commit eb721c2447
8 changed files with 98 additions and 262 deletions

View File

@@ -166,10 +166,7 @@ clawgo channel test --channel telegram --to <chat_id> -m "ping"
"defaults": {
"runtime_control": {
"intent_high_confidence": 0.75,
"intent_confirm_min_confidence": 0.45,
"intent_max_input_chars": 1200,
"confirm_ttl_seconds": 300,
"confirm_max_clarification_turns": 2,
"autonomy_tick_interval_sec": 20,
"autonomy_min_run_interval_sec": 20,
"autonomy_idle_threshold_sec": 20,
@@ -179,11 +176,6 @@ clawgo channel test --channel telegram --to <chat_id> -m "ping"
"autolearn_max_rounds_without_user": 200,
"run_state_ttl_seconds": 1800,
"run_state_max": 500,
"run_control_latest_keywords": ["latest", "last run", "recent run", "最新", "最近", "上一次", "上个"],
"run_control_wait_keywords": ["wait", "等待", "等到", "阻塞"],
"run_control_status_keywords": ["status", "状态", "进度", "running", "运行"],
"run_control_run_mention_keywords": ["run", "任务"],
"run_control_minute_units": ["分钟", "min", "mins", "minute", "minutes", "m"],
"tool_parallel_safe_names": ["read_file", "list_files", "find_files", "grep_files", "memory_search", "web_search", "repo_map", "system_info"],
"tool_max_parallel_calls": 2
}

View File

@@ -166,10 +166,7 @@ Runtime-control config example (intent thresholds / autonomy guards / run-state
"defaults": {
"runtime_control": {
"intent_high_confidence": 0.75,
"intent_confirm_min_confidence": 0.45,
"intent_max_input_chars": 1200,
"confirm_ttl_seconds": 300,
"confirm_max_clarification_turns": 2,
"autonomy_tick_interval_sec": 20,
"autonomy_min_run_interval_sec": 20,
"autonomy_idle_threshold_sec": 20,
@@ -179,11 +176,6 @@ Runtime-control config example (intent thresholds / autonomy guards / run-state
"autolearn_max_rounds_without_user": 200,
"run_state_ttl_seconds": 1800,
"run_state_max": 500,
"run_control_latest_keywords": ["latest", "last run", "recent run", "最新", "最近", "上一次", "上个"],
"run_control_wait_keywords": ["wait", "等待", "等到", "阻塞"],
"run_control_status_keywords": ["status", "状态", "进度", "running", "运行"],
"run_control_run_mention_keywords": ["run", "任务"],
"run_control_minute_units": ["分钟", "min", "mins", "minute", "minutes", "m"],
"tool_parallel_safe_names": ["read_file", "list_files", "find_files", "grep_files", "memory_search", "web_search", "repo_map", "system_info"],
"tool_max_parallel_calls": 2
}

View File

@@ -17,10 +17,7 @@
},
"runtime_control": {
"intent_high_confidence": 0.75,
"intent_confirm_min_confidence": 0.45,
"intent_max_input_chars": 1200,
"confirm_ttl_seconds": 300,
"confirm_max_clarification_turns": 2,
"autonomy_tick_interval_sec": 20,
"autonomy_min_run_interval_sec": 20,
"autonomy_idle_threshold_sec": 20,
@@ -30,11 +27,6 @@
"autolearn_max_rounds_without_user": 200,
"run_state_ttl_seconds": 1800,
"run_state_max": 500,
"run_control_latest_keywords": ["latest", "last run", "recent run", "最新", "最近", "上一次", "上个"],
"run_control_wait_keywords": ["wait", "等待", "等到", "阻塞"],
"run_control_status_keywords": ["status", "状态", "进度", "running", "运行"],
"run_control_run_mention_keywords": ["run", "任务"],
"run_control_minute_units": ["分钟", "min", "mins", "minute", "minutes", "m"],
"tool_parallel_safe_names": ["read_file", "list_files", "find_files", "grep_files", "memory_search", "web_search", "repo_map", "system_info"],
"tool_max_parallel_calls": 2
}

View File

@@ -109,14 +109,6 @@ type controlPolicy struct {
autoLearnMaxRoundsWithoutUser int
}
type runControlLexicon struct {
latestKeywords []string
waitKeywords []string
statusKeywords []string
runMentionKeywords []string
minuteUnits map[string]struct{}
}
type runtimeControlStats struct {
runAccepted int64
runCompleted int64
@@ -204,7 +196,6 @@ type AgentLoop struct {
controlConfirmMu sync.Mutex
controlConfirm map[string]pendingControlConfirmation
controlPolicy controlPolicy
runControlLex runControlLexicon
parallelSafeTools map[string]struct{}
maxParallelCalls int
controlStats runtimeControlStats
@@ -263,12 +254,15 @@ type taskExecutionDirectivesLLMResponse struct {
Confidence float64 `json:"confidence"`
}
var runIDPattern = regexp.MustCompile(`(?i)\b(run-\d+-\d+)\b`)
var runWaitTimeoutPattern = regexp.MustCompile(`(?i)(\d+)\s*(seconds|second|secs|sec|minutes|minute|mins|min|分钟|秒|s|m)`)
var defaultRunControlLatestKeywords = []string{"latest", "last run", "recent run", "最新", "最近", "上一次", "上个"}
var defaultRunControlWaitKeywords = []string{"wait", "等待", "等到", "阻塞"}
var defaultRunControlStatusKeywords = []string{"status", "状态", "进度", "running", "运行"}
var defaultRunControlRunMentionKeywords = []string{"run", "任务"}
type runControlIntentLLMResponse struct {
Matched bool `json:"matched"`
RunID string `json:"run_id"`
Latest bool `json:"latest"`
Wait bool `json:"wait"`
TimeoutSeconds int `json:"timeout_seconds"`
Confidence float64 `json:"confidence"`
}
var defaultParallelSafeToolNames = []string{"read_file", "list_files", "find_files", "grep_files", "memory_search", "web_search", "repo_map", "system_info"}
var autonomyIntentKeywords = []string{
"autonomy", "autonomous", "autonomy mode", "self-driven", "self driven",
@@ -278,14 +272,6 @@ var autoLearnIntentKeywords = []string{
"auto-learn", "autolearn", "learning loop", "learn loop",
"自学习", "学习循环", "自动学习", "学习模式",
}
var defaultRunWaitMinuteUnits = map[string]struct{}{
"分钟": {},
"min": {},
"mins": {},
"minute": {},
"minutes": {},
"m": {},
}
type stageReporter struct {
onUpdate func(content string)
@@ -375,18 +361,9 @@ func loadControlPolicyFromConfig(base controlPolicy, rc config.RuntimeControlCon
if rc.IntentHighConfidence > 0 {
p.intentHighConfidence = rc.IntentHighConfidence
}
if rc.IntentConfirmMinConfidence >= 0 {
p.intentConfirmMinConfidence = rc.IntentConfirmMinConfidence
}
if rc.IntentMaxInputChars > 0 {
p.intentMaxInputChars = rc.IntentMaxInputChars
}
if rc.ConfirmTTLSeconds > 0 {
p.confirmTTL = time.Duration(rc.ConfirmTTLSeconds) * time.Second
}
if rc.ConfirmMaxClarificationTurns >= 0 {
p.confirmMaxClarificationTurns = rc.ConfirmMaxClarificationTurns
}
if rc.AutonomyTickIntervalSec > 0 {
p.autonomyTickInterval = time.Duration(rc.AutonomyTickIntervalSec) * time.Second
}
@@ -423,42 +400,6 @@ func loadRunStatePolicyFromConfig(rc config.RuntimeControlConfig) (time.Duration
return ttl, maxEntries
}
func defaultRunControlLexicon() runControlLexicon {
latest := append([]string(nil), defaultRunControlLatestKeywords...)
wait := append([]string(nil), defaultRunControlWaitKeywords...)
status := append([]string(nil), defaultRunControlStatusKeywords...)
mention := append([]string(nil), defaultRunControlRunMentionKeywords...)
minutes := make(map[string]struct{}, len(defaultRunWaitMinuteUnits))
for unit := range defaultRunWaitMinuteUnits {
minutes[unit] = struct{}{}
}
return runControlLexicon{
latestKeywords: latest,
waitKeywords: wait,
statusKeywords: status,
runMentionKeywords: mention,
minuteUnits: minutes,
}
}
func loadRunControlLexiconFromConfig(rc config.RuntimeControlConfig) runControlLexicon {
base := defaultRunControlLexicon()
base.latestKeywords = normalizeKeywordList(rc.RunControlLatestKeywords, base.latestKeywords)
base.waitKeywords = normalizeKeywordList(rc.RunControlWaitKeywords, base.waitKeywords)
base.statusKeywords = normalizeKeywordList(rc.RunControlStatusKeywords, base.statusKeywords)
base.runMentionKeywords = normalizeKeywordList(rc.RunControlRunMentionKeywords, base.runMentionKeywords)
minuteUnits := normalizeKeywordList(rc.RunControlMinuteUnits, nil)
if len(minuteUnits) == 0 {
return base
}
base.minuteUnits = make(map[string]struct{}, len(minuteUnits))
for _, unit := range minuteUnits {
base.minuteUnits[unit] = struct{}{}
}
return base
}
func normalizeKeywordList(values []string, fallback []string) []string {
if len(values) == 0 {
return append([]string(nil), fallback...)
@@ -688,7 +629,6 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
defaultModel := defaultModelFromModels(modelsByProxy[primaryProxy], provider)
policy := loadControlPolicyFromConfig(defaultControlPolicy(), cfg.Agents.Defaults.RuntimeControl)
policy = applyLegacyControlPolicyEnvOverrides(policy)
runControlLex := loadRunControlLexiconFromConfig(cfg.Agents.Defaults.RuntimeControl)
parallelSafeTools, maxParallelCalls := loadToolParallelPolicyFromConfig(cfg.Agents.Defaults.RuntimeControl)
runStateTTL, runStateMax := loadRunStatePolicyFromConfig(cfg.Agents.Defaults.RuntimeControl)
// Keep compatibility with older env names.
@@ -722,7 +662,6 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
autonomyBySess: make(map[string]*autonomySession),
controlConfirm: make(map[string]pendingControlConfirmation),
controlPolicy: policy,
runControlLex: runControlLex,
parallelSafeTools: parallelSafeTools,
maxParallelCalls: maxParallelCalls,
runStates: make(map[string]*runState),
@@ -731,10 +670,7 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
}
logger.InfoCF("agent", "Control policy initialized", map[string]interface{}{
"intent_high_confidence": policy.intentHighConfidence,
"intent_confirm_min_confidence": policy.intentConfirmMinConfidence,
"intent_max_input_chars": policy.intentMaxInputChars,
"confirm_ttl": policy.confirmTTL.String(),
"confirm_max_clarification_turns": policy.confirmMaxClarificationTurns,
"autonomy_tick_interval": policy.autonomyTickInterval.String(),
"autonomy_min_run_interval": policy.autonomyMinRunInterval.String(),
"autonomy_idle_threshold": policy.autonomyIdleThreshold.String(),
@@ -742,11 +678,6 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
"autonomy_max_pending_duration": policy.autonomyMaxPendingDuration.String(),
"autonomy_max_consecutive_stalls": policy.autonomyMaxConsecutiveStalls,
"autolearn_max_rounds_without_user": policy.autoLearnMaxRoundsWithoutUser,
"run_control_latest_keywords": len(runControlLex.latestKeywords),
"run_control_wait_keywords": len(runControlLex.waitKeywords),
"run_control_status_keywords": len(runControlLex.statusKeywords),
"run_control_run_keywords": len(runControlLex.runMentionKeywords),
"run_control_minute_units": len(runControlLex.minuteUnits),
"parallel_safe_tool_count": len(parallelSafeTools),
"tool_max_parallel_calls": maxParallelCalls,
"run_state_ttl": runStateTTL.String(),
@@ -1697,63 +1628,9 @@ func (al *AgentLoop) waitForRun(ctx context.Context, runID string) (runState, bo
}
}
func detectRunControlIntent(content string) (runControlIntent, bool) {
return detectRunControlIntentWithLexicon(content, defaultRunControlLexicon())
}
func detectRunControlIntentWithLexicon(content string, lex runControlLexicon) (runControlIntent, bool) {
text := strings.TrimSpace(content)
if text == "" {
return runControlIntent{}, false
}
if strings.HasPrefix(text, "/") {
return runControlIntent{}, false
}
lower := strings.ToLower(text)
intent := runControlIntent{
timeout: defaultRunWaitTimeout,
}
if m := runIDPattern.FindStringSubmatch(text); len(m) > 1 {
intent.runID = strings.ToLower(strings.TrimSpace(m[1]))
}
intent.latest = containsAnySubstring(lower, lex.latestKeywords...)
intent.wait = containsAnySubstring(lower, lex.waitKeywords...)
isStatusQuery := containsAnySubstring(lower, lex.statusKeywords...)
isRunMentioned := containsAnySubstring(lower, lex.runMentionKeywords...)
if !intent.wait && !isStatusQuery {
if intent.runID == "" || !isRunMentioned {
return runControlIntent{}, false
}
}
if intent.runID == "" && !intent.latest {
return runControlIntent{}, false
}
if intent.wait {
intent.timeout = parseRunWaitTimeoutWithLexicon(text, lex)
}
return intent, true
}
func parseRunWaitTimeout(content string) time.Duration {
return parseRunWaitTimeoutWithLexicon(content, defaultRunControlLexicon())
}
func parseRunWaitTimeoutWithLexicon(content string, lex runControlLexicon) time.Duration {
timeout := defaultRunWaitTimeout
matches := runWaitTimeoutPattern.FindStringSubmatch(content)
if len(matches) < 3 {
return timeout
}
n, err := strconv.Atoi(matches[1])
if err != nil || n <= 0 {
return timeout
}
unit := strings.ToLower(strings.TrimSpace(matches[2]))
if _, isMinute := lex.minuteUnits[unit]; isMinute {
timeout = time.Duration(n) * time.Minute
} else {
timeout = time.Duration(n) * time.Second
func normalizeRunWaitTimeout(timeout time.Duration) time.Duration {
if timeout <= 0 {
return defaultRunWaitTimeout
}
if timeout < minRunWaitTimeout {
return minRunWaitTimeout
@@ -1887,19 +1764,86 @@ func (al *AgentLoop) executeRunControlIntent(ctx context.Context, sessionKey str
return al.naturalizeUserFacingText(ctx, formatRunStateReport(rs))
}
func (al *AgentLoop) inferRunControlIntent(ctx context.Context, content string) (runControlIntent, float64, bool) {
text := strings.TrimSpace(content)
if text == "" || strings.HasPrefix(text, "/") {
return runControlIntent{}, 0, false
}
limit := defaultControlPolicy().intentMaxInputChars
if al != nil && al.controlPolicy.intentMaxInputChars > 0 {
limit = al.controlPolicy.intentMaxInputChars
}
if len(text) > limit {
text = truncate(text, limit)
}
systemPrompt := al.withBootstrapPolicy(`You classify run-control intent for an AI assistant.
Return JSON only.
Schema:
{"matched":true|false,"run_id":"","latest":false,"wait":false,"timeout_seconds":0,"confidence":0.0}
Rules:
- matched=true only when user asks run status/wait/latest-run control.
- run_id: use canonical form like run-123-1 if provided, else empty.
- latest=true when user asks latest/recent run.
- wait=true when user asks to wait until run completes.
- timeout_seconds: wait timeout in seconds, 0 means default.
- confidence: 0..1`)
resp, err := al.callLLMWithModelFallback(ctx, []providers.Message{
{Role: "system", Content: systemPrompt},
{Role: "user", Content: text},
}, nil, map[string]interface{}{
"max_tokens": 220,
"temperature": 0.0,
})
if err != nil || resp == nil {
return runControlIntent{}, 0, false
}
raw := extractJSONObject(resp.Content)
if raw == "" {
return runControlIntent{}, 0, false
}
var parsed runControlIntentLLMResponse
if err := json.Unmarshal([]byte(raw), &parsed); err != nil {
return runControlIntent{}, 0, false
}
if !parsed.Matched {
return runControlIntent{}, parsed.Confidence, false
}
intent := runControlIntent{
runID: strings.ToLower(strings.TrimSpace(parsed.RunID)),
latest: parsed.Latest,
wait: parsed.Wait,
timeout: defaultRunWaitTimeout,
}
if parsed.TimeoutSeconds > 0 {
intent.timeout = time.Duration(parsed.TimeoutSeconds) * time.Second
}
intent.timeout = normalizeRunWaitTimeout(intent.timeout)
if intent.runID == "" && !intent.latest {
return runControlIntent{}, parsed.Confidence, false
}
return intent, parsed.Confidence, true
}
func (al *AgentLoop) handleNaturalRunControl(ctx context.Context, msg bus.InboundMessage) (bool, string) {
intent, ok := detectRunControlIntentWithLexicon(msg.Content, al.effectiveRunControlLexicon())
intent, confidence, ok := al.inferRunControlIntent(ctx, msg.Content)
if !ok {
return false, ""
}
return true, al.executeRunControlIntent(ctx, msg.SessionKey, intent)
}
func (al *AgentLoop) effectiveRunControlLexicon() runControlLexicon {
if al == nil || len(al.runControlLex.latestKeywords) == 0 || len(al.runControlLex.minuteUnits) == 0 {
return defaultRunControlLexicon()
policy := defaultControlPolicy()
if al != nil {
policy = al.controlPolicy
}
return al.runControlLex
if confidence < policy.intentHighConfidence {
return false, ""
}
return true, al.executeRunControlIntent(ctx, msg.SessionKey, intent)
}
func (al *AgentLoop) controlMetricAdd(counter *int64, delta int64) {
@@ -2025,16 +1969,16 @@ func (al *AgentLoop) handleControlPlane(ctx context.Context, msg bus.InboundMess
al.clearPendingControlConfirmation(msg.SessionKey)
return true, al.executeAutonomyIntent(ctx, msg, intent), nil
} else if outcome.needsConfirm {
al.storePendingAutonomyConfirmation(msg.SessionKey, msg.Content, intent, outcome.confidence)
return true, al.naturalizeUserFacingText(ctx, al.formatAutonomyConfirmationPrompt(intent)), nil
al.clearPendingControlConfirmation(msg.SessionKey)
return true, al.executeAutonomyIntent(ctx, msg, intent), nil
}
if intent, outcome := al.detectAutoLearnIntent(ctx, msg.Content); outcome.matched {
al.clearPendingControlConfirmation(msg.SessionKey)
return true, al.executeAutoLearnIntent(ctx, msg, intent), nil
} else if outcome.needsConfirm {
al.storePendingAutoLearnConfirmation(msg.SessionKey, msg.Content, intent, outcome.confidence)
return true, al.naturalizeUserFacingText(ctx, al.formatAutoLearnConfirmationPrompt(intent)), nil
al.clearPendingControlConfirmation(msg.SessionKey)
return true, al.executeAutoLearnIntent(ctx, msg, intent), nil
}
return false, "", nil

View File

@@ -11,85 +11,35 @@ import (
func TestDetectRunControlIntent(t *testing.T) {
t.Parallel()
intent, ok := detectRunControlIntent("请等待 run-123-7 120 秒后告诉我状态")
if !ok {
t.Fatalf("expected run control intent")
}
if intent.runID != "run-123-7" {
t.Fatalf("unexpected run id: %s", intent.runID)
}
if !intent.wait {
t.Fatalf("expected wait=true")
}
if intent.timeout != 120*time.Second {
t.Fatalf("unexpected timeout: %s", intent.timeout)
if got := normalizeRunWaitTimeout(0); got != defaultRunWaitTimeout {
t.Fatalf("expected default timeout, got %s", got)
}
}
func TestDetectRunControlIntentLatest(t *testing.T) {
t.Parallel()
intent, ok := detectRunControlIntent("latest run status")
if !ok {
t.Fatalf("expected latest run status intent")
}
if !intent.latest {
t.Fatalf("expected latest=true")
}
if intent.runID != "" {
t.Fatalf("expected empty run id")
if got := normalizeRunWaitTimeout(time.Second); got != minRunWaitTimeout {
t.Fatalf("expected min timeout %s, got %s", minRunWaitTimeout, got)
}
}
func TestParseRunWaitTimeout_MinClamp(t *testing.T) {
t.Parallel()
got := parseRunWaitTimeout("wait run-1-1 1 s")
if got != minRunWaitTimeout {
t.Fatalf("expected min timeout %s, got %s", minRunWaitTimeout, got)
if got := normalizeRunWaitTimeout(maxRunWaitTimeout + time.Minute); got != maxRunWaitTimeout {
t.Fatalf("expected max timeout %s, got %s", maxRunWaitTimeout, got)
}
}
func TestParseRunWaitTimeout_MinuteUnit(t *testing.T) {
t.Parallel()
got := parseRunWaitTimeout("等待 run-1-1 2 分钟")
if got != 2*time.Minute {
if got := normalizeRunWaitTimeout(2 * time.Minute); got != 2*time.Minute {
t.Fatalf("expected 2m, got %s", got)
}
}
func TestDetectRunControlIntentIgnoresNonControlText(t *testing.T) {
t.Parallel()
if _, ok := detectRunControlIntent("帮我写一个README"); ok {
t.Fatalf("did not expect run control intent")
}
}
func TestDetectRunControlIntentWithCustomLexicon(t *testing.T) {
t.Parallel()
lex := runControlLexicon{
latestKeywords: []string{"newest"},
waitKeywords: []string{"block"},
statusKeywords: []string{"health"},
runMentionKeywords: []string{"job"},
minuteUnits: map[string]struct{}{"mins": {}},
}
intent, ok := detectRunControlIntentWithLexicon("block run-55-1 for 2 mins and show health", lex)
if !ok {
t.Fatalf("expected intent with custom lexicon")
}
if !intent.wait {
t.Fatalf("expected wait=true")
}
if intent.timeout != 2*time.Minute {
t.Fatalf("unexpected timeout: %s", intent.timeout)
}
}
func TestLatestRunStateBySession(t *testing.T) {
t.Parallel()

View File

@@ -43,10 +43,7 @@ type AgentDefaults struct {
type RuntimeControlConfig struct {
IntentHighConfidence float64 `json:"intent_high_confidence" env:"CLAWGO_INTENT_HIGH_CONFIDENCE"`
IntentConfirmMinConfidence float64 `json:"intent_confirm_min_confidence" env:"CLAWGO_INTENT_CONFIRM_MIN_CONFIDENCE"`
IntentMaxInputChars int `json:"intent_max_input_chars" env:"CLAWGO_INTENT_MAX_INPUT_CHARS"`
ConfirmTTLSeconds int `json:"confirm_ttl_seconds" env:"CLAWGO_CONFIRM_TTL_SECONDS"`
ConfirmMaxClarificationTurns int `json:"confirm_max_clarification_turns" env:"CLAWGO_CONFIRM_MAX_CLARIFY_TURNS"`
AutonomyTickIntervalSec int `json:"autonomy_tick_interval_sec" env:"CLAWGO_AUTONOMY_TICK_INTERVAL_SEC"`
AutonomyMinRunIntervalSec int `json:"autonomy_min_run_interval_sec" env:"CLAWGO_AUTONOMY_MIN_RUN_INTERVAL_SEC"`
AutonomyIdleThresholdSec int `json:"autonomy_idle_threshold_sec" env:"CLAWGO_AUTONOMY_IDLE_THRESHOLD_SEC"`
@@ -56,11 +53,6 @@ type RuntimeControlConfig struct {
AutoLearnMaxRoundsWithoutUser int `json:"autolearn_max_rounds_without_user" env:"CLAWGO_AUTOLEARN_MAX_ROUNDS_WITHOUT_USER"`
RunStateTTLSeconds int `json:"run_state_ttl_seconds" env:"CLAWGO_RUN_STATE_TTL_SECONDS"`
RunStateMax int `json:"run_state_max" env:"CLAWGO_RUN_STATE_MAX"`
RunControlLatestKeywords []string `json:"run_control_latest_keywords"`
RunControlWaitKeywords []string `json:"run_control_wait_keywords"`
RunControlStatusKeywords []string `json:"run_control_status_keywords"`
RunControlRunMentionKeywords []string `json:"run_control_run_mention_keywords"`
RunControlMinuteUnits []string `json:"run_control_minute_units"`
ToolParallelSafeNames []string `json:"tool_parallel_safe_names"`
ToolMaxParallelCalls int `json:"tool_max_parallel_calls"`
}
@@ -262,10 +254,7 @@ func DefaultConfig() *Config {
},
RuntimeControl: RuntimeControlConfig{
IntentHighConfidence: 0.75,
IntentConfirmMinConfidence: 0.45,
IntentMaxInputChars: 1200,
ConfirmTTLSeconds: 300,
ConfirmMaxClarificationTurns: 2,
AutonomyTickIntervalSec: 20,
AutonomyMinRunIntervalSec: 20,
AutonomyIdleThresholdSec: 20,
@@ -275,11 +264,6 @@ func DefaultConfig() *Config {
AutoLearnMaxRoundsWithoutUser: 200,
RunStateTTLSeconds: 1800,
RunStateMax: 500,
RunControlLatestKeywords: []string{"latest", "last run", "recent run", "最新", "最近", "上一次", "上个"},
RunControlWaitKeywords: []string{"wait", "等待", "等到", "阻塞"},
RunControlStatusKeywords: []string{"status", "状态", "进度", "running", "运行"},
RunControlRunMentionKeywords: []string{"run", "任务"},
RunControlMinuteUnits: []string{"分钟", "min", "mins", "minute", "minutes", "m"},
ToolParallelSafeNames: []string{"read_file", "list_files", "find_files", "grep_files", "memory_search", "web_search", "repo_map", "system_info"},
ToolMaxParallelCalls: 2,
},

View File

@@ -65,7 +65,6 @@ func TestLoadConfigAllowsKnownRuntimeControlFields(t *testing.T) {
"runtime_control": {
"intent_high_confidence": 0.88,
"run_state_max": 321,
"run_control_wait_keywords": ["wait", "block"],
"tool_parallel_safe_names": ["read_file", "memory_search"],
"tool_max_parallel_calls": 3
}
@@ -86,9 +85,6 @@ func TestLoadConfigAllowsKnownRuntimeControlFields(t *testing.T) {
if got := cfg.Agents.Defaults.RuntimeControl.RunStateMax; got != 321 {
t.Fatalf("run_state_max mismatch: got %d", got)
}
if got := len(cfg.Agents.Defaults.RuntimeControl.RunControlWaitKeywords); got != 2 {
t.Fatalf("run_control_wait_keywords mismatch: got %d", got)
}
if got := cfg.Agents.Defaults.RuntimeControl.ToolMaxParallelCalls; got != 3 {
t.Fatalf("tool_max_parallel_calls mismatch: got %d", got)
}

View File

@@ -21,18 +21,9 @@ func Validate(cfg *Config) []error {
if rc.IntentHighConfidence <= 0 || rc.IntentHighConfidence > 1 {
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.intent_high_confidence must be in (0,1]"))
}
if rc.IntentConfirmMinConfidence < 0 || rc.IntentConfirmMinConfidence >= rc.IntentHighConfidence {
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.intent_confirm_min_confidence must be >= 0 and < intent_high_confidence"))
}
if rc.IntentMaxInputChars < 200 {
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.intent_max_input_chars must be >= 200"))
}
if rc.ConfirmTTLSeconds <= 0 {
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.confirm_ttl_seconds must be > 0"))
}
if rc.ConfirmMaxClarificationTurns < 0 {
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.confirm_max_clarification_turns must be >= 0"))
}
if rc.AutonomyTickIntervalSec < 5 {
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.autonomy_tick_interval_sec must be >= 5"))
}
@@ -60,11 +51,6 @@ func Validate(cfg *Config) []error {
if rc.RunStateMax <= 0 {
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.run_state_max must be > 0"))
}
errs = append(errs, validateNonEmptyStringList("agents.defaults.runtime_control.run_control_latest_keywords", rc.RunControlLatestKeywords)...)
errs = append(errs, validateNonEmptyStringList("agents.defaults.runtime_control.run_control_wait_keywords", rc.RunControlWaitKeywords)...)
errs = append(errs, validateNonEmptyStringList("agents.defaults.runtime_control.run_control_status_keywords", rc.RunControlStatusKeywords)...)
errs = append(errs, validateNonEmptyStringList("agents.defaults.runtime_control.run_control_run_mention_keywords", rc.RunControlRunMentionKeywords)...)
errs = append(errs, validateNonEmptyStringList("agents.defaults.runtime_control.run_control_minute_units", rc.RunControlMinuteUnits)...)
errs = append(errs, validateNonEmptyStringList("agents.defaults.runtime_control.tool_parallel_safe_names", rc.ToolParallelSafeNames)...)
if rc.ToolMaxParallelCalls <= 0 {
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.tool_max_parallel_calls must be > 0"))