Files
weichen 7f2c8b9ee1
ci / vet-and-test (pull_request) Successful in 50s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
Sink the final gate mechanics and add the contract backend adapter
Why: N1 WP5/WP6 library side - the gate decision logic, artifact
checks, verdict parsing and progress-reminder throttling move from
the product into a gates package behind ports, and the native backend
gains a contract form so conformance has a real implementation to
drive.

Evidence: go vet clean; go test ./... 27 packages ok, including the
conformance suite (succeeded/descriptor/error-sideeffects) against
the real backend.

Impact: new runtime/gates package (ClaimGate, CheckArtifacts,
ParseVerdict, ProgressReminder, shared caps); adapters/native.Backend
gains Contract() returning an exec.ContractBackend wrapper - the
legacy facade keeps its signature so squad consumption is untouched.
Product texts (fix prompts, verifier prompts) stay injected by hosts.

Refs: Documents proposal 2026-10-07-agent-stack-n1q1i1-plans.md WP5/WP6
2026-10-07 06:14:29 +00:00

90 lines
3.1 KiB
Go

package native
import (
"context"
"errors"
"strings"
"agent-native/exec"
)
// contractBackend 把 Backend 适配为 exec.ContractBackend。
// 独立类型的原因:旧门面 Execute(ctx, exec.Request) 与契约的
// Execute(ctx, exec.ContractRequest) 签名冲突,同名方法不能并存;
// 包装让现有 squad 消费(旧门面)与契约消费(conformance/Core)互不影响。
type contractBackend struct{ b *Backend }
// Contract 返回该后端的契约形态(N1 WP6;旧门面保持不变)。
func (b *Backend) Contract() exec.ContractBackend {
return contractBackend{b: b}
}
func (c contractBackend) Descriptor() exec.BackendDescriptor {
return exec.BackendDescriptor{
Name: c.b.Name(),
Capabilities: []exec.Capability{exec.CapText, exec.CapStream, exec.CapTools},
}
}
func (c contractBackend) Execute(ctx context.Context, req exec.ContractRequest) (*exec.ContractResult, error) {
var onEvent func(ev exec.ProcessEvent)
if req.OnEvent != nil {
next := req.OnEvent
onEvent = func(ev exec.ProcessEvent) {
next(exec.Event{Kind: exec.EventKind(ev.Type), Text: ev.Text, Tool: ev.Tool, Detail: ev.Detail})
}
}
// 契约语义:空 ToolPolicy.Profile 等同 reasoning(无工具纯文本任务)。
profile := strings.TrimSpace(req.ToolPolicy.Profile)
if profile == "" {
profile = exec.ToolProfileReasoning
}
old := exec.Request{
Prompt: req.Prompt,
Instructions: req.Instructions,
ExecutionID: req.ExecutionID,
Targets: append([]string(nil), req.Targets...),
ToolProfile: profile,
ToolAllowlist: append([]string(nil), req.ToolPolicy.Allowlist...),
WorkDir: req.Workspace.Root,
OnEvent: onEvent,
}
if b := req.ContractBudget; b.MaxPromptTokens > 0 || b.MaxCompletionTokens > 0 || !b.Deadline.IsZero() {
old.Budget = &exec.Budget{
MaxPromptTokens: b.MaxPromptTokens,
MaxCompletionTokens: b.MaxCompletionTokens,
Deadline: b.Deadline,
}
}
res, err := c.b.Execute(ctx, old)
if err != nil {
// 传输/实现级错误:按契约映射为 Unknown,不经 Go error 返回业务终态。
return &exec.ContractResult{
Outcome: exec.OutcomeUnknown,
Err: &exec.Error{Code: exec.ErrProviderUnavailable, Reason: err.Error(), SideEffects: exec.SideEffectPossible},
}, nil
}
out := &exec.ContractResult{
Logs: res.Logs,
Usage: exec.Usage{
PromptTokens: res.PromptTokens,
CompletionTokens: res.CompletionTokens,
},
}
switch {
case res.Success:
out.Outcome = exec.OutcomeSucceeded
out.Files = res.Patches
case errors.Is(ctx.Err(), context.Canceled):
out.Outcome = exec.OutcomeCanceled
out.Err = &exec.Error{Code: exec.ErrCanceled, Reason: res.ErrorReason, SideEffects: exec.SideEffectPossible}
case errors.Is(ctx.Err(), context.DeadlineExceeded):
out.Outcome = exec.OutcomeFailed
out.Err = &exec.Error{Code: exec.ErrDeadlineExceeded, Reason: res.ErrorReason, SideEffects: exec.SideEffectPossible}
default:
out.Outcome = exec.OutcomeFailed
out.Err = &exec.Error{Code: exec.ErrProviderRejected, Reason: res.ErrorReason, Retryable: res.Retryable, SideEffects: exec.SideEffectPossible}
}
return out, nil
}