fix(pat): preserve batch session metadata

This commit is contained in:
shangguanxuan.sgx
2026-06-02 14:36:59 +08:00
parent 4bbd52fc58
commit 3ce64db2fc
4 changed files with 105 additions and 6 deletions
+20 -2
View File
@@ -341,13 +341,31 @@ func extractToolAuthMetadata(schema map[string]any, productID string) *ToolAuthM
if err := json.Unmarshal(data, &metadata); err != nil {
return nil
}
if len(metadata.RequiredScopes) == 0 && len(metadata.RequiredPermissions) == 0 &&
len(metadata.ClientObservedScopes) == 0 && metadata.AuthMetaHash == "" {
if !hasMeaningfulToolAuthMetadata(metadata) {
return nil
}
return &metadata
}
func hasMeaningfulToolAuthMetadata(metadata ToolAuthMetadata) bool {
return strings.TrimSpace(metadata.Version) != "" ||
strings.TrimSpace(metadata.ProductCode) != "" ||
strings.TrimSpace(metadata.Domain) != "" ||
len(metadata.ClientObservedScopes) > 0 ||
len(metadata.RequiredScopes) > 0 ||
len(metadata.RequiredPermissions) > 0 ||
len(metadata.RecommendedScopes) > 0 ||
len(metadata.ExcludedScopes) > 0 ||
len(metadata.GrantProductCodes) > 0 ||
strings.TrimSpace(metadata.RiskHint) != "" ||
strings.TrimSpace(metadata.RiskAction) != "" ||
metadata.ConfirmationRequired ||
len(metadata.Identities) > 0 ||
strings.TrimSpace(metadata.Source) != "" ||
strings.TrimSpace(metadata.AuthMetaVersion) != "" ||
strings.TrimSpace(metadata.AuthMetaHash) != ""
}
func productGrantAuthMetadata(productID string) *ToolAuthMetadata {
productID = strings.TrimSpace(productID)
if productID == "" {
+45
View File
@@ -265,6 +265,51 @@ func TestBuildCatalogCarriesDingTalkAuthMetadata(t *testing.T) {
}
}
func TestBuildCatalogCarriesGrantOnlyAuthMetadata(t *testing.T) {
t.Parallel()
catalog := BuildCatalog([]discovery.RuntimeServer{
{
Server: market.ServerDescriptor{
Key: "calendar-key",
DisplayName: "日历",
Endpoint: "https://example.com/server/calendar",
},
Tools: []transport.ToolDescriptor{
{
Name: "createEvent",
InputSchema: map[string]any{
"type": "object",
"x-dingtalk-auth": map[string]any{
"grantProductCodes": []any{"calendar"},
"recommendedScopes": []any{"calendar.event:create"},
"riskAction": "high-risk-write",
"confirmationRequired": true,
},
},
},
},
},
})
tool, ok := catalog.Products[0].FindTool("createEvent")
if !ok {
t.Fatalf("FindTool(createEvent) = not found")
}
if tool.Auth == nil {
t.Fatal("tool.Auth = nil")
}
if got := tool.Auth.GrantProductCodes; len(got) != 1 || got[0] != "calendar" {
t.Fatalf("GrantProductCodes = %#v, want calendar", got)
}
if got := tool.Auth.RecommendedScopes; len(got) != 1 || got[0] != "calendar.event:create" {
t.Fatalf("RecommendedScopes = %#v, want calendar.event:create", got)
}
if !tool.Auth.ConfirmationRequired {
t.Fatal("ConfirmationRequired = false, want true")
}
}
func TestBuildCatalogFallsBackToProductGrantAuthMetadata(t *testing.T) {
t.Parallel()
+9 -4
View File
@@ -213,7 +213,7 @@ grantType 规则:
if c != nil && c.DryRun() {
if usesPlan {
planArgs := buildBatchPlanArgs(scopes, productCodes, recommend, grantType, true)
planArgs := buildBatchPlanArgs(scopes, productCodes, recommend, grantType, sessionID, true)
result, err := callPATBatchPlan(cmd.Context(), c, agentCode, sessionID, planArgs)
if err != nil {
return fmt.Errorf("pat chmod plan failed: %w", err)
@@ -244,7 +244,7 @@ grantType 规则:
sessionID = resolveSessionIDFromEnv()
}
if usesPlan {
planArgs := buildBatchPlanArgs(scopes, productCodes, recommend, grantType, true)
planArgs := buildBatchPlanArgs(scopes, productCodes, recommend, grantType, sessionID, true)
planResult, err := callPATBatchPlan(cmd.Context(), c, agentCode, sessionID, planArgs)
if err != nil {
return fmt.Errorf("pat chmod plan failed: %w", err)
@@ -269,6 +269,7 @@ grantType 规则:
toolArgs["agentCode"] = agentCode
}
if sessionID != "" {
batchArgs["sessionId"] = sessionID
toolArgs["sessionId"] = sessionID
}
// Legacy server schema accepted singular "scope"; clone the
@@ -405,14 +406,18 @@ func callPATBatchPlan(ctx context.Context, c edition.ToolCaller, agentCode, sess
})
}
func buildBatchPlanArgs(scopes []string, productCodes []string, recommend bool, grantType string, dryRun bool) map[string]any {
return map[string]any{
func buildBatchPlanArgs(scopes []string, productCodes []string, recommend bool, grantType string, sessionID string, dryRun bool) map[string]any {
args := map[string]any{
"scopes": scopes,
"productCodes": productCodes,
"recommend": recommend,
"grantType": grantType,
"dryRun": dryRun,
}
if sessionID != "" {
args["sessionId"] = sessionID
}
return args
}
func extractSelectedScopes(result *edition.ToolResult) ([]string, error) {
+31
View File
@@ -314,6 +314,37 @@ func TestChmod_productsFlagPlansThenGrantsSelectedScopes(t *testing.T) {
}
}
func TestChmod_productsSessionModePassesSessionIDToPlanAndGrant(t *testing.T) {
t.Setenv(agentCodeEnv, "qoderwork")
fake := &sequenceToolCaller{responses: []string{
`{"success":true,"data":{"selectedScopes":["calendar.event:read"]}}`,
`{"success":true,"data":{"grantedScopes":["calendar.event:read"]}}`,
}}
cmd := newChmodCommand(fake)
_ = cmd.Flags().Set("products", "calendar")
_ = cmd.Flags().Set("session-id", "session-123")
if err := cmd.RunE(cmd, nil); err != nil {
t.Fatalf("chmod RunE error = %v", err)
}
if len(fake.calls) != 2 {
t.Fatalf("CallTool count = %d, want 2", len(fake.calls))
}
if got := fake.calls[0].args["grantType"]; got != "session" {
t.Fatalf("plan grantType = %#v, want session", got)
}
if got := fake.calls[0].args["sessionId"]; got != "session-123" {
t.Fatalf("plan sessionId = %#v, want session-123", got)
}
if got := fake.calls[1].args["grantType"]; got != "session" {
t.Fatalf("grant grantType = %#v, want session", got)
}
if got := fake.calls[1].args["sessionId"]; got != "session-123" {
t.Fatalf("grant sessionId = %#v, want session-123", got)
}
}
func TestChmod_recommendFlagPlansThenGrantsWithoutPositionalScopes(t *testing.T) {
t.Setenv(agentCodeEnv, "qoderwork")
fake := &sequenceToolCaller{responses: []string{