diff --git a/adapters/native/backend.go b/adapters/native/backend.go index 40a255d..7a1686d 100644 --- a/adapters/native/backend.go +++ b/adapters/native/backend.go @@ -166,7 +166,15 @@ func (b *Backend) Execute(ctx context.Context, req exec.Request) (*exec.Result, } workDir := req.WorkDir - if !filepath.IsAbs(workDir) { + if strings.TrimSpace(workDir) == "" { + // 空工作区 = 请求未授权持久工作区:会话状态(.agentx/transcript 等) + // 落一次性临时目录,不污染进程 CWD(契约套件即发此类请求)。 + tmp, err := os.MkdirTemp("", "agent-native-run-*") + if err != nil { + return &exec.Result{Success: false, ErrorReason: err.Error()}, nil + } + workDir = tmp + } else if !filepath.IsAbs(workDir) { wd, _ := os.Getwd() workDir = filepath.Join(wd, workDir) } diff --git a/adapters/native/contract_test.go b/adapters/native/contract_test.go index 11c87d0..f214508 100644 --- a/adapters/native/contract_test.go +++ b/adapters/native/contract_test.go @@ -2,6 +2,8 @@ package native import ( "context" + "os" + "path/filepath" "testing" "time" @@ -65,3 +67,47 @@ func TestContractDescriptor(t *testing.T) { t.Fatalf("capabilities = %v", d.Capabilities) } } + +// TestContractEmptyWorkspaceKeepsCWDClean 空 Workspace 的请求不得把会话状态 +// 写进进程 CWD(CI 护栏:make test 后工作区必须保持干净;包目录的已提交 +// .agentx 夹具内容必须逐字节不变)。 +func TestContractEmptyWorkspaceKeepsCWDClean(t *testing.T) { + wd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + snapshot := map[string][]byte{} + for _, rel := range []string{ + filepath.Join(".agentx", "transcript.jsonl"), + filepath.Join(".agentx", "transcript.jsonl.meta.json"), + } { + if b, readErr := os.ReadFile(filepath.Join(wd, rel)); readErr == nil { + snapshot[rel] = b + } + } + b := New(Options{ + ModelOptions: ModelOptions{ + Model: "fake", + Stream: script(llm.Message{Role: llm.RoleAssistant, Content: "hi", StopReason: llm.StopEnd}), + }, + }).Contract() + res, err := b.Execute(context.Background(), exec.ContractRequest{ExecutionID: "e-clean", Prompt: "hi"}) + if err != nil { + t.Fatalf("transport error: %v", err) + } + if res.Outcome != exec.OutcomeSucceeded { + t.Fatalf("outcome = %q err = %+v", res.Outcome, res.Err) + } + for rel, want := range snapshot { + got, readErr := os.ReadFile(filepath.Join(wd, rel)) + if readErr != nil { + t.Fatalf("夹具被删除:%s", rel) + } + if string(got) != string(want) { + t.Fatalf("空 Workspace 的执行改写了包内夹具 %s(应落临时目录)", rel) + } + } + if _, statErr := os.Stat(filepath.Join(wd, ".agentx", "activated-tools.json")); !os.IsNotExist(statErr) { + t.Fatal("空 Workspace 的执行在包目录写下了 activated-tools.json(应落临时目录)") + } +}