* feat: PAT scope error visualization and auto-retry with authorization polling
1. PAT error result visualization (non-JSON)
- Human-readable error output matching lark-cli style
- Shows error type, message, hint, and authorization command
- JSON output also available via --format json
2. Auto-retry with polling after authorization wall
- Detects missing_scope / insufficient_scope / permission errors
- Polls every 5s for token update after user completes auth
- 10 minute timeout before giving up
- Automatically retries the original command after successful auth
Changes:
- internal/app/pat_auth_retry.go: Core PAT error handling and retry logic
- internal/app/pat_auth_retry_test.go: 11 unit tests
- internal/app/runner.go: Integration with executeInvocation error path
- internal/auth/device_flow.go: Add SetScope() method
* feat: migrate PAT (Permission Authorization) infrastructure to open-source core
Move PAT permission management code from dws-wukong overlay to the
open-source dingtalk-workspace-cli core. This enables PAT handling
in the public distribution while keeping dws-wukong fully compatible
through the edition.Hooks mechanism.
Changes:
- pkg/edition: add 6 new Hooks fields (AuthClientID, AuthClientFromMCP,
SaveToken, LoadToken, DeleteToken, ClassifyToolResult) for overlay
extensions to inject custom auth/token/error behaviour
- internal/errors/pat: new PATError type with ExitCode()=4 and
RawStderr() for embedded-mode passthrough, plus ClassifyToolResultContent
and ClassifyMCPResponseText classification functions
- internal/pat: new command group (pat chmod) with tool result handling
- internal/app/root: register PAT commands before RegisterExtraCommands hook
- internal/app/runner: integrate ClassifyToolResult hook in the
callResult.IsError branch so overlays can intercept PAT/gateway errors
before generic handling
The dws-wukong overlay remains unchanged — its existing pat/ package and
RegisterExtraCommands hook continue to work. Through the go.mod replace
directive and deduplicateCommands mechanism, both codebases coexist
without conflict.
* feat(pat): add PAT auth check with device flow polling and auto-retry
- Add ClassifyPatAuthCheck / AsPatAuthCheckError in pat.go for
AGENT_CODE_NOT_EXISTS error detection
- Add handlePatAuthCheck in pat_auth_retry.go: inject clientId as
x-robot-uid header, poll device flow endpoint, auto-retry on APPROVED
- Integrate PAT check into runner.go executeInvocation pipeline
(edition hook + open-source fallback)
- Add patAuthRequiredCodes map for extensible auth-required code matching
- Remove test_parse.go (temporary test script)
* fix(pat): update poll path to /cli/oauth/device/poll and add x-user-access-token header
- Change DevicePollPath from /api/dingtalk-workspace-cli/oauth/device/poll
to /cli/oauth/device/poll (aligning with backend endpoint convention)
- Add x-user-access-token header to poll requests (loaded from stored token)
* feat(auth): PAT device flow improvements and flowId empty guard
- pat_auth_retry: fix JSON field parsing (result -> data) using DevicePollResponse
- pat_auth_retry: fix elapsed time calculation based on start timestamp
- device_flow: add flowId empty guard - skip polling and show auth URL for manual handling
- device_flow: unify terminalBaseURL to GetMCPBaseURL for polling endpoint
- device_flow: remove unused PollFlowApproval dead code
- runner: integrate DWS_CLIENT_ID env and app.json persistence
- oauth_provider: add x-robot-uid header removal on logout
- client: add request/response logging for transport debugging
* feat(env): switch all service discovery URLs from pre-release to production
- endpoints: pre-login/pre-api/pre-mcp/pre-open-dev -> login/api/mcp/open-dev
- loader: DefaultMarketBaseURL -> mcp.dingtalk.com
- registry: defaultBaseURL -> mcp.dingtalk.com
- constants: DefaultTerminalBaseURL -> open-dev.dingtalk.com
* fix: remove residual merge conflict markers and duplicate Hooks fields in edition.go; unify developer-settings URL via config helper in device_flow.go
* chore: remove lark-cli reference from PrintPatAuthError comment
* test: add comprehensive unit tests for PAT auth modules
- Create internal/errors/pat_test.go (38 tests): covers ClassifyToolResultContent,
ClassifyMCPResponseText, ClassifyPatAuthCheck, AsPatAuthCheckError, cleanPATJSON,
stripClassFields, getDWSGatewayErrorCode, isNotLoggedInError, isBusinessError,
IsPATError, IsPATNoPermissionCode, suggestForBusinessErrorText
- Supplement pat_auth_retry_test.go (+7 tests): IsPatRetrying context checks,
pollPatDeviceFlow edge cases (server error fallback, SSO redirect skip),
extractPatScopeError nil/identity extraction
* feat(pat): exchange authCode for fresh token after PAT APPROVED
Previously pollPatDeviceFlow discarded the authCode from the poll
response, so handlePatAuthCheck retried with the stale token after
APPROVED. This mirrors device_flow.go loginOnce which calls
exchangeCode → SaveTokenData.
Changes:
- pollPatDeviceFlow: return (status, authCode, error) instead of
(status, error); extract Data.AuthCode on APPROVED
- handlePatAuthCheck APPROVED path: call ExchangeCodeForToken +
SaveTokenData before ResetRuntimeTokenCache, with graceful
fallback on exchange failure
- Update all 6 poll test cases for new 3-return signature; add
authCode assertions for APPROVED/REJECTED/EXPIRED/CANCELLED/
ServerError scenarios
* fix(pat): CR round-2 must-fix items
- scopeValueRegex: support multi-segment scopes (mail:a.b:send)
Updated regex to ([a-zA-Z][a-zA-Z0-9_.]*(?::[a-zA-Z][a-zA-Z0-9_.]*)+)
- handlePatAuthCheck: add 3 integration tests (APPROVED/REJECTED/EmptyFlowID)
Covers the 132-line main orchestrator with mock runner + httptest server
- Extract ParseDeviceFlowStatus + status constants to auth package
Eliminates string literal duplication across device_flow.go and pat_auth_retry.go
* fix(pat): use direct OAuth path when clientSecret is provided
When PAT error response includes both clientId and clientSecret,
use SetClientID() (direct DingTalk API mode) instead of
SetClientIDFromMCP() (MCP proxy mode). The MCP proxy does not
hold the secret for the PAT-assigned app, causing HTTP 400
'invalidParameter.idOrSecret.notFound' on token exchange.
Now: clientSecret present → direct mode; absent → MCP proxy mode.
---------
Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
Add Auth Token Registry pattern (inspired by stdio_registry.go) to allow
plugins to declare per-server HTTP headers in plugin.json. This enables
third-party streamable-http MCP servers (e.g. Alibaba Cloud Bailian) to
use their own API keys independently from the default DingTalk OAuth token.
Changes:
- New: internal/app/auth_registry.go — per-productID auth credential store
- manifest.go: MCPServer gains Headers field for custom HTTP headers
- registry.go: ServerDescriptor gains AuthHeaders field
- converter.go: resolve and pass headers through ToServerDescriptors
- root.go: inject plugin auth at discovery time + auto-generate ToolOverrides
- runner.go: route plugin-auth servers to their own Bearer token at runtime
Multi-token isolation: each server is keyed by its CLI.ID (productID),
so different servers can use independent tokens without interfering with
each other or with the default DingTalk OAuth token.
Stdio MCP servers discovered by the plugin loader now have their tools
automatically registered as CLI subcommands. The runner dispatches
stdio:// virtual endpoints to the local StdioClient subprocess instead
of the HTTP transport, enabling plugin tools to be invoked directly
from the command line (e.g. `dws hello greet --name Peter`).
Also fixes a bug where plugin endpoints registered via AppendDynamicServer
were overwritten by the subsequent SetDynamicServers call in
loadDynamicCommands, and a context lifecycle bug where stdio subprocesses
were killed immediately after startup due to a short-lived timeout context.
- Add ExitCoder interface for edition-specific error types to provide custom exit codes
- Add RawStderrError interface for errors that bypass CLI formatting and output raw content to stderr
- Update ExitCode() to resolve exit codes via ExitCoder interface before falling back to default
- Add ClassifyToolResult hook in edition.Hooks for custom MCP tool result error classification
- Invoke ClassifyToolResult in runner before default business-error detection
- Handle RawStderrError in printExecutionError to pass raw JSON through to desktop runtime
- Add unit tests for ExitCoder and RawStderrError interfaces
EnvironmentLoader.Load() previously returned (empty Catalog, nil) on all
failure paths, making it impossible for callers to distinguish "no services
available" from "discovery failed due to auth/network issues".
Changes:
- Add CatalogDegraded error type with three reasons: unauthenticated,
market_unreachable, runtime_all_failed
- Add auth pre-check: return DegradedUnauthenticated immediately when no
token is available, avoiding doomed MCP connections
- Schema command now handles CatalogDegraded gracefully: outputs hint to
stderr and includes degraded/reason/hint fields in JSON output
- Runner preserves graceful degradation by ignoring CatalogDegraded errors
- Skip empty-products cache to prevent stale cache from masking auth errors
- Edition-aware hint text (open-source vs embedded/wukong)
Made-with: Cursor
Extract resolveAccessTokenFromDir from getCachedRuntimeToken so both
MCP and non-MCP clients (e.g. A2A gateway) share the same OAuth +
legacy fallback logic including host compatibility hooks.
- Add ResolveAuxiliaryAccessToken in internal/app for overlay use;
reuses process-level token cache when configDir matches the edition
default, avoiding repeated Keychain access.
- Simplify pkg/runtimetoken to a thin delegate to the shared impl.
- Refactor getCachedRuntimeToken to call resolveAccessTokenFromDir,
removing duplicated provider/manager setup code.
Made-with: Cursor
- Add token validation in executeInvocation to reject requests early
- Return clear Chinese error message: '未登录,请先执行 dws auth login'
- Include actionable hint and suggested command in error response
- Avoid wasting network resources on HTTP 400 from MCP gateway
Also includes:
- Add DWS_PERF_TIMING env for CLI execution timing analysis
- Add TimingCollector to track cmd_init, auth_token, mcp_call durations
Test changes:
- Add --token flag to tests that require authentication
- Add TestRuntimeRunnerRejectsUnauthenticatedRequest test case
- resolveVerbosity: use cmd.Flags() instead of PersistentFlags() to correctly
pick up inherited --verbose/--debug flags on subcommands
- FileLogger lazy binding: bind in executeInvocation since configureLogLevel
initializes it after runner construction
- logBusinessError: log MCP tool errors and business errors (HTTP 200 +
success=false) to file logger for offline diagnosis
Improve CLI error output and local logging to enable offline issue diagnosis:
- Add ServerDiagnostics struct to extract and propagate trace_id, server_error_code,
technical_detail, and server_retryable from MCP server responses
- Extract diagnostics from JSON-RPC error.data, tool call result content, and HTTP
response headers (X-Trace-Id, X-Request-Id, x-dingtalk-trace-id)
- Redesign PrintHuman with three verbosity levels (Normal/Verbose/Debug):
Normal shows trace ID + server code; Verbose adds technical detail;
Debug adds internal diagnostics (RPC code, operation, reason)
- Add PrintHumanAt for explicit verbosity control, PrintHuman defaults to Normal
- Enhance local logging with request body (sanitized), response body (on error),
retry attempts, and error classification events
- Add TruncateBody, SanitizeArguments, RedactHeaders logging utilities with
sensitive key detection via substring matching
- Fix respRetryAfter called before nil guard in doWithRetry retry loop
- Use GetBool instead of string comparison for flag resolution
Improve CLI error output and local logging to enable offline issue diagnosis:
- Add ServerDiagnostics struct to extract and propagate trace_id, server_error_code,
technical_detail, and server_retryable from MCP server responses
- Extract diagnostics from JSON-RPC error.data, tool call result content, and HTTP
response headers (X-Trace-Id, X-Request-Id, x-dingtalk-trace-id)
- Redesign PrintHuman with three verbosity levels (Normal/Verbose/Debug):
Normal shows trace ID + server code; Verbose adds technical detail;
Debug adds internal diagnostics (RPC code, operation, reason)
- Add PrintHumanAt for explicit verbosity control, PrintHuman defaults to Normal
- Enhance local logging with request body (sanitized), response body (on error),
retry attempts, and error classification events
- Add TruncateBody, SanitizeArguments, RedactHeaders logging utilities with
sensitive key detection via substring matching
- Fix respRetryAfter called before nil guard in doWithRetry retry loop
- Use GetBool instead of string comparison for flag resolution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Support schema output in table format for human-readable display
- Add product-level endpoint loading in CLI loader
- Wire schema-text rendering into runner output pipeline
- Add priority field support for todo task helper
- Build tool → endpoint mapping from CLI tools and overrides
- directRuntimeEndpoint resolves by tool name before product fallback
- Support dynamic command routing with compat layer updates
- Wire file logger into root command lifecycle (init + close)
- Pass file logger to transport client for request/response logging
- Add control char and dangerous Unicode validation on tool calls
- Strip query/fragment from endpoints to prevent parameter injection