Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 10 additions & 23 deletions internal/app/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import (
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"testing"
Expand Down Expand Up @@ -140,12 +139,6 @@ func TestBuildRuntimeRejectsUnsupportedSelectedProviderDriverOnStartup(t *testin
func TestBuildToolRegistryUsesWebFetchConfig(t *testing.T) {
t.Parallel()

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
_, _ = w.Write([]byte("1234567890"))
}))
defer server.Close()

cfg := config.StaticDefaults().Clone()
cfg.Workdir = t.TempDir()
cfg.Tools.WebFetch.MaxResponseBytes = 4
Expand All @@ -162,23 +155,17 @@ func TestBuildToolRegistryUsesWebFetchConfig(t *testing.T) {
t.Fatalf("registry.Get(webfetch) error = %v", err)
}

args, err := json.Marshal(map[string]string{"url": server.URL})
if err != nil {
t.Fatalf("marshal args: %v", err)
}

result, execErr := tool.Execute(context.Background(), tools.ToolCallInput{
Name: "webfetch",
Arguments: args,
})
if execErr != nil {
t.Fatalf("webfetch execute error = %v", execErr)
concrete := reflect.ValueOf(tool)
if concrete.Kind() != reflect.Ptr {
t.Fatalf("expected pointer tool, got %T", tool)
}
if truncated, ok := result.Metadata["truncated"].(bool); !ok || !truncated {
t.Fatalf("expected truncated metadata, got %+v", result.Metadata)
cfgField := concrete.Elem().FieldByName("cfg")
if !cfgField.IsValid() {
t.Fatalf("expected webfetch tool cfg field")
}
if result.Content == "" {
t.Fatalf("expected formatted webfetch content")
maxBytesField := cfgField.FieldByName("MaxResponseBytes")
if maxBytesField.Int() != 4 {
t.Fatalf("expected MaxResponseBytes=4, got %d", maxBytesField.Int())
}
}

Expand Down
9 changes: 1 addition & 8 deletions internal/runtime/controlplane/decider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,15 @@ import (
// StopInput 汇总停止决议所需的信号(可多信号并存,由 DecideStopReason 按优先级表决)。
type StopInput struct {
ContextCanceled bool
MaxLoopsReached bool
RunError error
Success bool
}

// DecideStopReason 按固定优先级返回唯一 StopReason:取消 > 达到轮数上限 > 错误 > 成功。
// DecideStopReason 按固定优先级返回唯一 StopReason:取消 > 错误 > 成功。
func DecideStopReason(in StopInput) (StopReason, string) {
if in.ContextCanceled {
return StopReasonCanceled, ""
}
if in.MaxLoopsReached {
if in.RunError != nil {
return StopReasonMaxLoops, strings.TrimSpace(in.RunError.Error())
}
return StopReasonMaxLoops, "runtime: max loop reached"
}
if in.RunError != nil {
if errors.Is(in.RunError, context.Canceled) {
return StopReasonCanceled, ""
Expand Down
23 changes: 3 additions & 20 deletions internal/runtime/controlplane/decider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,15 @@ func TestDecideStopReasonPriority(t *testing.T) {
reason StopReason
}{
{
name: "canceled_wins_over_max_loops",
name: "canceled_wins_over_error",
in: StopInput{
ContextCanceled: true,
MaxLoopsReached: true,
RunError: errSample,
},
reason: StopReasonCanceled,
},
{
name: "max_loops_wins_over_error",
in: StopInput{
MaxLoopsReached: true,
RunError: errSample,
},
reason: StopReasonMaxLoops,
},
{
name: "error_when_no_max_loop_flag",
name: "error",
in: StopInput{
RunError: errSample,
},
Expand Down Expand Up @@ -70,15 +61,7 @@ func TestDecideStopReasonPriority(t *testing.T) {
func TestDecideStopReasonDetails(t *testing.T) {
t.Parallel()

reason, detail := DecideStopReason(StopInput{MaxLoopsReached: true})
if reason != StopReasonMaxLoops {
t.Fatalf("reason = %q, want %q", reason, StopReasonMaxLoops)
}
if detail != "runtime: max loop reached" {
t.Fatalf("detail = %q, want default max-loop detail", detail)
}

reason, detail = DecideStopReason(StopInput{})
reason, detail := DecideStopReason(StopInput{})
if reason != StopReasonError {
t.Fatalf("reason = %q, want %q", reason, StopReasonError)
}
Expand Down
2 changes: 0 additions & 2 deletions internal/runtime/controlplane/stop_reason.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ type StopReason string
const (
// StopReasonSuccess 表示助手正常结束(无待执行工具调用)。
StopReasonSuccess StopReason = "success"
// StopReasonMaxLoops 表示达到配置的最大推理轮数。
StopReasonMaxLoops StopReason = "max_loops"
// StopReasonError 表示不可恢复的运行时或 provider 错误。
StopReasonError StopReason = "error"
// StopReasonCanceled 表示运行上下文被取消(含用户中断)。
Expand Down
6 changes: 0 additions & 6 deletions internal/runtime/run_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ import (
"neo-code/internal/runtime/controlplane"
)

// ErrMaxLoopReached 表示达到配置的最大推理轮数,用于停止原因分类与测试断言。
var ErrMaxLoopReached = errors.New("runtime: max loop reached")

// ErrNoProgressStreakLimit 表示循环内连续多次未取得进展,触发死循环拦截。
var ErrNoProgressStreakLimit = errors.New("runtime: no progress streak limit reached")

Expand Down Expand Up @@ -57,9 +54,6 @@ func (s *Service) emitRunTermination(ctx context.Context, input UserInput, state
switch {
case errors.Is(err, context.Canceled):
in.ContextCanceled = true
case errors.Is(err, ErrMaxLoopReached):
in.MaxLoopsReached = true
in.RunError = err
default:
in.RunError = err
}
Expand Down
25 changes: 0 additions & 25 deletions internal/runtime/runtime_branch_coverage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,31 +103,6 @@ func TestTransitionRunPhaseNoopBranches(t *testing.T) {
}
}

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

service := &Service{events: make(chan RuntimeEvent, 4)}
state := newRunState("run-max-loops", newRuntimeSession("session-max-loops"))
service.emitRunTermination(
context.Background(),
UserInput{RunID: "run-max-loops", SessionID: state.session.ID},
&state,
ErrMaxLoopReached,
)

events := collectRuntimeEvents(service.Events())
if len(events) != 1 || events[0].Type != EventStopReasonDecided {
t.Fatalf("expected one stop_reason_decided event, got %+v", events)
}
payload, ok := events[0].Payload.(StopReasonDecidedPayload)
if !ok {
t.Fatalf("expected StopReasonDecidedPayload, got %#v", events[0].Payload)
}
if payload.Reason != controlplane.StopReasonMaxLoops {
t.Fatalf("reason = %q, want %q", payload.Reason, controlplane.StopReasonMaxLoops)
}
}

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

Expand Down
4 changes: 0 additions & 4 deletions internal/runtime/runtime_limits.go

This file was deleted.

6 changes: 1 addition & 5 deletions internal/tools/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,7 @@ func NewManager(executor Executor, engine security.PermissionEngine, sandbox Wor
return nil, errors.New("tools: executor is nil")
}
if engine == nil {
defaultEngine, err := security.NewStaticGateway(security.DecisionAllow, nil)
if err != nil {
return nil, err
}
engine = defaultEngine
return nil, errors.New("tools: permission engine is nil")
}
if sandbox == nil {
sandbox = NoopWorkspaceSandbox{}
Expand Down
38 changes: 29 additions & 9 deletions internal/tools/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,21 @@ func (s *stubSandbox) Check(ctx context.Context, action security.Action) (*secur
return s.plan, s.err
}

func mustAllowEngine(t *testing.T) security.PermissionEngine {
t.Helper()
engine, err := security.NewStaticGateway(security.DecisionAllow, nil)
if err != nil {
t.Fatalf("new static gateway: %v", err)
}
return engine
}

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

registry := NewRegistry()
registry.Register(&managerStubTool{name: "bash"})
manager, err := NewManager(registry, nil, nil)
manager, err := NewManager(registry, mustAllowEngine(t), nil)
if err != nil {
t.Fatalf("new manager: %v", err)
}
Expand Down Expand Up @@ -105,7 +114,7 @@ func TestDefaultManagerMicroCompactPolicy(t *testing.T) {
t.Run("executor without policy support defaults to compact", func(t *testing.T) {
t.Parallel()

manager, err := NewManager(executorWithoutOptionalCompactFeatures{}, nil, nil)
manager, err := NewManager(executorWithoutOptionalCompactFeatures{}, mustAllowEngine(t), nil)
if err != nil {
t.Fatalf("new manager: %v", err)
}
Expand All @@ -120,7 +129,7 @@ func TestDefaultManagerMicroCompactPolicy(t *testing.T) {
registry := NewRegistry()
registry.Register(&managerStubTool{name: "preserve_tool", policy: MicroCompactPolicyPreserveHistory})

manager, err := NewManager(registry, nil, nil)
manager, err := NewManager(registry, mustAllowEngine(t), nil)
if err != nil {
t.Fatalf("new manager: %v", err)
}
Expand All @@ -145,7 +154,7 @@ func TestDefaultManagerMicroCompactSummarizer(t *testing.T) {
t.Run("executor without summarizer support returns nil", func(t *testing.T) {
t.Parallel()

manager, err := NewManager(executorWithoutOptionalCompactFeatures{}, nil, nil)
manager, err := NewManager(executorWithoutOptionalCompactFeatures{}, mustAllowEngine(t), nil)
if err != nil {
t.Fatalf("new manager: %v", err)
}
Expand All @@ -162,7 +171,7 @@ func TestDefaultManagerMicroCompactSummarizer(t *testing.T) {
return "summary:" + content
})

manager, err := NewManager(registry, nil, nil)
manager, err := NewManager(registry, mustAllowEngine(t), nil)
if err != nil {
t.Fatalf("new manager: %v", err)
}
Expand Down Expand Up @@ -197,7 +206,7 @@ func TestDefaultManagerListAvailableSpecsBoundaries(t *testing.T) {
manager: func() *DefaultManager {
registry := NewRegistry()
registry.Register(&managerStubTool{name: "bash"})
manager, _ := NewManager(registry, nil, nil)
manager, _ := NewManager(registry, mustAllowEngine(t), nil)
return manager
}(),
ctx: func() context.Context {
Expand Down Expand Up @@ -378,7 +387,7 @@ func TestDefaultManagerExecuteBoundaries(t *testing.T) {
manager: func() *DefaultManager {
registry := NewRegistry()
registry.Register(&managerStubTool{name: "custom_tool"})
manager, _ := NewManager(registry, nil, nil)
manager, _ := NewManager(registry, mustAllowEngine(t), nil)
return manager
}(),
input: ToolCallInput{Name: "custom_tool"},
Expand All @@ -389,7 +398,7 @@ func TestDefaultManagerExecuteBoundaries(t *testing.T) {
manager: func() *DefaultManager {
registry := NewRegistry()
registry.Register(&managerStubTool{name: "bash"})
manager, _ := NewManager(registry, nil, nil)
manager, _ := NewManager(registry, mustAllowEngine(t), nil)
return manager
}(),
input: ToolCallInput{Name: "bash", Arguments: []byte(`{"command":"echo hi"}`)},
Expand Down Expand Up @@ -584,6 +593,17 @@ func TestNewManagerRejectsNilExecutor(t *testing.T) {
}
}

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

registry := NewRegistry()
registry.Register(&managerStubTool{name: "bash"})
manager, err := NewManager(registry, nil, nil)
if err == nil || !strings.Contains(err.Error(), "permission engine is nil") {
t.Fatalf("expected nil engine error, got manager=%v err=%v", manager, err)
}
}

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

Expand Down Expand Up @@ -1879,7 +1899,7 @@ func TestDefaultManagerCapabilitySignerHelpers(t *testing.T) {
registry := NewRegistry()
registry.Register(&managerStubTool{name: "filesystem_read_file", content: "ok"})

manager, err := NewManager(registry, nil, nil)
manager, err := NewManager(registry, mustAllowEngine(t), nil)
if err != nil {
t.Fatalf("new manager: %v", err)
}
Expand Down
Loading
Loading