From 4a7e19ad62a0d90c1d3336e73b8dd5fca923e372 Mon Sep 17 00:00:00 2001 From: weichen Date: Wed, 7 Oct 2026 11:47:15 +0000 Subject: [PATCH] Keep session state out of the process CWD for workspace-less requests Why: a ContractRequest without a workspace (the conformance minimal case) resolved WorkDir to the process CWD and wrote .agentx transcripts into the repo, dirtying the tree in CI and failing the build step's worktree check. Evidence: GOWORK=off go vet + go test -count=1 ./... green; TestContractEmptyWorkspaceKeepsCWDClean pins the fixture bytes; the adapters/native tree no longer drifts after a suite run. Impact: requests with an explicit workspace are unchanged; empty workspace now lands in a disposable temp dir. Refs: Agent-System-Dev#242 CI failure (run 1144) --- adapters/native/backend.go | 10 ++++++- adapters/native/contract_test.go | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) 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(应落临时目录)") + } +} -- 2.54.0