Merge remote-tracking branch 'origin/main-v2' into fix/pr-8275-agent-role-settings

# Conflicts:
#	internal/agent/run_loop.go
This commit is contained in:
SivanCola committed 2026-08-11 14:06:35 +08:00
commit ec2da98bda
2 files changed
+95 -12

No files matched your search

+12 -12
View File
@@ -115,7 +115,7 @@ func (a *Agent) beginRunTurn(ctx context.Context, input string) (rawInput string
// A fresh user turn starts from zeroed per-turn host state; the new turn's
// values are computed below. Cross-turn state (checkpoint, scope, failure
// budgets) lives directly on Agent and is reconciled field by field.
a.perTurnState = perTurnState{turnInput: input}
a.perTurnState = perTurnState{}
a.resetStructuralRunGuards()
scope, scoped := DeliveryExecutionScopeFromContext(ctx)
preserveEvidence := a.preserveEvidenceOnce
@@ -180,25 +180,25 @@ func (a *Agent) beginRunTurn(ctx context.Context, input string) (rawInput string
// deadlocked read-only subagents. Without the override the raw input is
// classified verbatim: stripping user-controllable markup here would let
// input dressed up as host framing disarm the delivery gates.
classifierInput := a.classifierTaskText
a.turnInput = a.classifierTaskText
if scoped && strings.TrimSpace(scope.TaskText) != "" {
classifierInput = scope.TaskText
} else if strings.TrimSpace(classifierInput) == "" {
classifierInput = rawInput
a.turnInput = scope.TaskText
} else if strings.TrimSpace(a.turnInput) == "" {
a.turnInput = rawInput
}
intent := taskintent.Classify(classifierInput)
intent := taskintent.Classify(a.turnInput)
a.deliveryTaskExpected = intent.NeedsEvidence()
a.deliveryMutationExpected = intent == taskintent.Mutation && registryHasWriterTools(a.tools)
a.deliveryPersistentExpected = taskintent.NeedsPersistentAction(classifierInput)
a.recoveryTaskSummary = boundedRecoveryTaskSummary(classifierInput)
a.deliveryPersistentExpected = taskintent.NeedsPersistentAction(a.turnInput)
a.recoveryTaskSummary = boundedRecoveryTaskSummary(a.turnInput)
// Freeze TaskPolicy for this turn from the session role setting. Subsequent
// SetAgentPreset calls must not change this turn's route/review floor.
if policy, ok := taskpolicy.FromContext(ctx); ok {
a.turnPolicy = policy
} else {
a.turnPolicy = taskpolicy.Derive(taskpolicy.Input{
Raw: classifierInput,
Instruction: taskpolicy.StripQuotedConstraints(classifierInput),
Raw: a.turnInput,
Instruction: taskpolicy.StripQuotedConstraints(a.turnInput),
Preset: agentpreset.AgentPreset(a.AgentPreset()),
PlanMode: a.planMode.Load(),
})
@@ -234,7 +234,7 @@ func (a *Agent) beginRunTurn(ctx context.Context, input string) (rawInput string
a.activeTurnCreatedAt.Store(userCreatedAt)
rawContent := rawInput
if rawContent == "" {
rawContent = classifierInput
rawContent = a.turnInput
}
a.session.Add(provider.Message{
Role: provider.RoleUser, Content: input, RawContent: rawContent,
@@ -597,7 +597,7 @@ func (a *Agent) handleFinalResponse(ctx context.Context, state *runLoopState, te
if readiness.applies {
event.RecordReadinessAudit(a.sink, readiness.audit(evidence.ReadinessAllowed, a.readinessRecovered))
}
a.emitTurnShadows(state.input)
a.emitTurnShadows(a.turnInput)
if !a.closeSteerIntakeIfIdle() {
return true, nil
}
+83
View File
@@ -70,6 +70,89 @@ func TestRunPersistsRawUserInputSeparatelyFromProviderContext(t *testing.T) {
if !strings.HasPrefix(legacy.Content, composed) || !strings.Contains(legacy.Content, "<execution-policy") {
t.Fatalf("previous-release reader sees %q, want provider-visible composed prefix plus execution-policy", legacy.Content)
}
receipt := a.CompletionReceipt()
if receipt == nil {
t.Fatal("completion receipt is nil")
}
if len(receipt.Gaps) != 2 {
t.Fatalf("completion gaps = %+v, want the atomic requirement and mutation check", receipt.Gaps)
}
if got := receipt.Gaps[0].Detail; got != "r1: "+raw {
t.Fatalf("atomic criterion = %q, want raw user input %q", got, raw)
}
if strings.Contains(receipt.Gaps[0].Detail, "capability-route") {
t.Fatalf("completion receipt leaked transient provider context: %+v", receipt.Gaps)
}
}
func TestTransientCapabilityRouteCannotTurnConversationIntoDeliveryReceipt(t *testing.T) {
prov := &userInputCaptureProvider{}
a := New(prov, tool.NewRegistry(), NewSession("system"), Options{}, event.Discard)
const raw = "请解释这个项目目前的进度"
const composed = `<capability-route version="1">
Relevant capabilities for this turn:
- skill:minimax-docx prefer: the skill trigger matches the user request
Policy: prefer means use the skill for the required change
</capability-route>
` + raw
if err := a.Run(WithRawUserInput(context.Background(), raw), composed); err != nil {
t.Fatalf("Run: %v", err)
}
if a.CompletionReceipt() != nil {
t.Fatalf("an advisory turn received a delivery receipt from transient routing: %+v", a.CompletionReceipt())
}
if len(prov.request.Messages) < 2 ||
!strings.Contains(prov.request.Messages[1].Content, `<capability-route version="1">`) ||
!strings.Contains(prov.request.Messages[1].Content, raw) {
t.Fatalf("provider lost the capability route: %+v", prov.request.Messages)
}
if got := a.turnInput; got != raw {
t.Fatalf("contract input = %q, want authenticated raw input %q", got, raw)
}
c := a.LiveContract()
if c == nil || len(c.Requirements) != 0 || len(c.Checks) != 0 {
t.Fatalf("transient route created delivery requirements: %+v", c)
}
}
func TestCompletionContractUsesGoalScopeTaskText(t *testing.T) {
prov := &userInputCaptureProvider{}
a := New(prov, tool.NewRegistry(), NewSession("system"), Options{}, event.Discard)
ctx := WithRawUserInput(context.Background(), "Continue working.")
ctx = WithDeliveryExecutionScope(ctx, DeliveryExecutionScope{ID: "goal-1", TaskText: "fix the parser"})
if err := a.Run(ctx, "<goal-context>continue</goal-context>"); err != nil {
t.Fatalf("Run: %v", err)
}
assertAtomicCriterion(t, a, "fix the parser")
}
func TestCompletionContractUsesPristineSubagentTaskText(t *testing.T) {
prov := &userInputCaptureProvider{}
a := New(prov, tool.NewRegistry(), NewSession("system"), Options{
ClassifierTaskText: "fix the parser",
}, event.Discard)
const wrapped = "<workspace-context>private host framing</workspace-context>\n\nfix the parser"
if err := a.Run(context.Background(), wrapped); err != nil {
t.Fatalf("Run: %v", err)
}
assertAtomicCriterion(t, a, "fix the parser")
}
func assertAtomicCriterion(t *testing.T, a *Agent, want string) {
t.Helper()
receipt := a.CompletionReceipt()
if receipt == nil || len(receipt.Gaps) == 0 {
t.Fatalf("completion receipt = %+v, want atomic criterion %q", receipt, want)
}
if got := receipt.Gaps[0].Detail; got != "r1: "+want {
t.Fatalf("atomic criterion = %q, want %q", got, want)
}
}
func TestSubagentImageCandidatesAreCopiedAndIsolated(t *testing.T) {