* fix: honor global --jq/--fields on product commands; round-2 QA fixes
Make the global --jq / --fields output filters actually work for the
product (MCP) commands. The helper Formatter used by every product
command ignored them, so they were silent no-ops there (they already
worked for `dws api`). Expose Fields()/JQ() on the ToolCaller interface
and apply the existing output.WriteFiltered path in the helper
Formatter's PrintJSON. The handful of bespoke utility commands
(auth/config/profile/...) still encode directly and are documented as
such.
Additional CLI fixes surfaced by the second real-machine QA pass:
- sheet write-image: emit clean JSON under --format json (suppress the
progress lines that leaked onto stdout, same as media-upload/export)
- sheet range batch-set-style: under --format json, collect per-item
results into a single JSON object instead of printing N separate ones
- chat download-media: create the output directory when missing and
strip URL-encoded path separators from the inferred filename so the
file actually lands instead of failing on a missing subdirectory
- pat chmod, aitable, sheet, chat, attendance: correct --help text
(real scope names, non-existent subcommands, flag requiredness,
alxs -> axls typo)
Helper scripts (mono and multi):
- minutes_extract_todos: parse dingtalkTodoList/actions (there is no
todos key), so todos are no longer silently dropped
- sync the multi copies of chat_export_messages / chat_history_with_user
(were crashing with AttributeError), minutes_list_parse /
minutes_recent_summary, and calendar_free_slot_finder to the fixed
mono versions
Skill docs (mono and multi): correct return-structure keys, flag names,
deprecated command routing (doc download -> drive download), enum values
and server-side limitations across products; update the global
reference to note --jq/--fields now apply to product commands.
* fix(skill): make skill setup --dry-run a no-op preview; doc/help fixups
skill setup ignored the global --dry-run flag and always wrote the skill
files (overwriting an existing install). Short-circuit into a preview
that lists the source, target dirs and selected sub-skills without
touching the filesystem.
Also correct a few doc/help mismatches found in the round-3 health check:
- attendance vacation balance/records quick-reference examples were
missing the required --leave-code flag
- mail mailbox list --help described the returned field as "mailboxes"
but the real field is "emailAccounts"
* docs: clarify --fields projects top-level/list keys, use --jq for nested
* docs: drop QA voice ("真机") and don't state env-specific quirks as absolute rules
The QA-driven doc/comment edits leaked test-process narration ("真机实测")
and this environment/account's quirks stated as universal rules into the
skill files, which are general-purpose instructions for any org/account.
Strip the "真机" narration everywhere; reword environment-specific findings
(PUBLIC sharing disabled by org policy, transient 1002, sender-open-dingtalk-id
behaviour) from absolute bans into conditional hints; keep genuinely
universal command behaviour, just without the QA voice.
Switches the discovery version code bamboo -> cedar and aligns the open edition CLI with dws-wukong across communication (calendar book/acl/attendee, minutes tag, mail folder/template/contact, chat file upload, todo add-attachment, attendance transforms) and structured-office (aitable advperm/view/section/workflow/record, sheet/drive/wiki/doc) domains. Includes output-envelope parity, parse_bool/attendance_class_check_time transforms, --calendar-id support, CHANGELOG 1.0.43 and README command-index refresh. cedar config validated on pre and prod endpoints.
Attach the clawType tool argument to every user-identity send path
(send_personal_message text/rich-media/reply and
send_direct_message_as_user) so the IM server can render the
"Send from AI" indicator on delivered messages. The value comes from a
new edition hook (Hooks.ClawTypeValue, exposed via edition.ClawType())
that falls back to DefaultOSSClawType ("openClaw"); overlays such as
wukong set their own identity to get their branded indicator.
Also fixes the reply command, which hardcoded clawType="wukong" and
made open-source replies carry the Wukong AI identity.
Bot sends intentionally stay untouched: they already render as bot
messages and must not carry the user-identity claw tag.
* feat(pat): A-core minimum viable loop for host-owned PAT
Enables third-party agents to integrate PAT via the smallest possible
contract surface. The flow a host needs to ship end-to-end:
1. Export DINGTALK_DWS_AGENTCODE in the spawned shell.
2. Run any `dws ...` business command.
3. On PAT hit, CLI exits with code 4 and writes a single-line stderr
JSON following docs/pat/contract.md §2.
4. Host parses the JSON and invokes `dws pat chmod <scope>...
--agentCode ... --grant-type ...` to grant.
5. Host replays the original command.
This PR intentionally ships ONLY the chmod path plus the host-owned
switch; the async flow (apply / status / scopes + authRequestId
registry + PAT_SCOPE_AUTH_REQUIRED active-request branch) lands in a
follow-up stacked PR (A-ext).
Scope of A-core:
- Host-owned PAT trigger: auth.HostOwnsPATFlow() keyed exclusively on
DINGTALK_DWS_AGENTCODE (DINGTALK_AGENT / DWS_CHANNEL / claw-type do
NOT participate).
- claw-type: hard-wired to "openClaw" in pkg/edition/default.go
MergeHeaders hook, matching historical main behavior and decoupled
from DINGTALK_AGENT.
- dws pat chmod: factory-built (newChmodCommand) so the PAT subcommand
tree has no shared package-level caller.
- stderr JSON classifier: internal/errors/pat.go covers PAT_NO_PERMISSION
/ PAT_LOW_RISK / PAT_MEDIUM_RISK / PAT_HIGH_RISK / PAT_SCOPE_*
selectors and fills data.hostControl for host consumption.
- Env contract: DINGTALK_SESSION_ID / DINGTALK_TRACE_ID /
DINGTALK_MESSAGE_ID carry HTTP trace headers (Chain B);
DWS_SESSION_ID (+ REWIND_SESSION_ID alias) is the lone fallback for
`dws pat chmod --session-id` (Chain A). The two chains are
independent; aliases do not cross-pollinate.
- Docs: docs/pat/{README,contract,host-integration}.md + refreshed
docs/architecture.md "PAT Architecture" chapter + docs/reference.md
PAT section. error-catalog.md folded into contract.md §6.
- Tests: host-owned signal, stderr contract, classifier, chmod factory,
retry / poll loop for the CLI-owned chmod path.
Compatibility: no breaking changes on main. All non-PAT commands are
untouched.
Follow-up PRs (in order):
- codex/pat-ext : dws pat apply / status / scopes + AsyncRegistry.
- codex/pat-oss-refactor (aka PR B) : oauth_helpers / secure_store
refactors split out per code review.
Made-with: Cursor
* fix(pat): unify host-owned stderr contract
* revert: drop PAT docs and changelog from pat core PR
* fix(pat): pin behavior auth endpoint
* fix(pat): recognize legacy tool miss
* fix(pat): fallback on gateway diagnostics
* fix(pat): treat authorization uri as opaque
* fix(pat): accept result envelope in device-flow polling
* feat(pat): add browser policy and poll compatibility
* fix(pat): keep CLI and host PAT contracts separate
* test(pat): isolate opaque uri retry env
* fix(pat): harden PAT result routing
* fix(pat): harden host-owned flow and runtime fallback
* fix(pat): preserve chmod failure on empty grant result
* refactor(pat): drop dead doc anchors, dedupe stderr injection helpers
Address two reviewer findings on top of the PAT-core series.
Fix:
- chmod: resolveSessionIDFromEnv silently selects DWS_SESSION_ID; drop
the slog.Warn that emitted both raw session ids into stderr /
~/.dws/logs.
- Remove every docs/pat/contract.md / error-catalog.md /
host-integration.md anchor from comments, help, and tests; those
files were never added by this branch. Comments are now self-contained
or point to docs/reference.md where applicable.
Refactor:
- internal/errors/pat.go: extract lookupCodeIn so getPATErrorCode and
getDWSGatewayErrorCode share one traversal; have ClassifyPatAuthCheck
delegate to getPATErrorCode instead of repeating the enum walk.
- internal/errors/pat.go + internal/app/pat_auth_retry.go: extract
ApplyHostMutations as the single injection point for data.hostControl
+ data.openBrowser; cleanPATJSON and enrichPATErrorForHostControl now
share it so the two stderr-JSON write paths cannot drift.
- internal/app/pat_auth_retry.go: drop buildPATScopeHostJSON and
buildHostControlState dead 1-line wrappers.
- internal/pat/chmod.go: drop unused patApply/Status/Scopes constants;
derive legacyToolArgs from toolArgs by clone + scopes->scope rename so
the two payloads stay in lock-step on every other field.
Net 15 files, +176 / -193. Verification:
env -u DINGTALK_DWS_AGENTCODE go test \
./internal/errors ./internal/auth ./internal/pat ./test/unit
env -u DINGTALK_DWS_AGENTCODE go test ./internal/app \
-run 'Test(IsPat|ExtractPat|PrintPat|PollPat|HandlePat|RetryWithPat|EnrichPAT|BuildPAT|DirectRuntime|ResolveIdentityHeaders)'
env -u DINGTALK_DWS_AGENTCODE go test -race \
./internal/pat ./internal/errors ./test/unit
env -u DINGTALK_DWS_AGENTCODE go test -race ./internal/app \
-run 'Test(HandlePat|RetryWithPat|EnrichPAT|BuildPAT|ResolveIdentityHeaders|DirectRuntime)'
go vet ./... && go build ./...
All green.
Made-with: Cursor
---------
Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
* 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 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
Add SaveToken, LoadToken, and DeleteToken hook fields to edition.Hooks so overlay builds can provide custom token storage. Refactor SaveTokenData, LoadTokenData, and DeleteTokenData to delegate to hooks when present, falling back to default keychain-based storage.
- Add AuthClientID and AuthClientFromMCP fields to edition Hooks, allowing overlays to override the default OAuth client ID and route auth through MCP endpoints.
- Introduce token marker file (token.json) mechanism so the host application in embedded mode can detect authentication state without accessing the keychain.
- Update SaveTokenData/DeleteTokenData to write/remove the marker file when running in embedded mode.
- Update ClientID() and IsClientIDFromMCP() to respect edition overrides.