139 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is inspired by Keep a Changelog and this project follows Semantic Versioning.
[Unreleased]
[1.0.47] - 2026-07-05
This release adds connector supervision & health monitoring (dev connect list/status/restart/stop) and fixes bot-to-bot @-mention delivery end-to-end.
Added
dev connect list— PM2-style colored table enumerating all local connectors with state (healthy / degraded / down / not_running), PID, channel, and uptime.dev connect status— panel view with heartbeat, last recv timestamp, session webhook age, and--jsonfor external monitoring.dev connect restart— restarts a daemon via persisteddaemon-state.json(unified-app-id credential fetch, no local secret storage).dev connect stop— graceful SIGTERM shutdown releasing the single-instance lock and Stream connection.- Health watchdog — background goroutine writes
heartbeat.json;status/listderive state from heartbeat freshness + process liveness + pid-reuse detection. --alwaysonflag — opt-in auto-restart: supervisor relaunches the worker on crash (requires--daemon).--notify-staff-id— state-change notifications (start / stop / crash) sent as DingTalk messages to the specified staffId.--unified-app-idcredential flow fordev connect— fetches clientId/clientSecret at startup viadev app credentials get, keeping secrets off the command line and out ofdaemon-state.json.- API-sent file download (
feat(connect): download API-sent files via storage v2 API) — file messages sent viadws chat message send --msg-type file --dentry-id --space-idare now downloaded by the connector through the storage v2getDownloadInfoAPI (dentryId + spaceId → presigned URL → local temp file), so file-based Q&A works regardless of how the file was sent. --at-open-dingtalk-idsforchat message send-by-bot— @-mention bots or cross-org users by openDingTalkId in group messages.
Fixed
- Bot-to-bot @-mention send side —
atOpendingtalkIds(the server's lowercase spelling) is now used instead of the camelCaseatOpenDingTalkIdswhich was silently ignored. The unnecessaryopenDingTalkId → userIdreverse lookup (always failed for bots) is removed; the id is forwarded verbatim. - Bot-to-bot @-mention receive side —
interactiveCardmessages (how DingTalk delivers a bot @-mentioning another bot) are now parsed:extractInteractiveCardTextflattenscardContent[].children[].valueleaves and strips the leading @-mention by leaf boundary. Theemotion/replyreaction (which 500s on bot-sent cards) is skipped forinteractiveCardturns. - Markdown/richText body extraction —
extractCallbackTextgains acardContentfallback so structured-text messages are no longer silently dropped. - Send-by-bot @ chip rendering —
<@id>placeholders in the markdown body are rewritten to@idfor both userIds and openDingTalkIds so the mention chip renders in all cases. - Connector retry on transient network errors —
sendBySessionretries on transient failures instead of dropping the reply. - Orphan worker cleanup & watchdog deadlock — stale workers from a crashed supervisor are detected and cleaned; a channel-capacity fix prevents the watchdog from blocking.
- Idle connector false-down — heartbeat ticker now advances
updatedUnixso a connector with no inbound traffic is not marked degraded. - FD limit check —
checkFDLimitsplit into platform files for Windows cross-compilation. - Default agent timeout removed — no timeout by default (was incorrectly defaulting to a low value).
- keepAlive shortened to 30 µs — aligns with Stream SDK expectations; adds
ulimitcheck for multi-agent stability.
[1.0.46] - 2026-07-01
Fixed
- PAT agentCode grants no longer split from follow-up command checks (
internal/auth/agent_code_detect.go,internal/app/runner.go,internal/pat/chmod_test.go) — explicitDINGTALK_DWS_AGENTCODEdeclarations are now forwarded verbatim as the common cross-host contract, and unknown hosts no longer synthesizecustomintox-dingtalk-dws-agent-code/x-dws-agent-instance-id.pat chmod --agentCoderemains the highest-priority grant target and still wins over the env fallback.
[1.0.45] - 2026-06-29
This release adds multi-organization (profile) support (#500): dws can stay logged in to several DingTalk organizations at once and switch between them, while staying fully backward/forward compatible with the previous single-org token. A profile is one logged-in organization (corp); the current profile decides which org a command runs against. The release also hardens the new credential store for concurrency and corruption recovery, documents the capability in both the mono and multi skill sets, and flips --ai-tag on by default so messages sent through dws carry the DingTalk 「通过AI发送」 badge (#524).
Added
- Multi-organization login &
profilemanagement (internal/auth/profiles.go,internal/app/profile_command.go) —dws auth loginagainst a new organization adds a profile (the first login becomes the primary);dws profile listshows logged-in orgs with primary / current markers, status and validity;dws profile switch <name|corpId|->persistently switches the default org (-toggles back to the previous one, no-arg opens a TUI selector on a terminal);dws profile useis an alias ofswitch.dws auth status [--profile <name>]reports a specific profile. Credentials are stored per organization in keychain slots keyed by corpId (auth-token:<corpId>), with a plaintextprofiles.jsonregistry holding only metadata and the primary/current/previous pointers (no tokens). - Global
--profile <name|corpId>flag — run a single command against a specific organization without changing the default (one-shot; does not move currentProfile). Cross-org reads are orchestrated by the agent (list profiles → query each with--profile→ merge); there is intentionally no built-in--all-orgs. - Backward / forward compatibility with the legacy single token slot — a pre-existing single-slot token is migrated into
auth-token:<corpId>and marked primary on first multi-profile use; the current (or primary) profile's token is mirrored back into the legacy slot so older binaries and the embedded host keep working.profiles.jsonis additive and ignored by older versions. dingtalk-profileanddws-sharedskills + multi-org documentation (skills/) — a standalonedingtalk-profileskill plus a newdws-sharedskill that carries auth, global flags and the multi-org rule, so every multi-mode product skill's PREREQUISITE resolves and all read/search skills inherit cross-org behavior. The mono skill gains a "multi-org / profile" section, trigger conditions, a decision-tree entry and a corrected logout danger note. Multi-mode install now always shipsdws-sharedeven when--skill/--excludenarrows the set.
Changed
--ai-tagnow defaults on — DingTalk 「通过AI发送」 badge for dws-sent messages (internal/helpers/chat.go, #524) —chat message send/replyflip the--ai-tagdefault from false to true, attaching the AIclawTypeby default so messages sent throughdws(and by AI agents) transparently carry the 「通过AI发送」 badge; pass--ai-tag=falseto send as the user with no badge.- Concurrency-safe, self-healing
profiles.json(internal/auth/profiles.go,internal/auth/token.go) — every read-modify-write onprofiles.jsonand the legacy mirror is serialized under the existing dual-layer (process + cross-process) lock, split into public (locking) entry points and lock-free*Lockedvariants so the non-reentrant lock is never re-acquired (the refresh path and the load-path migration use the lock-free savers).profiles.jsonand the token marker are written via per-write random temp names + atomic rename so concurrent writers can no longer corrupt a fixed.tmp. An unparseableprofiles.jsonis quarantined (*.corrupt-*) and rebuilt empty so the CLI self-heals;auth reset/logoutproceed even when it cannot be read and sweep the quarantined files.
Fixed
- No silent fallback to a different org's token (
internal/auth/token.go) — when the resolved current/primary profile's keychain slot fails to read and no--profilewas given, the loader now only falls back to the legacy single slot if it belongs to the same organization; otherwise it surfaces the error instead of acting as a different org. - Legacy mirror no longer wiped on a transient keychain read error (
internal/auth/profiles.go) —SyncLegacyTokenMirrordistinguishes "token genuinely absent" from "keychain momentarily unreadable" and keeps the existing mirror in the latter case, so a host app's login state is not dropped by a transient failure.
[1.0.44] - 2026-06-28
This release hardens the dynamic-command surface and finishes the dws-wukong parity pass for structured input. Phantom override commands whose backing MCP tool isn't deployed are hidden from --help; report entry submit reads --contents-file / stdin natively; structured JSON flags accept @file / @-; and sheet range update / range read now accept the same plain shapes wukong does (scalar cells, flat values, null-clears-cell, a --hyperlinks flag). On the wukong01 sandbox this lifts the full open-edition cli_to_mcp pass rate from 77.6% to 95.5% (sheet 28.5% → 99.8%, report → 100%); the remaining failures are account / org / out-of-scope, not CLI defects.
Added
dingtalk-devskill: image-upload →mediaIdrecipe + per-resource command discovery (skills/multi/dingtalk-dev/references/) — documents how to obtain amediaIdfor app / robot icons via the DingTalk OpenAPI (credentials get→gettoken→/media/upload?type=image→--icon-media-id→ read back), since the dev command set has no upload command; and adds a "discovering commands" block to all 10 product refs pointing at each group's--helpanddws schema dev.app.<group>.<method>(dws schema dev.connectfor connect), so agents inspect commands instead of relying on memory.report entry submit --contents-file <path>/--contents -(stdin) read natively (#514,internal/compat/report_hooks.go) — the envelope publishesentry submit(MCPcreate_report) with a--contents(json_parse, required) flag plus a sibling--contents-filethat had no transform / mapsTo, so a--contents-file-only submit silently sentcontents: [null]and the report failed (only inline--contentsworked, which is whyreport createsucceeded whilereport entry submit --contents-filedid not). A build-time compat hook now resolves the file / stdin natively (10MB cap, UTF-8 check, wukong priority--contents-file>--contents -> inline) and relaxes the individualrequiredon--contentsinto acontents/contents-fileone-of group. No discovery-config change needed.@file/@-input for structured JSON flags (internal/compat/transform.go) —json_parse/json_parse_strictnow expand a leading@before parsing (@-reads stdin,@<path>reads a file), so long / complex payloads (many records, big 2D cell ranges, filter criteria) skip shell-quoting hell. A JSON / YAML value never starts with@, so the sentinel is unambiguous; the error hint that already advertised@path/to/file.jsonis now truthful.sheet's sharedsheetParseJSONFlagroutes throughcli.ResolveInputSourceso the same support reaches--values/--criteria/--sort-keys.sheet range update --hyperlinks(internal/helpers/sheet.go) — a wukong-shaped 2D hyperlink grid ([[{"type":"path","link":"...","text":"..."}]]) overlaid onto the cells grid as each cell'shyperlinkfield;--valuesor--hyperlinksis now required (at least one).
Changed
- Phantom override commands hidden from
--help(#515,internal/compat/dynamic_commands.go) — override leaves whose backing MCP tool isn't actually deployed used to render indws <svc> --helpand then fail at invocation with tool not found. A tool-existence guard now hides them, and command groups left empty by the hidden leaves are collapsed, so--helpreflects only invokable commands. Skill references are re-aligned to the real CLI surface (phantom commands dropped; role/duty "who is responsible" queries routed toaisearch, notcontact). sheet range updateaccepts scalar cells;sheet range readprojects a flatvalues;--values '[[null]]'clears a cell (internal/helpers/sheet.go,internal/helpers/sheet_cell_validation.go) — dws-wukong parity.range update(set_cell_range) auto-wraps a scalar cell (string / number / bool) into{type:text,text:"..."}instead of rejecting it, so the plain[["姓名","部门"]]shape thatsheet appendand wukong's update_range accept now works; a null cell clears content (matching wukong);{}still means keep-original.range read(get_cell_infos) now also exposes a flatvalues2D array next to the richcellspayload, matching wukong's get_range shape without dropping cell styles.- report skill aligned to
entry submit/inbox list/outbox list(skills/multi/dingtalk-report/,skills/mono/references/intent-guide.md) — the multi skill tree was two versions behind and still taught the deprecated flat aliases (report create/sent/list/detail/stats) and falsely claimedreport inboxwas unimplemented. Re-aligned to the canonical resource.verb commands consistently (old aliases still execute with a stderr deprecation notice).
[1.0.43] - 2026-06-26
This release aligns the open edition's CLI surface with dws-wukong across the communication domain (chat / mail / minutes / todo / calendar / contact / aisearch / live / report / ding) and the structured-office domain (aitable / sheet / drive / wiki / doc), and switches the discovery version code from bamboo to cedar so the aligned command tree is served from its own discovery config.
Added
calendar book get|searchandcalendar acl list(cedar discovery overrides) — query a specific calendar (primary via--id primary), fuzzy-search calendars by name, and list a calendar's access-control entries. Maps to the calendar MCPget_calendar/search_calendar/list_aclstools.calendar attendee list|add|delete(internal/helpers/calendar_commands.go) — manage event participants under the wukong-alignedattendeenaming (equivalent to the legacyparticipantgroup; callsget/add/remove_calendar_participant).minutes tag listandminutes tag query --tag-id— list a user's AI-minutes tags and query minutes by tag (query_user_tag_list/query_minutes_by_tag_id).minutes list mine|shared|all(internal/helpers/minutes_commands.go) — list own / shared / all minutes with renamed output fields.mail folder create|update|delete,mail template create|list|get|update|delete,mail contact create|list|update|batch-delete, andmail message list— full mail folder / message-template / contact CRUD plus folder-scoped message listing.chat file upload(internal/helpers/chat_file.go) — upload a local file (init/PUT/commit) or a remote URL to a conversation's file space.todo task add-attachment(internal/helpers/todo_commands.go) — attach a local file to a todo (multi-step upload).- aitable extensions (
internal/helpers/aitable_extra.go) — advanced permission / roles, view sub-commands (lock / duplicate / frozen-cols / row-height / fill-color-rule / card / timebar), section node management, workflow enable/disable, recordupsert/share-url/history-list/ primary-doc, and field search-options. Helper tools route to the hardcodedaitable-helpersupplement endpoint. - sheet, drive, wiki, doc helper coverage synced from dws-wukong (
internal/helpers/sheet.go,drive.go,wiki.go,doc.go).
Changed
- Discovery version code
bamboo→cedar(internal/market/registry.go;discoveryAPIPath = "/cli/discovery/apis/cedar") — version codes step by first letter (bamboo → cedar → …);cedarcarries the dws-wukong alignment. Older binaries keep readingbamboo, so the change is isolated to this release line. All test/mock/generator fixtures updated to the cedar path. - CLI output envelope aligned with wukong for cross-edition parity (
internal/app/runner.go,internal/compat/registry.go) — dry-run prints aDRY-RUN Arguments:line, successful results carrysuccess: true, missing-required-flag wording is unified tomissing required flag(s): --x, and OutputTransform applies to the response content layer. - New flag transforms (
internal/compat/transform.go) —parse_bool(explicit boolean strings so--flag falseis honoured) andattendance_class_check_time(HH:mm→ UTC+8 milliseconds for shift check-times). --calendar-idaccepted on calendar event / participant / room / attachment commands so calendars other than the primary can be targeted.
Fixed
- Client-side validation for calendar recurrence completeness and attendance schedule / class / group inputs, surfacing input errors before they reach the server.
[1.0.42] - 2026-06-25
This release rounds out dws dev connect — bridge a DingTalk robot to your local AI (Claude Code / Codex / opencode / Qoder / …): a generic custom channel for any headless CLI tool, in-chat /new / /clear session commands aligned to each agent's real session op, and a fix for long opencode turns being cut at 30 seconds.
Added
dws devapp robot connect— genericcustomchannel for self-built / unsupported AI tools (issue #37;internal/helpers/devapp_connect.go,internal/helpers/connect_stream.go) — a new--agent-cmd "<command>"flag (andcustomchannel) lets the bot forward to any headless AI CLI that takes a question as its trailing argument and prints the answer to stdout, so tools that aren't built-in (e.g. 网易有道龙虾 LobsterAI) or self-built agents can be onboarded without code changes.--agent-cmdforces thecustomchannel unless--channelis set explicitly; detection also falls back tocustomwhenDWS_AGENT_CMDis present.
Changed
-
robot connectnow hints how to match terminal answer quality (issue #39;internal/helpers/devapp_connect.go) — when neither a work dir nor a knowledge source is configured, the connector prints a one-time note that the bot runs in a clean temp dir without local project context, pointing at--agent-workdir/--knowledge-dir/--knowledge-source/--agent-model. The robot quickstart gains matching FAQ entries, plus a clarification that step 3 (robot connect) produces no approval ticket (issue #19). -
robot connectsession commands/newvs/clearnow use each channel's real session op (PR #20;internal/helpers/connect_opencode.go,internal/helpers/connect_stream.go) —/new(and/start,/reset) opens a fresh session and leaves the previous one intact (resumable where the agent supports it);/clearactively disposes the current session through the agent's real delete primitive — opencode issuesDELETE /session/:id. Channels whose agent exposes no delete in the mode DWS drives it (Codex app-server, Qoder stream, Claude-family exec) fall back to a reset, so/clearbehaves like/newthere. Previously both commands only dropped the localconversationId → sessionIdmapping, so the two were indistinguishable and opencode sessions were never disposed (they leaked).
Fixed
robot connectno longer aborts long opencode turns at 30 seconds (PR #19;internal/helpers/connect_opencode.go) — the shared opencode HTTP client hard-coded a 30sTimeoutthat covered every request, includingPOST /session/{id}/message, so a long agent turn (e.g. a multi-minute research report) was killed mid-flight withcontext deadline exceeded (Client.Timeout exceeded while awaiting headers)even though the per-turn budget (DWS_AGENT_TIMEOUT_MS, default 300s) was far larger. The client-level deadline is removed so the per-request ctx governs the round-trip; only the/global/healthprobe keeps a short 10s timeout so startup detection stays snappy.
[1.0.41] - 2026-06-24
This release makes the installers work from mainland China out of the box (no env var) and keeps the Gitee mirror in sync automatically.
Added
- Auto-fallback to the Gitee mirror when GitHub is unreachable (#492;
scripts/install.sh,scripts/install.ps1,scripts/install-skills.sh) — the installers probe GitHub Releases on startup and, when it is unreachable (typical in mainland China), automatically resolve the version and download every asset (binary,checksums.txt,dws-skills.zip) from the Gitee mirror instead. A plaincurl … | shnow works in China with noDWS_GITEE_REPOneeded. ExplicitDWS_GITEE_REPOstill wins,DWS_NO_FALLBACK=1forces GitHub, and local source-checkout installs skip the probe.
Changed
- CI mirrors repo code to Gitee automatically (#493;
.github/workflows/mirror-to-gitee.yml) — the mirror workflow now pushesmain+ tags to the Gitee mirror over HTTPS usingGITEE_TOKEN(no SSH key), on every push tomainand every tag, keeping the Giteeraw/maininstall scripts and tags in sync without any manualgit push. Gated onGITEE_TOKEN; skips cleanly when unset.
[1.0.40] - 2026-06-24
This release adds China-accessible install mirrors so the CLI installs reliably from mainland China, where GitHub raw + Releases are slow or fail.
Added
- China mirror via Gitee + npmmirror (#486;
scripts/install.sh,scripts/install.ps1,scripts/install-skills.sh,scripts/release/sync-to-gitee.sh,.github/workflows/release.yml,.github/workflows/mirror-to-gitee.yml) — an opt-inDWS_GITEE_REPOenv var makes all three installers resolve the latest version and every release asset (binary,checksums.txt,dws-skills.zip) from the Gitee OpenAPI v5 instead of GitHub; with it unset, installation defaults to GitHub (fully backward compatible). The release pipeline mirrors release attachments to the matching Gitee release after each tag (gated onGITEE_TOKEN/GITEE_REPO), and a hub-mirror workflow keeps the repo code in sync (gated onGITEE_PRIVATE_KEY). README documents three China install channels: Gitee raw script, Gitee release binaries, and the npm package viaregistry.npmmirror.com. - Skills embedded in the binary (#488;
skills_embed.go,internal/app/skill_setup.go,internal/app/skill_setup_embed.go) — theskills/tree (mono + multi) is embedded into thedwsbinary viago:embedanddws skill setupdefaults to the embedded copy, refreshing the installed skill instead of silently reusing a stale copy probed from the current working directory — so skills install offline with no separate download.
[1.0.39] - 2026-06-18
This release makes the AI-sent indicator opt-in. 1.0.38 unconditionally tagged every user-identity send/reply with the edition claw identity, so the IM server rendered a "Send from AI" badge under every message — and on the open edition a stale hardcoded value even leaked the Wukong-branded label (「悟空AI发送」) to external users. The badge is now off by default and shown only when the caller explicitly asks for it.
Added
--ai-tagopt-in flag forchat message send/chat message reply(#477;internal/helpers/chat.go) — by default noclawTypetool argument is attached, so delivered messages carry no "Send from AI" badge. Passing--ai-tagattachesedition.ClawType()so the IM server renders the badge (open editionopenClaw→ 「通过AI发送」; the wukong overlay sets its own value → 「悟空AI发送」). Covers the text/Markdown, rich-media, and--user/--open-dingtalk-iddirect send paths plusreply. Bot (send-by-bot) and webhook sends are intentionally untouched — they already render as bot messages. The badge is opt-in so dws does not brand every message a user sends.
Fixed
dws chat message replyno longer leaks the Wukong AI label on the open edition (#475, fixes #474;internal/helpers/chat.go,pkg/edition/edition.go) — the reply path hardcodedclawType: "wukong", so open-source quoted replies were tagged 「悟空AI发送」 by the IM server, leaking Wukong branding to external users (reported by an external customer integrating via openclaw). The value now derives from the edition via the newedition.ClawType()accessor (open →DefaultOSSClawType=openClaw), and — together with #477 — is only attached when--ai-tagis passed. The earlier fix existed on a branch (PR #450) but was never merged to main; #475 cherry-picked it.
[1.0.38] - 2026-06-16
This release adds client-side agent attribution for usage stats, fixes two commands that silently misbehaved (dws sheet export hanging, dws upgrade --dry-run actually upgrading), hardens the document write path against server-rejected characters, and makes the long-broken --no-browser login flag actually work.
Added
- Client-side
agent_codedetection + per-channel agent instance id for usage stats (#467;internal/auth/agent_code_detect.go,internal/auth/identity.go,docs/agent-code.md) — every MCP request now carriesx-dingtalk-dws-agent-code(which agent host is driving dws — e.g.claudecode/codex/qoder/cursor/hermes/openclaw, falling back tocustom),x-dws-agent-instance-id(a per-machine×channel id,dwsa_<base62(sha256(machineId|agent_code))>), the existing machine-levelx-dws-agent-id, andX-Cli-Version. Detection is a confidence ladder, each signature verified on real hosts / official docs (never guessed; anything unrecognized resolves tocustom): T0 explicitDINGTALK_DWS_AGENTCODE, T1 per-agent env signatures, T2VSCODE_BRANDcovering the whole VS Code fork family, T3 the macOS__CFBundleIdentifiermap, T4custom.identity.jsonmigrates v1 → v2 transparently and keepsx-dws-agent-idmachine-level for continuity. Trust boundary:agent_codeand both ids are client self-reported and forgeable — they are for stats / observability only and must not be used for auth, authorization, rate-limiting, billing, or revocation. Server-side gateway work (header passthrough allowlist + logging the fields into the warehouse) is required before the data lands and is tracked separately.
Fixed
dws sheet exportno longer hangs for the full ~5-minute poll timeout (#462;internal/compat/pipeline.go) — the pipeline poll loop compared the API status againstpollUntilValuewith case-sensitive==, but the API returns"success"while the pipeline config declares"SUCCESS", so the match never fired and the loop spun until timeout. Switched tostrings.EqualFold, aligning with the case-insensitivenormalizeAsyncStatushelper already used fordoc export/aitable export.dws upgrade --dry-runnow previews instead of performing a real upgrade (#416, fixes #364;internal/app/upgrade.go) —newUpgradeCommandregistered no--dry-runflag and never read the global persistent one, so--dry-runfell through and ran a real, irreversible upgrade (download + binary replace), directly contradicting the flag's documented预览操作内容,不实际执行contract. It now resolves the target release and platform asset (so "already latest" / "no build for this platform" is still surfaced), prints the 1–5 steps it would perform via the side-effect-freewriteDryRunPlan, and returns before any backup / download / replace. Covered byTestWriteDryRunPlan_*and an updated help test.dws doc create/dws doc updatestrip server-rejected characters instead of failing (#465;internal/helpers/doc.go,internal/helpers/doc_jsonml.go) — the Markdown write path sent raw content straight through, and the dangerous-Unicode strip only ran on the JSONML branch, so content carrying C0 control characters (anything< 0x20except\t/\n), DEL (0x7F), or zero-width / line-separator codepoints (U+200D,U+2028,U+2029) — common in LLM-generated or copy-pasted text — was rejected by the server-sideRejectControlCharsvalidator and the command failed.stripDocDangerousUnicodeis renamed tostripDocInputUnsafe, extended to match the authoritativeapiclient.rejectDangerousCharsset, and applied on both the Markdown and JSONML node write paths. Tab and newline are preserved. Ported from dws-wukong.dws auth login --no-browseris now honored (#365;internal/app/auth_command.go,internal/auth/device_flow.go,internal/auth/oauth_provider.go) — the flag was already defined (and hidden) but never wired to the login providers, so the browser always opened regardless. The value is now passed intoDeviceFlowProvider.NoBrowser/OAuthProvider.NoBrowserand gates theopenBrowsercall; the flag is also unhidden so headless / remote sessions can discover it.
[1.0.37] - 2026-06-11
This release realigns the npm channel and hardens PAT batch grants. Background on the npm realignment: 1.0.36 was re-cut on GitHub on 2026-06-11 to fold in the canonical-tree poisoned-cache guard (#454), but the npm registry permanently forbids republishing a version number, so the npm package stayed on the original, unguarded cut. 1.0.37 is therefore the first version where every distribution channel — GitHub releases, dws upgrade, the install scripts, and npm — ships the same guarded build. If you installed 1.0.36 from npm, upgrade to this version.
Fixed
- PAT batch grants carry the agent identity and require explicit confirmation (#455;
internal/pat/chmod.go,internal/auth/channel.go,internal/app/runner.go) — an explicit--agentCodeflag or theDINGTALK_DWS_AGENTCODEenv var is now carried into PAT batch plan/grant arguments instead of being dropped, and a missing agentCode is forwarded as absent so the PAT core can apply the server-side default rather than failing. Batch grants now refuse to execute without an explicit--yes(dry-run and single-scope grants keep their existing behavior), closing the gap where a multi-scope grant could fire without a deliberate confirmation. Only the canonical env nameDINGTALK_DWS_AGENTCODEis recognized; draft/reversed spellings from earlier iterations are ignored. Verified against prepub: dry-run, single grant, flag-priority grant, and batch grant all resolve the target agentCode, with the granted rows confirmed server-side. Tests:internal/pat/chmod_test.go,internal/pat/browser_policy_test.go,test/unit/pat_host_owned_signal_test.go.
[1.0.36] - 2026-06-10
This release closes out the poisoned-discovery-cache lock-out for good, with four layers of defense landing together. The lock-out class (seen again on 2026-06-09 as chat_permission_grant flag redefined: params): the dynamic command tree is built from cached discovery data before Cobra dispatches any command, so a pflag panic fed by a poisoned cache aborted every invocation — including dws cache refresh and dws upgrade, the very commands that could repair it. Now: (1) any panic during the build is recovered instead of crashing (#447), (2) the four known envelope shapes that made pflag panic are skipped at registration so they never fire (#449), (3) when an unknown panic class does fire, the CLI quarantines the poisoned cache and rebuilds itself from a fresh fetch — and dws upgrade clears the discovery caches after every binary swap, so simply getting this version onto a machine is enough to escape, no manual cache surgery (#452), and (4) the same guards now also cover the canonical dws mcp tree, which is built even earlier and sat outside all three defenses as originally cut (#454 — this release was re-cut on 2026-06-11 to include it; verified against the preserved real poisoned cache from the 2026-05-25 incident). Also in this release: dws devdoc gains RAG-backed Open Platform doc search and a new error-diagnosis command (#434), and dws doc create stops producing documents with two identical titles (#448).
Escaping a locked-out older binary: a binary ≤1.0.35 bricked by a poisoned cache cannot run dws upgrade. Either bypass the cache for one invocation with DWS_CACHE_DIR=$(mktemp -d) dws upgrade, or delete ~/.dws/cache/<partition>/tools/ by hand, or reinstall via the install script. Once 1.0.36 is on the machine this never needs doing again.
Added
dws devdoc— RAG-backed Open Platform doc search and error diagnosis (#434;internal/helpers/devdoc.go,internal/transport/client.go) —dws devdoc article searchnow routes to the upstreamsearch_open_platform_docs_ragtool, returning structured RAG/reference payloads (the CLI stays a thin invoker; no extra AI analysis layer). Newdws devdoc error diagnose(aliastroubleshoot) routes tosearch_open_error_code_ragfor diagnosing DingTalk Open Platform API errors, with--request-id(hidden--trace-idkept for compatibility),--error-code,--error-message,--api,--context,--query,--page,--size. Transport-side: query parameters required by DingTalk MCP gateway URLs are preserved on the wire but their values are redacted from debug logs. Default MCP / skill hosts stay on productionhttps://mcp.dingtalk.com(prepub remains runtime-configurable). Skill docs (mono + multidingtalk-devdoc) anddocs/command-index.mdupdated alongside.
Fixed
- CLI no longer bricks when the dynamic command build panics — degrades to built-in commands (#447;
internal/app/legacy.go) —buildEnvelopeCommandsSafewraps the envelope-driven build in a localrecover(). On panic the CLI logs it, prints a stderr hint, and falls back to the hardcoded helper commands, soauth/cache/doctor/version/upgradeand the helpers stay alive anddws cache refreshcan rebuild the poisoned cache. Before this, the only recovery from the pre-1.0.32 lock-out class was manually deleting cache files; the duplicate-flag class itself had been fixed at the builder level, but any future panic class in the cache-driven build would have bricked the CLI again. Tests:TestNewLegacyPublicCommandsPanicFallsBackToHelpers,TestNewLegacyPublicCommandsNoPanicKeepsDynamicPath. - Envelope-driven flag registration no longer panics on the four known malformed-envelope shapes (#449;
internal/compat/registry.go) — while reproducing the lock-out byte-for-byte, four envelope shapes were found still forwarded to pflag calls that panic, each bricking every invocation: a flag namedparams/jsoncolliding with the reserved payload flags (the originalflag redefined: params— earlier dedup fixes covered the alias list and Detail-schema path but not the primary name); two bindings resolving to the same long flag name across bindings; two flags claiming the same shorthand; and a multi-character shorthand. Two small guards applied at every registration site (ApplyBindings,registerPositionalAliasFlags):canRegisterFlagskips duplicate/reserved long names (the value stays reachable via--params), andsafeShorthanddrops an invalid or already-taken shorthand while keeping the long flag. The trailing--json/--paramsregistration is now idempotent. Defense in depth with #447: the escape hatch should never trigger for these known vectors. Test:TestBuildDynamicCommandsSurvivesMalformedFlagEnvelope(5 table-driven vectors). - Poisoned discovery cache now self-heals: quarantine + rebuild on panic, and
dws upgradeclears discovery caches (#452;internal/app/legacy.go,internal/app/upgrade.go,internal/cache/store.go) — #447's recovery is upgraded from "degrade and ask the user to rundws cache refresh" to a two-stage self-heal: on the first build panic the partition's discovery cache is moved aside to<partition>.quarantined(kept on disk for inspection; a previous quarantine is replaced so nothing accumulates — newStore.QuarantinePartition) and the build retried once against a fresh fetch. If the retry succeeds the user gets the full dynamic command tree with zero manual steps; only a second panic (remote envelope itself still poisoned, or offline) degrades to helper commands with thecache refreshhint. Additionallydws upgradepurges discovery-derived caches (market/tools/detailacross all partitions — newStore.PurgeDiscoveryData) after a successful binary swap, leaving the co-locateddownloads/cache untouched, so an upgraded binary always rebuilds its command tree from fresh data instead of inheriting snapshots written by the old version. Tests:internal/cache/store_quarantine_test.go, rewritteninternal/app/legacy_panic_fallback_test.go(self-heal success, double-panic degradation, no-cache no-op, happy path). - Canonical
dws mcptree no longer escapes the poisoned-cache guards (#454;internal/cli/canonical.go,internal/app/root.go) — the canonical tree is assembled from cached catalog data before the legacy command build, so a pflag panic there — a tool schema property named after the reserved--paramsflag, exactly what the 2026-05-25 incident cache contained — bypassed #447/#449/#452 entirely and still bricked every invocation, including on this release as originally cut. Two layers, mirroring the existing guards:applyFlagSpecsskips reserved (--json/--params), duplicate, and alias-colliding flag names and sanitizes shorthands (canRegisterToolFlag/safeToolShorthand; a skipped property stays reachable through the reserved JSON payload flags), andnewMCPCommandwraps the build in the #452 recover → quarantine → retry-once → degrade-to-stub sequence. Verified against the preserved real poisoned cache: the original cut locks out on--version/cache refresh/doctor; this build self-heals on first run andcache refreshclears the poison. Tests:internal/cli/canonical_flag_guard_test.go(4 cases),internal/app/canonical_panic_fallback_test.go(4 cases mirroring the legacy fallback suite). dws doc createno longer produces a document with two identical headings (#448;internal/helpers/doc.go) — the platform renders the document name as the page title, and LLM agents habitually repeat# <title>as the markdown body's first line despite the skill docs saying not to, so duplicate-heading documents kept appearing. Thedoc createhelper (which wins the envelope merge viapreferLegacyLeaf) now strips a leading ATX H1 whose text exactly equals--name(trimmed, case-insensitive) before forwarding tocreate_document, printing a stderr note so agents learn the convention. Deliberately conservative: only an exact match is removed (# 背景stays), ATX closing hashes are handled without over-trimming names ending in#(e.g.C#), H2+/setext headings are never touched, and a body that is nothing but the duplicate H1 omits themarkdownparam instead of sending an empty string. JSONML bodies are out of scope. Tests:TestStripLeadingDuplicateTitleHeading(9 cases) plus three end-to-end cobra tests asserting the exactmarkdownparam sent.
[1.0.35] - 2026-06-08
Fixed
chat message send@-mentions not rendered in group / direct chat (#433,internal/helpers/chat.go) — when sending a group message or an openDingTalkId direct message (send_personal_message) as the current user, thecontentbody was packed withjson.Marshal, whose default HTML escaping turns the<>in<@openDingTalkId>/<@all>into<>. The DingTalk client renders @-mentions by matching the literal<@...>token, so after escaping the match fails and the mention shows as plain text — while the API still returnssuccess, masking the bug. Fix: addmarshalMessageContent, which serializes{title,text}withjson.Encoder+SetEscapeHTML(false); both the group and openDingTalkId-directsend_personal_messagepaths now use it, preserving the literal<@...>. Added regression testTestChatMessageSendContentNotHTMLEscapedasserting the content keeps the literal token and is never HTML-escaped. Verified on a real device:@someoneand@allboth render as clickable blue mentions.chatskill docs & scripts aligned to direct-chatlist-direct(#424) —chat message listnow supports group chats only (--user/--open-dingtalk-idremoved); reading a direct chat moves to the dedicatedlist-directcommand, but the skill docs and scripts still taughtchat message list --user, which now errors withunknown flag: --user, also breakingchat_history_with_user.py(listed as the "preferred" way to query direct chats). This update:skills/{mono,multi/dingtalk-chat}/references/products/chat.mdswitchesmessage listto group-only and documents the newlist-directcommand, syncing the intent routing / key-distinction / context-passing tables / caveats;skills/mono/references/best_practices/01-messaging.mdchanges query-private-chat fromlist --usertolist-direct(the multi version was already updated);chat_history_with_user.py(mono + multi) now callslist-directand fixes response parsing (unwrapsresult.messages, alignscreateTime/content/senderfields — it previously crashed on'str' object has no attribute 'get'). Direct-chat sending still useschat message send --user(since v1.0.34 the direct-send rpc is folded into thesendcommand; there is no separatesend-direct). Docs/scripts only; no change to CLI binary behavior.pat chmodbatch authorization did not pass throughagentCode(#414,internal/pat/chmod.go) — the batch plan / grant paths (buildBatchPlanArgs/batchArgs) previously carriedagentCodeonly in the single-granttoolArgs; batch calls omitted it, so a batch authorization with an explicitagentCodewas processed under the default agent. Fix: the batch plan / grant args now also carryagentCode, matching the single-grant path.patJSON output escaped the authorization URL into an unreadable form (#401,internal/pat) — the authorization URL attached to PAT error messages, after default HTML escaping, turned&into&, breaking the link when copied / recognized on mobile. Fix: the PAT error-enrichment JSON output now usesSetEscapeHTML(false)(scoped to PAT JSON only), preserving the readable&separators.
[1.0.34] - 2026-06-03
Changed
- Service discovery path now carries a version-coded segment (
internal/market/registry.go) — the server-list endpoint moves from/cli/discovery/apisto/cli/discovery/apis/bamboo. The path is now a singlediscoveryAPIPathconstant so future version bumps touch one place. Only the path changes; the MCP base host stays on productionhttps://mcp.dingtalk.comand the auth / skill / doctor endpoints are untouched. Discovery via the editionDiscoveryURLhook (full-URLFetchServersFromURL) is unaffected. Server side must serve the new path.
Removed
dws aiapp— AI application product taken offline — removed theaiappproduct surface (create/query/modify) from the CLI: deletedinternal/helpers/aiapp.go, dropped it from the generator coverage targets andknownRegistryProducts, removed theaiappskill references (monoreferences/products/aiapp.md+dingtalk-aiappmulti skill), and unpublished theaiappserver from the service-discovery envelope. Product count drops from 19 to 18.
[1.0.33] - 2026-06-02
This release merges the multi-contributor pre-mcp-discovery feature branch into main as a single squash (#391), bringing a large batch of new product surface — full DingTalk docs (doc), knowledge base (wiki), AI app (aiapp), AI-table forms + import/export, and reworked mail / todo / report command trees — while keeping service discovery pinned to production https://mcp.dingtalk.com (the branch's pre-mcp.dingtalk.com endpoint change was deliberately excluded; the four host constants in skill_command.go / auth/endpoints.go / cli/loader.go / market/registry.go stay on prod). It also folds in the portable auth bundle (dws auth export / import, #357) and PAT batch authorization (#389).
Added
dws doc— full DingTalk document command family (#387, #362, #388, #390;internal/helpers/doc.go,internal/helpers/doc_jsonml.go,internal/helpers/docjsonml/) — search / list / info / read / create / update / upload / download / copy / move / rename, plusfile,folder,block-level editing andcomment(list / create / reply / create-inline). Authoring supports both DocxXML and a JSONML format with a v2 schema validator (docjsonml/jsonml-schema-v2.json+doc_jsonml_validate_v2.go). Document export and OA alignment land here.dws wiki— knowledge base management (internal/helpers/wiki.go,internal/helpers/wiki_proxy.go) — knowledge spacecreate/get/list/searchand memberadd/list/update, routed through a wiki proxy server.dws aiapp— AI application lifecycle (internal/helpers/aiapp.go) —create(with prompt / attachments / skills),queryby task ID,modifyby thread ID.dws aitableforms + import/export (internal/helpers/aitable_form.go,internal/helpers/aitable_export_import.go) — datasheet form management and full record import/export, the latter driven through the async-task helper for large datasets.- Reworked
chat/report/todo/contact/mailcommand trees aligned to the Wukong baseline (#355;internal/compat/mail_hooks.go,internal/compat/todo_hooks.go,internal/helpers/report_readable.go) — mail and todo gain dedicated compat hooks;reportgains a human-readable rendering path alongside the raw JSON, plus deprecation shims for the old report shape. dws auth export/dws auth import(#357) — portable auth bundle for migrating Linux sandbox credentials. Exports the encrypted keychain (~/.local/share/dws-cli, includingauth-token.encanddek) plus required~/.dwsconfig so refresh tokens survive import; copying onlyapp.jsonleaves access tokens expiring after ~2 hours. Supports-o/-itar.gz paths and--base64for copy/paste between sandboxes.dws auth statusnow shows refresh-token validity in table output.- Async-task and paging infrastructure (
pkg/asynctask/,pkg/paging/) — shared helpers underpinning long-running operations (e.g. aitable import/export, doc export) and cursor/page traversal.
Changed
envelopenow registerscli.Aliasesas cobra aliases (#391) — discovery-generated commands expose their declared aliases natively in the command tree, with accompanying command-structure and JSON-parsing cleanups.- Breaking:
dws pat chmodprints a compact authorization summary by default, and gains batch authorization flows (#389;internal/pat/chmod.go) — scripts that parse the raw MCP JSON from stdout must now pass--format jsonor--verboseto keep the machine-readable payload; the default summary keeps grant status, agentCode, grantType, scope counts, and a next-action hint. New batch grant/plan flows (pat.batch_grant/pat.batch_plan) authorize multiple products in one session, fall back to the legacy single-grant path when the server reportsPAT_BATCH_AUTH_UNSUPPORTED, use the server's defaultagentCodewhen none is given, and surface per-tool authorization metadata for grant planning. - Skill packs synced to the Wukong-aligned content across attendance / calendar / minutes / oa / sheet and others (#391).
[1.0.32] - 2026-05-25
Two user-visible regressions resolved plus two AI-agent discoverability fixes. dws drive upload was returning HTTP 403 SignatureDoesNotMatch for any file whose MIME detects to a non-empty value — basically every real file — because the helper added a client-side Content-Type fallback whenever drive.get_upload_info returned an empty headers map. DingTalk drive's OSS presigned PUT URLs are signed against an empty Content-Type at signing time, so any client-supplied header makes the signature OSS recomputes diverge from the server-signed one, and the PUT is rejected (#347). On Apple Silicon, dws upgrade was aborting at the "解压并验证" step with signal: killed because GoReleaser cross-compiles darwin/arm64 binaries on ubuntu-latest with no codesign step, and macOS 11+ amfid SIGKILLs unsigned arm64 binaries on first exec (#339) — the release pipeline now ad-hoc signs every darwin tarball, and the upgrade client self-heals if it ever encounters an unsigned binary again. On the AI-agent discoverability side, dws aitable attachment upload-file (the one-shot prepare + PUT + commit composite) is no longer hidden from --help — agents that only browse the command tree were getting stuck at the prepare-only attachment upload step, which returns an upload URL + fileToken but doesn't actually upload. And dws --help itself now surfaces the missing-command upgrade hint that the custom renderRootHelp had been silently dropping from cobra's root.Long.
Added
dws aitable attachment upload-fileis now visible indws aitable attachment --help(#347,internal/helpers/aitable.go) — the hardcoded one-shot composite (prepare + HTTP PUT + commit, returnsfileTokendirectly) was previously markedHidden:trueand only reachable by agents that readskills/references/products/aitable.md. Agents that only discover commands via--helpwere getting stuck at the sibling envelope-generatedattachment upload(prepare-only): they'd receiveuploadUrl+fileToken, have no idea how to consume the URL, and either write the URL into the attachment field as if it were a token (wrong shape — the field expects[{"fileToken":"ft_xxx"}]) or fall back to "please use the UI" messages, which madedwslook broken even though the capability was fully implemented. Unhiding mirrors the discoverability patternlark-cli base +record-upload-attachmentalready follows.Shortis tightened to explicitly mention the 3 steps it bundles;Longcalls out the prepare-only sibling and recommendsupload-fileas the default for AI agents. The siblingattachment upload(prepare-only) keeps its envelope-generated registration but gets a newLongthat states it is only step 1 of a 3-step flow, lists what an agent must do after (HTTP PUT touploadUrl, then write[{"fileToken":"ft_xxx"}]into the attachment field), and points toupload-fileas the recommended one-shot alternative.TestAITableUploadFileCommandIsDiscoverableininternal/helpers/aitable_upload_file_test.goguards against re-introducingHidden:true.dws --helproot output now surfaces thedws upgradehint when no listed command fits (#347,internal/app/root.go+internal/app/root_help.go) —root.Longis set to"提示: 如果遇到能力缺失、命令报错、新功能未注册、或无法完成任务, 请先用 'dws upgrade' 升级到最新版本后再试. 钉钉 OpenAPI 和 dws CLI 持续迭代, 新能力和 bugfix 会先在新版本上线.". The customrenderRootHelp(which replaces cobra's default template to render the services / utilities sections) had been silently droppingroot.Long; restoring it costs oneFprintlnafter the command list, separated by a blank line. The natural failure mode for both agents and users staring atdws --helpis to give up or hack around when none of the listed commands fit — but in many cases the right action is simplydws upgrade, because new capabilities and bugfixes ship continuously and a missing command is usually a stale-binary issue.TestRenderRootHelpIncludesLongininternal/app/visibility_test.gouses a sentinelLongstring and asserts the rendered output contains it verbatim, so any future rewrite of the help renderer that dropsLongfails this test immediately.
Fixed
dws drive uploadno longer fails withHTTP 403 SignatureDoesNotMatchon any non-empty MIME type (#347,internal/helpers/drive.go) —httpPutDriveFilewas settingreq.Header["Content-Type"] = fallbackMIMEwhenever the prepare_upload response returned an empty headers map. DingTalk drive's OSS presigned URLs signStringToSignagainst an emptyContent-Typeat signing time, so any client-side header makes the signature OSS recomputes at PUT time differ from the server's presignature, and the upload is rejected with403 SignatureDoesNotMatch. This broke everydws drive uploadfor any file whose MIME detects to a non-empty value (image/png,application/pdf, every common binary) — i.e. essentially every real upload. Fix: drop thehasContentType/fallbackMIMEpath entirely, trust the server's headers map as authoritative; empty map means "no client-side headers needed", do not infer.httpPutDriveFile's signature loses thefallbackMIMEparameter. Manual verification:curl -X PUT -H "Content-Type:" --data-binary @file <same-presigned-url>returnsHTTP 200, proving the only difference was the client-sideContent-Type.TestHttpPutDriveFile_NoContentTypeWhenServerHeadersEmptyguards the empty-map path;TestHttpPutDriveFile_PassthroughServerHeadersguards that server-providedContent-Type/x-oss-*headers are forwarded verbatim. Important:internal/helpers/aitable.go'supload-filehelper deliberately keeps itsSet("Content-Type", mimeType)call — its OSS endpoint uses a different signing mode (server includes the client-declared MIME in the signature, verified across 12 file types — all succeed). The two helpers must not be unified without re-validating both endpoints.dws upgradeno longer dies withsignal: killedon Apple Silicon after fetching the new binary (#339) — GoReleaser cross-compilesdarwin/arm64binaries onubuntu-latestwith no codesign step, and macOS 11+ on Apple Silicon requires at least an ad-hoc signature on every arm64 binary;amfidSIGKILLs unsigned arm64 binaries on first exec, which the upgrade client surfaces assignal: killedand aborts at the "解压并验证" step. Two layers of fix:- Release-side ad-hoc signing (
scripts/release/post-goreleaser.sh+.github/workflows/release.yml) — after GoReleaser produces the per-platform tarballs,post-goreleaser.shunpacks eachdws-darwin-*.tar.gz, applies an ad-hoc signature (codesign --force --sign -locally,rcodesignin CI), deterministically repacks the tarball, and rewrites the matching line inchecksums.txtso the checksum stays consistent with the resigned tarball.release.ymlinstallsrcodesign 0.27.0before GoReleaser runs. Every 1.0.32+ tarball ships signed; the install regression is fixed at the source. - Client-side self-heal in
validateNewBinary(internal/app/upgrade.go) — when running the freshly-extracted binary returnssignal: killedon darwin, the validator retries once after runningcodesign --force --sign -on the binary and clearing thecom.apple.quarantinexattr. This keepsdws upgradeworking even if a future release ever skips the signing step again, and covers users upgrading from older unsigned binaries.internal/app/upgrade_test.go(+80 lines) covers the retry path end-to-end: a stripped binary exits 137 on first exec,validateNewBinaryrecovers via ad-hoc sign + xattr clear, the final binary showsSignature=adhocand runs.
- Release-side ad-hoc signing (
[1.0.31] - 2026-05-21
Closes the last drive-surface gap with the Wukong edition: dws drive upload lands as a single-shot composite (drive.get_upload_info → HTTP PUT to OSS → drive.commit_upload) so a local file reaches DingTalk drive in one CLI invocation, no manual three-step orchestration. Two more drive commands — dws drive list-spaces (list visible drive spaces) and dws drive delete (delete a drive file, routed via serverOverride to the doc MCP server) — ship via the portal envelope; dws cache refresh once to pick them up. Companion skill docs teach the agent to recognise dingpan URLs of the form alidocs.dingtalk.com/document/edit?dentryKey=… / …/document/preview?dentryKey=… and pass the whole URL through to --node instead of trying to extract dentryKey by hand (the server interprets dentryKey and a bare nodeId differently — manual extraction was failing).
Added
-
dws drive upload --file <path> [--folder <dentryUuid>] [--space-id <id>] [--file-name <name>] [--mime-type <type>](#335, seeinternal/helpers/drive.go) — composite leaf that runs the full three-step upload internally:drive.get_upload_info— fetch the OSS-signedresourceUrl+uploadId+ per-URL headers.- HTTP
PUTthe file binary to OSS (10-minute timeout, attaches every header returned by step 1). drive.commit_upload— register the new file under the target space / folder.
--dry-runprints the three step invocations as a single JSON payload without making any network calls.--file -is rejected on purpose: this is a local-path upload, not stdin streaming.--folderonly accepts adentryUuid; pure-numeric values are rejected up front (validateDriveParentID) so callers don't accidentally pass a chat-linkdentryId(a different ID namespace) where the drive API expects adentryUuid. Response normalisation handles all the wrapper shapes the upstream returns —content/resultenvelopes,resourceUrls[]arrays, and the flatresourceUrl/uploadUrlfallbacks — so the composite produces a stable JSON shape regardless of which path the upstream takes. The helper only registersupload; the existing six envelope-generated leaves (list/info/download/mkdir/upload-info/commit) keep flowing through dynamic discovery unchanged.pickCommands.MergeHardcodedLeavesguarantees dynamic leaves win on collision, so this helper only fills the upload gap. -
dws drive list-spacesanddws drive delete(envelope rollout) (#335, ships via portal envelope) —list_spacesregisters as a plaincliNamealias on the existing drive MCP server;delete_documentregisters withserverOverride: docso the call routes to the doc MCP server (which owns the delete API), surfacing under the drive command tree for ergonomics. Existing users must rundws cache refreshonce to pick up these two new leaves; no binary upgrade is required for them, but they pair naturally with the v1.0.31 client that shipsupload. -
skills/references/url-patterns.md(#335) — single authority for dispatchingalidocs.dingtalk.comURLs across doc / sheet / wiki. Five-way split:/i/p/<token>short links → expand viadoc info;/i/nodes/<id>node URLs → probe withdoc infoand route bycontentType/extension/nodeType;/spreadsheetv2/...→sheet;/document/edit|preview?dentryKey=<key>(dingpan format) → pass the whole URL to--node, do not stripdentryKeyby hand;/i/share/...(read-only share) → use theread_urlfallback. The "URL precheck" Step 0 inskills/SKILL.mdnow redirects every URL-bearing prompt through this dispatcher before the agent picks a product.
Changed
skills/references/products/doc.md—--nodeaccepts dingpan URLs end-to-end (#335) —dws doc info/dws doc readexamples gain two extra rows showing--node "https://alidocs.dingtalk.com/document/edit?dentryKey=<KEY>"and…/preview?dentryKey=<KEY>as first-class--nodeinputs. The "URL recognition & DOC_ID extraction" table adds thedocument/edit|preview?dentryKey=<key>row, and the extraction rules are split into three explicit clauses so the agent stops manually pullingdentryKeyout of the URL and feeding it as a barenodeId(which the server rejects). The "nodeId dual-format note" upgrades to "nodeId multi-format note" with four equivalent--nodeinput shapes side by side.
[1.0.30] - 2026-05-19
Aligns the open-source CLI with the IM envelope and schema-pipeline plumbing the Wukong edition has been running in pre-prod, plus three user-visible quality-of-life fixes. The most visible one: chat-bot webhook payloads carrying literal Chinese mentions (@所有人 周报来了 / @张三 看一下) no longer fail with file not found — @ is only treated as the @<filename> file-injection prefix when followed by an ASCII path-shaped character. The chat command tree is refactored to lean on the service-discovery envelope: thin wrappers (chat search, chat group rename, chat group members list/add/remove/add-bot, chat bot search) move out of the hardcoded helper and become envelope-generated dynamic commands; the helper keeps only the chat commands with real business logic (intelligent routing, current-user resolution, response normalization, stdin/@file input). A new dws chat message reply joins the existing send / send-by-bot / recall-by-bot / send-by-webhook family. Underneath: transform: invert_bool lets envelopes flip boolean semantics between CLI surface and MCP body (e.g. --off ↔ mute=true); the pipeline executor fail-fast on upstream content.errorCode instead of polling forever; service-discovery dedup keeps two envelope entries that share an MCP endpoint but declare different cli.id as separate descriptors (so the bot-root / bot-message / bot-group trio fronting one MCP server stays as three distinct CLI command roots); and dws chat no longer nests as dws chat chat when two envelope servers both declare the same top-level command name.
Added
transform: invert_boolfor envelope flag overrides (#317, seeinternal/compat/transform.go) — flips a boolean at send time. Stringstrue/1/yes/on→false;false/0/no/off/""→true. Used when the CLI surface and the MCP body have opposite semantics — e.g. envelope declares--offon the CLI but the MCP parameter ismute=truefor "muted". The framework flips at send time so the envelope keeps the natural CLI verb without forcing every caller to remember the inverted mapping. Coverage ininternal/compat/transform_test.go.dws chat message reply(#317, seeinternal/helpers/chat.go) — reply to a chat message. Sits alongsidesend/send-by-bot/recall-by-bot/send-by-webhookunderdws chat message.
Changed
chatcommand tree refactored to lean on the service-discovery envelope (#317, commit6be1247) —internal/helpers/chat.gonow only carries the chat commands that need real business logic on top of the raw MCP call:chat message send(current-user resolution + symmetric direct/group title validation),chat message send-by-bot/recall-by-bot/send-by-webhook(bot routing + stdin/@file input), andchat group create(response normalization). The thin wrappers —chat search,chat group rename,chat group members list/add/remove/add-bot,chat bot search— are now produced by the envelope as dynamic commands. Net diff in the helper:+358 / -71overall (re-aligning to envelope-owned chat structure), andchat_test.godrops 71 lines of test-stubs the dynamic path covers natively. Every previously documented chat command keeps the same flag set and the same MCP tool routing — the surface is just sourced differently.- Pipeline executor fail-fast on
content.errorCode(#317, seeinternal/compat/pipeline.go) — when an upstream tool returns a non-emptycontent.errorCode,executePipelineCallraises a validation error immediately with the upstreamerrorMessageinstead of proceeding into the poll/download phase. Pre-execution cobra validation (MarkFlagRequired) only checks that a flag was set, not that its value was non-empty — so a--required-flag ""reaches the upstream tool and the upstream rejects witherrorCode. Without the short-circuit the pipeline kept polling for a task ID that would never exist, either spinning toPollTimeoutor burning through retries with no actionable error. Exit code 2 (validation), same as any other CLI-layer pre-flight rejection. - Service-discovery dedup keys now include
cli.id(#317, seeinternal/market/registry.go) —NormalizeServersused to dedup envelope entries by endpoint alone (and bydisplayNamein the second pass), which collapsed envelope entries that intentionally split one MCP endpoint into multiple CLI command trees. Thebot-root/bot-message/bot-grouptrio all front the same.../server/4717...MCP endpoint and share the displayName机器人消息, but each declares a distinctcli.idand a distinct CLI command root; the old dedup kept only the last-write and dropped two of them. The dedup key now appends#<cli.id>when present, falling back to endpoint / name when absent so historical envelopes withoutcli.idkeep their existing behaviour. Coverage ininternal/market/registry_test.go.
Fixed
@<text>injection no longer eats Chinese mentions like@所有人/@张三(#317, seeinternal/cli/stdin.go) —ReadFileArgandResolveInputSourceused to treat any value starting with@as the@<filename>injection syntax. Chat-bot webhook payloads commonly contain literal mentions, sodws chat message send-by-bot --text "@所有人 周报"was failing withfile not found: 所有人 周报before the message reached the API. The newlooksLikeFilePathheuristic accepts@followed by an ASCII path-prefix character (A-Z/a-z/0-9/.///~/_/-), or@-for stdin, and passes the value through unchanged otherwise.@A 但接下来都是中文@测试does still attempt a file lookup because the rune right after@is ASCII — this matches the documented@<path>prefix shape. The historical "bare@is an error" behaviour is preserved. Coverage ininternal/cli/stdin_test.go::TestReadFileArgChineseAtMention.dws chatno longer nests asdws chat chatwhen two envelope servers contribute the same top-level command (#317, seeinternal/compat/dynamic_commands.go) —BuildDynamicCommandsused to overwritetopLevel[name]on the second contribution and rely onattachOrMergelater, which then attached the whole incoming command (namedchat) under the existing root, producingdws chat chat <leaf>. The newmergeSubcommandsIntomoves the second contribution's children under the first root and drops the duplicate wrapper, so e.g.group-chat+imenvelopes that both declarecli.command: chatproduce a single flatdws chatsubtree.- Multi-server tool-name authority correction in the runtime runner (#317, see
internal/app/runner.go+internal/app/direct_runtime.go) — when two envelope servers share the samecli.command, the per-product endpoint mapendpoints[cmd]inregisterDynamicServeris second-writer-wins, andcatalog.FindProductmay return the wrong server's endpoint for a tool whose real owner is the other server.runtimeRunner.Runnow cross-checks the canonical tool→endpoint map exposed by the newdirectRuntimeToolEndpoint: when the per-tool endpoint exists and differs from the per-product endpoint the catalog returned, the tool-owner endpoint wins. Pairs with the registry dedup change above so the routing matches the dedup result.
[1.0.29] - 2026-05-17
Three discovery-envelope products land on the open-source surface — aiapp (AI applications), live (DingTalk live streaming), and aisearch (enterprise people search) — closing the gap with the Wukong edition's product list. The aisearch envelope ships rich model-tolerance affordances (short flags, flag aliases, subcommand aliases) so AI agents that hallucinate keyword synonyms (--query / --name / --q / --text / --find) or alias subcommands (search / find / query / user / people / ...) still route to the canonical person tool instead of erroring out. To support that final fragment of agent tolerance, internal/compat/registry.go relaxes the envelope-generated leaf command's Args validator from cobra.NoArgs to cobra.ArbitraryArgs — restoring cobra's own default (legacyArgs returns nil for leaves) so trailing positional words are silently ignored. Plus the previously-shipped credential-isolation fix.
Added
dws aiapp/dws live/dws aisearch— three new products discovered via envelope (no public issue; pre-Diamond rollout) — open-sourcedwsnow exposes:dws aiapp— AI application lifecycle:create --prompt <p> [--attachments <json>] [--skills <csv>]/query --task-id <id>/modify --prompt <p> --thread-id <id> [--skills <csv>]. Backed by upstreamcreate_ai_app/query_ai_app/modify_ai_appMCP tools.dws live stream list— list my DingTalk live streams. Backed by upstreamget_my_lives.dws aisearch person— enterprise people search by keyword + multi-dimension filter. Dimensions:all(default) /name/department/position/duty/supervisor/subordinate/phone/jobNumber— multiple comma-separated (--dimension name,department). Backed by upstreamenterprise_person_search.- The
aisearchenvelope additionally registers-w/-dshort flags (keyword / dimension); hidden flag aliases--query/--name/--q/--text/--findall routing tokeyword; and cobra subcommand aliasessearch/find/query/user/people/search-person/search-user/user-search/lookup/ask/contactall routing toperson. This closes the F-class model-tolerance regression cases indws-wukong/auto-test/cli_to_mcp/testcases/aisearch/test_90_aisearch_param_regression.py(50/50 pass for aiapp + live + aisearch on the pre-mcp build). - Users must run
dws cache refreshonce to pick up the new envelopes; no binary upgrade is required, but pairs naturally with the v1.0.29 client (see Fixed below for the envelope-leaf-Args change).
Fixed
- Envelope-generated leaf commands now tolerate trailing positional args (#306, no public issue) —
NewDirectCommandininternal/compat/registry.gowas hard-codingcobra.NoArgsfor leaves without positional bindings (totalMax == 0). This is stricter than cobra's ownlegacyArgs(cobraargs.go:30-32returnsnilfor any command without subcommands), and surfaced asunknown command "<word>" for "<leaf>"whenever an AI agent passed trailing positional words after a leaf — e.g.dws aisearch person search --keyword "张"ordws aisearch person user search --keyword "张". Switching thetotalMax == 0branch (and the initial value) fromcobra.NoArgstocobra.ArbitraryArgsrestores cobra's natural leaf behavior: trailing positional args are silently ignored. Existing positional-binding paths (MinimumNArgs/RangeArgs/MaximumNArgs) are unchanged. Verified againstdws-wukong/auto-test/cli_to_mcp/testcases— aiapp (9/9) + live (3/3) + aisearch (38/38) = 50/50 pass, vs 48/50 before this patch.
Security
- App credential files are partitioned by edition to prevent cross-edition credential leakage (#300, no public issue; found during internal review) — different
dwseditions sharing the same config directory previously read and wrote the sameapp.json. A sibling edition that pinned its OAuth client ID could persist that ID through the shared post-login path, and the open-source build could later adopt it from the same file. Open-source/empty edition keeps the legacyapp.jsonpath for compatibility; sibling editions now useapp-<edition>.json, matching the existing cache partitioning strategy. This prevents new cross-edition app credential writes and reads from colliding. After a sibling edition saves its new partitioned file, it also best-effort removes a legacy~/.dws/app.jsononly when that file'sclientIdmatches the sibling edition being saved; a different, unparsable, or otherwise unownedapp.jsonis left untouched to avoid deleting open-source credentials. If you previously ran multiple editions in one shared~/.dws, remove any confirmed-stale orphan manually withrm ~/.dws/app.jsonafter verifying it is not the open-source credential file you still need.
[1.0.28] - 2026-05-14
A single symmetric follow-up to 1.0.26's #250: dws chat message send --group <cid> now refuses an empty --title at the CLI layer instead of letting the call fall through to the API and surface a misleading 发群服务窗会话消息失败 error. No other behaviour changes.
Fixed
dws chat message sendrejects missing--titleon group messages (#294, completes #250) —send_message_as_user's schema markstitleas required (just likesend_direct_message_as_user), butbuildChatMessageSendInvocationonly had the pre-validation on the direct-message branches. Group sends without a title were falling through to the API and returning the same misleading发群服务窗会话消息失败that #250 already fixed for direct messages. The check now covers both branches: missing--titleon--groupreturns--title is required for group messages (--group)with exit code 2; missing on--user/--open-dingtalk-idkeeps the original--title is required for direct messages (--user / --open-dingtalk-id). TheLonghelp,--titleflag description, the firstExample, andskills/references/products/chat.md(including the drive→chat workflow example) are realigned to "title is required for both direct and group messages" — the docs previously contradicted themselves (the prose said 群聊可选 while the flag listing said 必填).internal/helpers/chat_test.goadds agroup-without-titlerejection case; the existinggroup/positional-textsuccess cases now pass--titleto stay aligned with the new validation. No API request shape change — the server has always requiredtitle; the CLI now matches.
[1.0.27] - 2026-05-14
Two user-visible fixes plus the schema primitive they're built on. dws doc update now reads Markdown from a file or stdin, so long / multi-line / table-heavy content no longer gets mangled by shell escaping; dws sheet find --query stops returning unknown flag on the open-source build, restoring copy-paste from internal wukong docs. Underneath, schema/discovery envelopes get a generic file_read transform and a CLIFlagOverride.MapsTo field that lets two sibling CLI flags route into the same MCP parameter slot. Also suppresses a noisy WARN on normal stdio-plugin shutdown.
Added
file_readtransform +CLIFlagOverride.MapsTofield (#291, closes #277 #278 #282 #288) — discovery envelopes can now declare a path-typed CLI flag that performs the "file path → file contents string" conversion client-side before the value reaches the upstream MCP parameter.transform: "file_read"(internal/compat/transform.go) — reads the file at the flag's value with UTF-8 validation;-means stdin. Any IO / encoding failure is surfaced as a validation error (exit 2), distinct from the generic transient-failure path (exit 1).CLIFlagOverride.MapsTo(internal/market/registry.go) — redirects the flag's final value (post-transform or literal) into a named MCP parameter slot instead of the defaultparams[propertyName]. This lets a single MCP parameter (e.g.markdown) be fed by two sibling CLI flags — a literal--contentand a file-reading--content-file— paired with the existing tool-levelMutuallyExclusive/RequireOneOfto express "exclusive, at least one".- Wired into the
internal/compat/dynamic_commands.gonormalizer via a separatemapsToRoutescollection + routing pass; emptyMapsTopreserves the legacyparams[propertyName] = valuesemantics, so every pre-existing dynamic_commands test passes unchanged. Pre-prod end-to-end verified across 6 cases (see PR #291's Validation table).
dws doc update --content-file <path>(envelope rollout) — fixes "long Markdown can't reach the doc". The old command only accepted--content "...", so long / multi-line / table-heavy Markdown got mangled by shell escaping and AI agents writing >2KB of content were stuck. The envelope now maps both--content(literal) and--content-file(file_readtransform) to themarkdownparameter, makes them mutually exclusive via cobra'sMarkFlagsMutuallyExclusive, and requires at least one viaRequireOneOf.--content-file -reads from stdin, socat long.md | dws doc update --content-file -works directly. Existing users must rundws cache refreshonce to pick up the new envelope.dws sheet find --queryhidden alias (envelope rollout) — fixes "unknown flag when copy-pasting commands across editions". Users copyingdws sheet find --query "..."from internal wukong docs onto open-sourcedwsgotunknown flag: --query, because the open-source primary flag is named--find. The envelope now registers--queryas a hidden alias of--findviaCLIFlagOverride.Aliases(the field shipped in 1.0.26) — it doesn't show up in--help, but accepts values and writes to the same MCP parameter.--findbehaviour is unchanged. Also requiresdws cache refreshonce.
Fixed
- Noisy
failed to stop stdio client: exit status 1WARN on normal stdio-plugin shutdown (#285) — whenStop()explicitlyKills the subprocess, the non-zero exit code returned bycmd.Wait()is expected behaviour, but it was being propagated as an error and logged to stderr on every CLI exit, polluting agent log parsing.Stop()now returnsnilafter Kill + Wait; the error path is reserved for "process exited on its own with non-zero" (e.g. stdin close without an explicit Kill).internal/transport/stdio.go+stdio_integration_test.goassert "Stop() returns nil after kill".
[1.0.26] - 2026-05-12
Platform-stability round: Windows PAT-auth browser opener no longer truncates URLs at &userCode=, macOS sandbox hosts get an opt-in keychain fallback, and dws doc download rejects axls nodes before requesting drive:download consent. Two new global output formats -f ndjson and -f csv (matching larksuite/cli) land as first-class citizens with real-traffic-verified list detection. The dws doc comment * regression tracked in #240 is also resolved — fix is in the market metadata, users just need dws cache refresh once.
Added
-f ndjsonand-f csvglobal output formats (#259, closes #252) —ndjsonemits one compact JSON record per line (works straight withjq -c/while read/ log pipelines);csvgoes throughencoding/csv(RFC-4180 — quoting, embedded newlines, CJK all handled by stdlib) and reuses the existing-f tablecolumn resolver (normalizePayload/unwrapPrimaryObject/extractRowsFromMap/rowsFromSlice/formatValue) so table and csv stay visually aligned. After a 7-product real-traffic sweep (contact / chat / doc / mail / todo / minutes / schema), thepreferredListKeyswhitelist was extended to cover the actual DingTalk envelope shapes —contact user search(result),chat search(result.value),doc search(documents),mail mailbox list(emailAccounts),todo task list(result.todoCards) — so these commands now degrade into a proper row stream instead of collapsing to a single-linekey,valueblob. Lives ininternal/output/ndjson.go+internal/output/csv.go;--formathelp ininternal/app/flags.gonow listsndjson|csvalongsidejson|table|raw|pretty.
Changed
- Sticky flag splitting is now schema-aware (#272) — PreParse
StickyHandler此前会把任何前缀命中已知 flag 的--flagsuffix一律切成--flag suffix,于是--starttime20260507这类拼错被静默改写成--start time20260507,把假值传到下游。新行为按 flag 的 pflag 类型 / JSON Schemaformat/enum校验 suffix 是否像合法 value(共享逻辑见pkg/cmdutil/sticky_suffix.go),不像就保留原 token 让 cobra 报unknown flag。slice/array/object 类型的 flag 永不切分。首 rune 读取使用utf8.DecodeRuneInString,对中文等多字节 value 安全。
Added
available_flagsfield on unknown-flag errors (#272) —dws -f json的 unknown-flag 错误体里新增available_flags(已排序、过滤掉 hidden 与内部json/params),方便 agent 不解析--help就能恢复。Human-readable 输出会附Flags: ...行,截断在 200 字节内。
Fixed
dws chat message send单聊缺--title时前置校验 (#250) — 单聊(--user/--open-dingtalk-id)的底层工具send_direct_message_as_user在 API 层强制要求 title,缺失时返回误导性的发群服务窗会话消息失败。CLI 现在在buildChatMessageSendInvocation里前置校验,直接返回--title is required for direct messages (--user / --open-dingtalk-id);同时把Longhelp、--titleflag 描述、Example 和skills/references/products/chat.md全部对齐为「单聊必填,群聊可选」。群聊行为不变。- PAT auth URLs were truncated on Windows browser open (#242, fixes #230) —
cmd /c start <url>on Windows interprets&as a command separator, so PAT URLs containing&userCode=...were silently chopped before the userCode segment, and the browser landed on a 0-permission DingTalk page. The retry opener now usesrundll32 url.dll,FileProtocolHandler, which passes the URL through verbatim. The PAT response also exposes a copy-safedata.authorizationUrl(in addition to the service-provideddata.uri, which is preserved as-is), and human-readable PAT output printsPAT_AUTHORIZATION_URL=<full-url>on its own line so OpenClaw-style host wrappers that swallow or reformat stderr can still capture the full link. Legacy DingTalk hash-route shapes (https://open-dev.dingtalk.com/fe/old#%2FpersonalAuthorization%3FflowId=...%26userCode=...) are normalised back into the working/fe/old?hash=...#/personalAuthorization?...&userCode=...form. Regression tests cover the issue-shaped URLs (encoded hash, fragment,&userCode) plus the OpenClaw malformed-hash variant. dws doc downloadtriggereddrive:downloadPAT consent for unsupported axls nodes (#268, fixes #190) — added aget_document_infopreflight beforedownload_file, so online-sheet (axls) nodes are rejected locally with guidance to use sheet range tools instead. The preflight readsextensionfrom deterministic response paths (no recursive payload scan) and routes its own PAT errors back throughhandlePatAuthCheck, preserving device-flow / host-owned PAT behaviour. Costs one extra MCP roundtrip perdoc download— deliberate, so the unsupported path fails before consent. Lives ininternal/app/doc_download_preflight.go; coverage ininternal/app/runner_test.go.- macOS sandbox hosts (Codex App etc.) couldn't read/write tokens via Keychain (#267, fixes #214) — sandboxed macOS environments intercept
security/ Keychain APIs, so every token operation failed. New opt-inDWS_DISABLE_KEYCHAIN=1switches macOS to the same file-DEK path Linux uses (DEK at~/Library/Application Support/dws-cli/dek, mode0600), bypassing the system Keychain. Default behaviour is unchanged — fallback is strictly opt-in because file-DEK is a weaker trust model than Keychain-managed storage (DEK file sits next to ciphertext in the same directory). The Darwin / Linux file-DEK implementation is now shared ininternal/keychain/file_dek.go(Linux path deduplicated by ~40 lines). Documented indocs/reference.md(中英) with the security tradeoff spelt out so users make the choice explicitly. dws doc comment {list,create,create-inline,reply}returnedPARAM_ERROR - 未找到指定工具(fixes #240, also #234) — the four comment tools used to live on an independentdoc-commentMCP server. After the Portal merged comment functionality into thedocserver descriptor, the runtimetools/liston the mergeddocserver didn't include them, so everydws doc comment *call returned the "tool not found" PARAM_ERROR. The market metadata for thedocserver now declaresserverOverride: "doc-comment"on all four commenttoolOverrides, so the existing CLI routing path sendsdws doc comment *to the still-runningdoc-commentMCP server (which has the tools). No CLI code change was required, but existing users must rundws cache refreshonce to pick up the updated descriptor — without that, the stale local market cache keeps pointing the call at the mergeddocserver and the error persists. Verified post-refresh: dry-run resolves tohttps://mcp-gw.dingtalk.com/server/doc-commentwith toollist_comments, real calls return normal business responses (e.g. legitimate cross-org authz errors) instead of未找到指定工具.
[1.0.25] - 2026-05-11
Two generic envelope-schema enhancements that close gaps the cli_to_mcp test suite kept surfacing — both product-agnostic, no hardcoded helper commands. Plus missing skill references for the already-registered sheet and wiki products are now shipped.
Added
sheet(在线电子表格) skill reference + product-overview entry — thesheetproduct registers 34 envelope tools covering worksheet CRUD (create/new/list/info/copy_sheet/update_sheet), range read/write (range read/range update/append), dimension ops (add-dimension/insert-dimension/delete-dimension/move-dimension/update-dimension), merge (merge-cells/unmerge-cells), find/replace (find/replace), filter views (filter-view {create, list, update, delete, update-criteria, delete-criteria}), sheet-level filters (create_filter/get_filter/update_filter/delete_filter/set_filter_criteria/clear_filter_criteria/sort_filter), image write (write-image), and async export (submit_export_job+query_export_job). These were live in the envelope butskills/references/products/sheet.mdhad not shipped andskills/SKILL.md产品总览 didn't listsheet, so agents had no reference to consult and were skipping it during intent routing. This release adds the doc, registerssheetin 产品总览 + 意图判断决策树, extendsdescriptionto include 在线电子表格, adds a Sheet row toREADME.md/README_zh.md"Key Services", and notes the v1.0.25 reality on naming (about a third ofsheettools still expose snake_case cli_names pendingCLIAliases(#246) rollout) and on export (no consolidateddws sheet exportexists in v1.0.25 —submit_export_job+query_export_jobare the atomic primitives; Pipeline (#247) provides the future plumbing).wiki(知识库) skill reference + product-overview entry — the wiki product's 7 envelope tools (wiki.create_wikiSpace,wiki.get_wikiSpace,wiki.list_wikiSpaces,wiki.search_wikiSpaces,wiki.add_member,wiki.list_member,wiki.update_member, surfaced asdws wiki space create / get / list / searchanddws wiki member add / list / update) have been registered for a while, but noskills/references/products/wiki.mdshipped with them, so agents had no per-command reference to consult. This release adds the reference doc, registerswikiinskills/SKILL.md's 产品总览 table and 意图判断决策树, mentions 知识库 in the skilldescriptionfrontmatter, adds a Wiki row toREADME.md/README_zh.md"Key Services", and removeswikifrom the "Coming soon" callout (which was now stale).CLIToolOverride.CLIAliasesenvelope field (#246) — lets a single MCP tool register additional cobra command aliases via envelope JSON (e.g.range readalso acceptsrange get,member listacceptsmember ls). Plumbed through the existingRoute.Aliases → cobra.Command.Aliasespath; sibling conflicts are silently dropped by cobra. Lives ininternal/market/registry.go+internal/compat/dynamic_commands.go.json_parse_stricttransform (#246) — strict-JSON variant ofjson_parsethat does not fall back to YAML. Use when the upstream tool requires a structured array/object and silently coercing a malformed input to a scalar string would mask a real user error (observed:filter-view --criteria 'NOT_VALID_JSON'was being accepted and quietly creating an empty-criteria view). Ininternal/compat/transform.go.CLIToolOverride.Pipeline+ pipeline executor (#247) — a single CLI command can now orchestrate an ordered sequence of MCP tool calls plus optional HTTP-download sinks, declared entirely in envelope JSON. Motivating use case: the "submit-job → poll-status → download-result" pattern (e.g. sheet export) that previously required per-product hardcoded helpers.PipelineStepsupportstype:"call"(with optionalPollUntilField/PollUntilValue/PollIntervalSec/PollTimeoutSecfor polling loops) andtype:"download"(resolvesDownloadURLField, HTTP GETs the body, writes to the path fromOutputFlag, infers filename for directory paths).- Template language:
$flag.<name>resolves a user CLI flag by alias;$step.<idx>.<dotPath>walks a prior step's response (works through wrapped MCP envelopes); literals pass through. CLIFlagOverride.PipelineLocalmarks a flag as CLI-side only soCollectBindingsskips it (value never reaches MCP params); the pipeline executor still reads it viaextractFlagValuesByAlias.- Download step emits machine-parseable plain-text lines (
jobId: <id>\n,downloadUrl: <url>\n) alongside the standard JSON envelope, so shell pipelines and regex-based tests can extract key values without JSON parsing.
[1.0.24] - 2026-05-09
Three small but user-visible safety/usability changes: the embedded distribution now refuses to self-upgrade, the dws auth login help text finally matches the actual default flow (loopback, not device), and the release workflow gains a manual fallback trigger.
Changed
dws upgradeis blocked in embedded distributions (#248) — when the CLI is shipped as an embedded asset (e.g. inside another product),dws upgradewould happily overwrite the host-managed binary. The upgrade entry point now detects the embedded build flag and exits early with a clear message; covered byinternal/app/upgrade_embedded_guard_test.go.
Docs
dws auth loginhelp text reflects the real default (#238, fixes #226) — the long help previously claimed "OAuth 设备流 (默认)", but the actual default starts a 127.0.0.1 loopback listener and only switches to device flow when--deviceis passed. SSH-into-headless-Linux users following the old text hit a dead end (remote-side 127.0.0.1 is unreachable from the local browser). Help and twoflagErrorWithSuggestionsmessages inroot.goare realigned: each method is named after its real flag (OAuth Loopback 流 (默认)/OAuth 设备流 (--device)/直接提供 Token (--token)), with an explicit--deviceexample for SSH/headless. No behaviour change.
CI
workflow_dispatchtrigger added to release workflow as a fallback (#261) — GitHub occasionally drops tag-push events; the release job can now be re-run manually against any tag ref without having to delete and re-push the tag.
[1.0.23] - 2026-05-08
A single fix for HTTP proxy support across the CLI's custom HTTP transports. No behaviour changes elsewhere.
Fixed
HTTP_PROXY/HTTPS_PROXYenvironment variables silently ignored by all custom transports (#237, fixes #236) — the three customhttp.Transportinstances built by the CLI (internal/transport/client.goMCP transport,internal/apiclient/client.goDingTalk OpenAPI client,internal/app/legacy.goIPv4-forcing registry client) all setDialContext/TLSClientConfig/ timeouts but omitted theProxyfield. Per Go'snet/httpcontract, a non-nil Transport without an explicitProxymeans "no proxy" — env vars are silently ignored, breaking sandboxed or air-gapped deployments that route outbound throughHTTP_PROXY/HTTPS_PROXY. All three transports now setProxy: http.ProxyFromEnvironment.
Tests
- Per-package regression test that pointer-compares the Transport's
Proxyfunc againsthttp.ProxyFromEnvironment, avoiding flakiness from Go'senvProxyOncememoisation when running alongside tests that read proxy env early. (#237)
[1.0.22] - 2026-05-07
Two release-blocking bug fixes: dws attendance summary now exposes the server-required --stats-type flag (without it, every call returned C0002), and the install scripts finally populate ~/.hermes/skills/dws/ for users who already have Hermes.
Fixed
dws attendance summaryreturned C0002 (统计类型错误) on every call (#228, fixes #227) — the DingTalk MCP toolget_attendance_summaryrequiresstatsTypeat the business layer even though the schema marks it optional. The CLI did not expose any way to set it, so the command was 100% unusable. A new--stats-typeflag (week/month) is now plumbed through toQueryUserAttendVO.statsType; the flag is documented as required in the long help, flag description, andskills/references/products/attendance.md.- Install scripts skipped
.hermes/skills/when populating skill directories (#221, fixes #188) — theAGENT_DIRSlists acrossbuild/npm/install.js,scripts/install.sh,scripts/install.ps1,scripts/install-skills.shand the four upgrade-path mirrors (8 sources total once review feedback was addressed) did not include.hermes/skills, so users with Hermes installed were not getting~/.hermes/skills/dws/populated automatically. The existing parent-directory gate keeps this zero-side-effect for users without Hermes.
Tests
- New
--stats-typeregression coverage intest/cli_compat/attendance_test.go— verifiesstatsTypeis written toQueryUserAttendVOwhen set tomonthorweek, and is omitted when not provided. (#228)
[1.0.21] - 2026-05-05
A single critical routing fix for dws drive commands. No new commands or behaviour changes elsewhere.
Fixed
dws drive mkdir/dws drive downloadsilently routed to the doc MCP server (#220, fixes #219) — when two MCP servers register tools with the same name (e.g. bothdriveanddocexposecreate_folder), the tool-level endpoint map used last-writer-wins, so drive-side calls landed on the doc endpoint and returned mock-shaped responses (success: truewith a fakefolderId) without actually creating anything.directRuntimeEndpointnow resolves product-level first when the caller already knows the productID, and only falls back to the tool-level lookup when productID is empty. The wrong-server collision and the resulting "succeeded but didn't" behaviour are gone.
[1.0.20] - 2026-05-04
Documentation polish and a login regression fix. No behaviour changes outside the login MCP refresh path.
Fixed
- Login no longer reuses stale
clientIdfrom an old MCP cache (#213) —dws loginnow unconditionally re-fetches the MCP descriptor, so a previously cached client id can't keep producing auth errors after the server rotates it.
Docs
dws chat message listpagination (#218, fixes #195) — clarifies thatnextCursoris opaque and must be passed back as--cursorexactly; warns against parsing or reusing it as an offset.dws contact searchexamples (#209) — switched from the removed--keywordflag to the current--query.dws todohelp text (#205) — expanded field semantics so MCP wrappers generate accurate schemas.dws chat message send-by-botanddws report createhelp (#217, #106, #107) —--robot-code/--title/--textnow carry the(必填)marker;report create --contentsdocuments thekey=field_namerequirement and rewrites examples as atemplate detail → createtwo-step pipeline.- CHANGELOG backfill for 1.0.19 (#204).
[1.0.19] - 2026-04-30
Discovery hardening for edition overlays: edition.SupplementServers / FallbackServers hooks now consistently surface through the runtime catalog loader, not just the static command tree, so overlay products that live outside the Portal envelope (e.g. Wukong gray-release conference) resolve an endpoint on both the cold-cache and tool-not-in-catalog paths. Ships with per-edition cache partitioning to stop cross-edition disk-cache leakage, plus a small todo fix.
Added
pkg/config.EditionPartition(name)(#197) — returns the cache partition key for a given edition. Open-source core (""/"open") keeps usingDefaultPartition(default/default); every other edition gets its own namespace (<edition>/default), preventing cross-edition data leakage in the shared~/.dwsdisk cache. Lives inpkg/configas a leaf helper sointernal/cli,internal/app, andinternal/cachecan all call it without risking import cycles.internal/editionmergeshared package (#197) — single source of truth for convertingedition.ServerInfointomarket.ServerDescriptor(ToDescriptor) and for mergingSupplementServers/FallbackServersinto a descriptor list. Bothinternal/cli(command tree) andinternal/app(runtime catalog) now apply the edition hooks against the same discovery pipeline.
Changed
EnvironmentLoader.loadFromCachehonorsSupplementServerseven on empty registry (#197) — when the Portal registry cache is missing or empty, the catalog loader still materialises the edition'sSupplementServersas endpoint-onlydiscovery.RuntimeServerentries (source:edition_supplement), so hardcoded overlay commands for supplement-only products can still resolve an endpoint via the catalog path. PreviouslyloadFromCacheshort-circuited to an empty catalog whenever the registry snapshot was empty, silently dropping gray-release products.- Cache loader switches from
DefaultPartitiontoEditionPartition(edition.Get().Name)(#197) — the runtime catalog, registry snapshot, and tools snapshot are now partitioned per edition instead of all editions sharingdefault/default. loadFromCacheappends supplement servers alongside fresh-cache servers (#197) — supplement entries whoseCLI.ID/Keyare already present in the cached registry are skipped, so the hook never shadows Portal-published servers; only new products are added.runtimeRunner.Runfalls through todirectRuntimeEndpointfor supplement products (#197) — when the catalog contains the product (e.g. supplied bySupplementServers) but the specific tool is not declared, the runner now trustsdirectRuntimeEndpointto resolve a working endpoint for the tool before returning the explicit catalog-miss error. Supplement entries intentionally carry no tool list, so this is the path that makes overlay-only tools executable.- Legacy
mergeSupplementServers/fallbackToDescriptorsmoved out ofinternal/app/legacy.go(#197) — relocated intointernal/editionmergeand reused by the catalog loader, eliminating the duplicateedition.ServerInfo → market.ServerDescriptorlogic that previously only ran on the static command-tree path.
Fixed
dws todo task getreturns empty (#202) — the helper was callingquery_todo_detail, which is not a valid MCP tool and returns empty. Switched toget_todo_detailas declared indiscovery.json, restoring correct task-detail behaviour.- Conference and other Wukong gray-release products miss endpoint on cold cache (#197) — products registered only via
edition.SupplementServers(not yet in the Portal envelope) now resolve an endpoint through the catalog path in both cold-start and tool-not-declared scenarios.
Tests
internal/editionmerge/merge_test.go— descriptor conversion + supplement/fallback merge semantics.internal/cli/loader_partition_test.go+loader_supplement_test.go— edition-partitioned cache reads and supplement hook surfacing fromloadFromCache(including empty-registry cold path and existing-ID deduplication).internal/app/legacy_wukong_partition_e2e_test.go— end-to-end cache partition isolation for the Wukong edition.internal/app/runner_supplement_fallback_test.go— runner falls through todirectRuntimeEndpointwhen the tool isn't declared by a supplement-sourced catalog entry.pkg/config/constants_test.go—EditionPartitionname handling ("","open", custom edition).
Docs
- CHANGELOG v1.0.18 rewrite (#193) — previous release notes expanded to call out the PAT host-owned A-core flow, exit-code contract change (auth
4, Discovery/cache/protocol6),dws pat chmod/pat browser-policyentry points, stderr-JSON classifier updates, and host-control metadata injection.
[1.0.18] - 2026-04-28
Raw DingTalk OpenAPI access lands as a new dws api surface for both api.dingtalk.com and oapi.dingtalk.com, backed by app-level token caching and guarded host allowlists. PAT enters the host-owned A-core loop: agent hosts can own authorization UI through DINGTALK_DWS_AGENTCODE, parse single-line stderr JSON, call dws pat chmod, and replay the original command. Chat helper regressions are fixed, skill references are brought back in line with shipped commands, and the v1.0.17 Mail release notes are backfilled into README / CHANGELOG.
Breaking
- PAT exit-code contract (#142) — PAT authorization interceptions now use exit code
4; Discovery, cache, and protocol negotiation failures now use exit code6. Downstream scripts that previously treated4as Discovery must update their handling.
Added
dws apiraw DingTalk OpenAPI command (#184) — direct DingTalk OpenAPI calls without writing an MCP wrapper first. SupportsGET/POST/PUT/PATCH/DELETE, JSON--params/--data, stdin input, dry-run previews,--jq, field selection,--page-all,--page-limit,--page-delay, and--base-url.- Dual-form OpenAPI routing (#184) —
api.dingtalk.comrequests use thex-acs-dingtalk-access-tokenheader;oapi.dingtalk.comrequests use the legacyaccess_tokenquery parameter. The raw API client validates the target host before attaching credentials. - App-level token cache for raw API (#184) — custom-app credentials now fetch app access tokens from the unified OAuth endpoint, cache them while valid, and refresh them before expiry. The same token provider works for new-style and legacy OpenAPI calls.
- Host-owned PAT A-core flow (#142) — when
DINGTALK_DWS_AGENTCODEis set, PAT hits returnexit=4plus single-line stderr JSON; the host renders authorization UI, callsdws pat chmod <scope>..., and replays the original command. dws pat chmodauthorization entry point (#142) — grants scopes with--agentCode,--grant-type, and session fallback support;DINGTALK_DWS_AGENTCODEcan supply the agent code when the flag is omitted.- PAT browser-open policy (#142) —
dws pat browser-policy --enabled <true|false> [--agentCode <id>]controls whether the CLI may open a browser, independently from--formatoutput mode.
Changed
- README raw API guide (#184) — English and Chinese READMEs now document custom-app prerequisites, api/oapi examples, auto-pagination, dry-run, jq filtering, security properties, and the new Raw API service-table row.
- Raw API token retrieval path (#184) — token lookup now goes through a single app-token interface; stale auth-refresh retry helpers were removed from the raw API path.
- PAT stderr JSON classifier (#142) — recognizes
code,errorCode, anderror_code, includingPAT_NO_PERMISSION, risk-tier PAT errors,PAT_SCOPE_AUTH_REQUIRED, andAGENT_CODE_NOT_EXISTS. - Host-control metadata injection (#142) — classifier and active-retry paths now share one mutation point for
data.hostControlanddata.openBrowser, keeping host-facing JSON shapes aligned. - Open-edition routing signals (#142) — open edition pins
claw-type: openClaw;DINGTALK_AGENT,DWS_CHANNEL, and host-owned PAT detection are kept as independent signals. - Behavior authorization endpoint fallback (#142) — the PAT runtime can resolve the built-in behavior-authorization MCP endpoint before discovery data is available.
- v1.0.17 documentation backfill (#181) — the previous release notes and README service table now explicitly include the shipped Mail product, update the total to 163 commands across 14 products, and remove Mail from "Coming soon".
Fixed
- CLI auth-denial attribution — local CLI authorization denials are attributed to the channel before falling back to user-scope classification, avoiding user-scope misclassification for channel-level auth failures.
- Opaque authorization URLs (#182, #142) — PAT authorization links are preserved verbatim, including query/hash/fragment content required by the server.
- Polling compatibility (#182, #142) — device-flow result envelopes and no-
flowIddevice-code fallback remain supported, with guarded debug output and envelope priority. - Group chat @-mentions restored (#180) —
dws chat message send --group ...again accepts and forwards--at-users,--at-all, and--at-mobiles; those flags are rejected outside group-chat mode so single-chat sends cannot silently drop @-mention intent. - Explicit members-list command restored (#180) —
dws chat group members list --id <openConversationId>is reachable after the helper/dynamic merge path changed.cmdutil.MergeHardcodedLeavesnow honors higher-priority helper groups when a dynamic envelope contributes a leaf at the same path. - Skill reference command names (#186) —
simple.mdnow uses shipped OA command names (list-pending,list-initiated), removes a non-existent devdocsearch-errorcommand, and marksworkbench.mdas Draft because workbench commands are not available in the runtime. - Empty grant result handling (#142) —
dws pat chmodnow returns an explicit error instead of treating{"Content": null}as success. - Session-id log safety (#142) — raw
DWS_SESSION_ID/REWIND_SESSION_IDvalues are no longer logged when the two env vars disagree.
Tests
- Added raw API coverage for request validation, api/oapi routing, token management, pagination, response handling, dry-run output, JSON parsing, stdin handling, and command wiring. (#184)
- Added chat/cmdutil regression tests for group @-mention forwarding, single-chat rejection,
members list, helper-vs-envelope shape mismatch, and merge-priority behavior. (#180) - Added PAT contract coverage for host-owned signal selection, single-line stderr JSON, chmod env fallback and legacy alias fallback, browser policy, direct-runtime PAT endpoint fallback, and retry/poll behavior. (#142)
- Coverage badge refreshed after the post-v1.0.17 CI runs.
[1.0.17] - 2026-04-27
New Mail product surface (mailbox list, KQL message search, message get, send) brings runtime command count to 163 across 14 products. Plugin command-tree visibility hardening: stdio plugins shipping CLI overlays no longer wait on subprocess discovery to surface their commands, and overlay-registered plugin products are no longer hidden by edition VisibleProducts whitelists. Chat docs clarify that --title is required on dws chat message send.
Added
mailproduct (#167) — new top-level service for DingTalk Mail. Four leaf commands across two subgroups:dws mail mailbox list— list mailbox addresses available to the current user (list_user_mailboxes)dws mail message search— KQL search across folders / sender / date / attachments / read-state (search_emails); supports--cursorpaginationdws mail message get— fetch full message body + headers + attachments by message ID (get_email_by_message_id)dws mail message send— send email to one or more recipients (send_email)- Skill reference at
skills/references/products/mail.mdregistered inskills/SKILL.mdmaster index and intent decision tree
- Stdio plugin overlay-first command registration (#179) — when a stdio plugin's
overlay.jsondeclarestoolOverrides, command trees are built from manifest metadata synchronously at startup, no subprocessInitialize/tools/listhandshake required. Previously, slow or failing subprocesses left plugin commands invisible indws --help. Background discovery still runs to refresh the warm cache for richer flag types on subsequent startups.
Changed
hideNonDirectRuntimeCommands/visibleMCPRootCommands/visibleUtilityRootCommands(#179) — refactored to share a singleresolveVisibleProducts()helper that unions the edition'sVisibleProductshook withDirectRuntimeProductIDs(), so plugins registered viaAppendDynamicServerstay visible indws --helpeven when an edition installs a static product whitelist. Previously the hook fully replaced the dynamic registry, silently hiding plugin commands.dws chat message senddocumentation clarifies--titleis required (#174) — the helper command short text and the chat skill reference now state explicitly that--titleis mandatory for both group and single-chat sends, matching the runtime validation.buildStdioCommandsrefactored to share helpers with the overlay-first path (#179) — overlay parsing (resolveStdioOverlay) and tools→DetailTool conversion (toolsToDetails) extracted as package-level helpers; the legacy discovery-first stdio path now delegates to them, eliminating duplicated overlay JSON / cache-snapshot logic.
Fixed
- Negative-cache poisoning guard for stdio plugin discovery (#179) —
refreshStdioToolsCachenow skipsSaveToolsentirely when discovery returns an empty tool list (transient failure, subprocess not ready, RPC timeout), so a single bad refresh cannot overwrite a previously-good cache and degrade flag enrichment on the next startup.
Tests
- 6 new test cases in
internal/app/plugin_stdio_overlay_test.goandinternal/app/visibility_test.gocover overlay-first registration without discovery, warm-cache flag enrichment fromInputSchema, fallback when overlays lacktoolOverrides, the cache-poisoning guard, and integration cases for plugin visibility under restrictiveVisibleProductswhitelists. - Coverage 49.8% → 52.8%.
[1.0.16] - 2026-04-24
Discovery service abstraction with schema v3 extensions, open-edition helper-subtree restoration, and a defensive device-flow login reset.
Added
internal/discoveryservice abstraction (#156) — encapsulates market registry fetch, MCP runtime negotiation (initialize → tools/list → detailmerge), and multi-level cache fallback.EnvironmentLoadernow does cache-first startup, with degraded-mode reasons (unauthenticated/market_unreachable/runtime_all_failed) andUpdatedAt-based selective re-discovery.- Schema v3 extensions (#156) — positional parameters with typed coercion,
Exampleon--help, flagDefault/RuntimeDefault(with$currentUserId/$nowetc.),BodyWrapper,MutuallyExclusive/RequireOneOfflag groups,OmitWhen, explicitTypeoverride, and detail-schemadefaultpropagation. dws chat message senddestination-flag routing (#170) — open edition gains a hardcoded helper that dispatches by--group(→send_message_as_user) vs--user/--open-dingtalk-id(→send_direct_message_as_user), mirroring the closed-source overlay so single-chat sends finally work end-to-end.
Changed
pickCommands→cmdutil.MergeHardcodedLeaves(#169) — when a top-level product name collides between the dynamic overlay and a helper subtree, helper-only siblings are grafted into the dynamic tree instead of dropped. Restoresdws chat message send-by-bot/recall-by-bot/send-by-webhookanddws chat group members add-bot, which had silently vanished from the open edition.OverridePriority/MergeHardcodedLeavespromoted intopkg/cmdutil(#170) — single source of truth for the merge layer; hardcoded leaves can opt into overriding the dynamic envelope via a strictly higher priority.
Fixed
- Device flow defensively resets credentials before login (#157) —
--devicelogin now clears stale credential state and re-fetchesclientIDfrom the MCP server, regardless of what previous login methods (OAuth scan, PAT) left inapp.json. Fixes the case where a prior OAuth login made--devicefall back to direct mode and demandclientSecret.
[1.0.15] - 2026-04-23
Compat layer gains subcommand merging under shared parents so multiple server entries can contribute into the same dws <parent> <branch> subtree without producing duplicate --help rows. Ships with a fresh auto-generated command index doc, a README sync to 159 commands across 13 products, and a wide-ranging flag-naming cleanup that standardises CLI flags across chat, calendar, drive, minutes, contact, and devdoc commands.
Added
internal/compatsubcommand merging viaattachOrMerge— when two or more server entries attach to the same parent (e.g.parent: "chat") and theircli.commandcollides with an existing subcommand in the parent's tree, the new subcommand's children are merged recursively into the existing one instead of creating a duplicate sibling. Leaf-name collisions resolve first-wins. Fixes the "doublegroup/messagerows indws chat --help" symptom when bot capabilities are distributed acrosschat.group.membersandchat.message.docs/command-index.md— a single, English, auto-generated listing of every runtime command thedwsCLI exposes under the pre environment (159 total). Each entry carries a description and a "when to use" column aimed at AI agents. Replaces the earliercommand-index.pre.*/command-index.full.*ad-hoc snapshots.
Changed
- README Key Services table (
README.md+README_zh.md) fully synced to the shipped command surface:Chat: 20 → 23 (bot capabilities merged in; newlist-all/list-focused/list-unread-conversations/conversation-infoexposed)Calendar: 13 → 14AI Tables: 37 → 41 (chart / dashboard public-share config rows)Doc: 16 → 21 (comment subtree +file create)Minutes: 22 → 19 (single-toolrecord,list query,list-by-keyword-rangepruned)- New
Driverow (6 commands) — promoted out of "Coming soon" Workbenchrow and standaloneBotrow removed- Total revised to 159 commands across 13 products
- Quick Start expanded to 7 examples covering
doc,minutes,drivein addition tocontact,calendar,todo - Coming soon trimmed to 5:
mail,conference,aiapp,live,wiki - Reference & Docs section now leads with a pointer to the new
docs/command-index.md - Flag naming cleanup — CLI flags across chat, calendar, drive, minutes, contact, and devdoc have been standardised so the names users type match the product-skill documentation. Notable flags:
dws contact user search/dws contact dept search/dws devdoc article searchnow take--query(previously--keyword)dws chat message list/dws chat message search/dws chat message list-mentions/dws chat conversation-info/dws chat message sendnow take--groupfor the target conversation (previously--id) and--open-dingtalk-id(previously--open-id)dws chat message list-by-sendernow takes--sender-user-id/--sender-open-dingtalk-id(previously--user/--open-id)dws chat message list-topic-repliesnow takes--group/--topic-id/--limit/--time(previously--id/--topic/--size/--start)dws chat search-commonnow takes--match-mode(previously--mode)dws drive listnow takes--max/--thumbnail(previously--max-results/--with-thumbnail)dws calendar event suggestnow takes--users/--duration/--timezone(previously--attendee-user-ids/--duration-minutes/--time-zone)dws minutes list mine/dws minutes list sharednow take--max(previously--max-results) and gain--query/--start/--enddws minutes list allno longer exposes the legacy--__scope__internal alias
- Flag coverage additions —
dws calendar event create/updategain--attendees,--open-dingtalk-ids,--timezone;dws chat message sendgains file-message flags (--dentry-id,--file-name,--file-size,--file-type,--media-id,--msg-type,--space-id) plus--open-dingtalk-id/--user;dws chat message listgains--open-dingtalk-id/--user;dws aitable table deletegains--reason;dws calendar participant addgains--optional;dws todo task creategains--recurrence.
Tests
- 3 new unit tests in
internal/compat/dynamic_commands_test.go:TestBuildDynamicCommands_ParentMergeSameName— two servers with identicalcommand+parentcollapse into a single merged subcommandTestBuildDynamicCommands_ParentMergeRecursive— recursive merge through nested groups (e.g.chat.group.members)TestBuildDynamicCommands_ParentMergeLeafCollision— identical leaf paths resolve first-wins without producing duplicates
[1.0.14] - 2026-04-22
Docs-only re-tag of v1.0.13. The single commit (#153) backfills the v1.0.13 release notes after the binary was already published; no functional or CLI surface change.
[1.0.13] - 2026-04-22
IM / Messaging capability expansion: the chat (aka im) product surface grows from "group + bot messaging" into a full conversational layer — user-identity messaging, message reading & search, personal messages, topic replies, mentions, focused contacts, unread/top/common conversations, org-wide group creation, and first-class bot lifecycle.
Added
dws imalias —dws imis now registered as an alias ofdws chatfor intent clarity- User-identity messaging (
chat message send) — send group or 1-on-1 messages as the current user- Recipient selection is mutually exclusive:
--group <openConversationId>/--user <userId>/--open-dingtalk-id <openDingTalkId> - Markdown text via
--text(or positional arg), optional--title - Group-only:
--at-allto @everyone,--at-usersfor per-member @mentions - Image messages via
--media-id(obtained fromdt_media_upload)
- Recipient selection is mutually exclusive:
- Personal messages (
chat message send-personal) — sensitive personal-channel send (⚠️ destructive/dangerous op, requires confirmation) - Conversation read paths:
chat message list— pull group / 1-on-1 conversation messageschat message list-all— pull all conversations for the current user in a time rangechat message list-topic-replies— pull group topic reply threadschat message list-by-sender— messages by a specific senderchat message list-mentions— messages where the current user was @-mentionedchat message list-focused— messages from focused / starred contactschat message list-unread-conversations— unread conversation listchat message search— keyword search across conversationschat message info— conversation metadatachat list-top-conversations— pinned conversation list
- Group creation & discovery:
chat group create-org— create an organization-wide groupchat search-common— search groups shared with a nickname list (--nicks,--match-mode AND|OR, cursor-based pagination)
- Bot lifecycle:
chat bot create— create an enterprise botchat bot search-groups— search the groups a bot is present in
Changed
chatskill reference (skills/references/products/chat.md, #148) restructured into three sub-groups —group(9) /message(15) /bot(3) — with refreshed intent-routing table, workflow examples, and context-passing rules aligned withdws-service-endpoints.json(16 new group-chat tool overrides + 2 new bot tool overrides)- README Key Services sync:
Chatrow: 10 → 20 commands; subcommand tags expanded tomessagegroupsearchlist-top-conversationsBotrow: 6 → 7 commands; subcommand tags expanded withcreatesearch-groups- Total raised to 152 commands across 14 products
[1.0.12] - 2026-04-21
Product-surface expansion: first-class doc (DingTalk Docs) and minutes (AI Minutes) skill references, refreshed aitable guide aligned with the shipped binary (including dashboard / chart / export), and a README sync that brings the full command catalog to 141 commands across 14 products.
Added
docskill reference (skills/references/products/doc.md) — 16-command coverage of DingTalk Docs:- Discovery:
search,list,info,read - Authoring:
create,update,folder create - Files:
upload,download - Block-level editing: block
query,insert,update,delete - Comments:
comment list,create,reply - URL →
doc_idextraction rules and nodeId dual-format notes
- Discovery:
minutesskill reference (skills/references/products/minutes.md) — coverage of AI Minutes:- Lists: personal / shared-with-me / all-accessible
- Content: basic info, AI summary, keywords, transcription, extracted todos, batch detail
- Editing: title update
- Recording control: start, pause, resume, stop
- SKILL.md routing:
- Product overview table rows for
docandminutes - Intent decision tree routes —
钉钉文档/云文档/知识库/块级编辑/文档评论→doc;听记/AI听记/会议纪要/转写/摘要/思维导图/发言人/热词→minutes - Danger-op table entries:
doc delete,doc block delete aitabledescription completed with the附件(attachment) group
- Product overview table rows for
aitableskill enhancements:field createsingle-field mode (--name/--type/--config) with examplesbase getURL →baseIdquick-tip- Dedicated "URL → baseId 提取" chapter
- "
--filters筛选语法排错与使用规范" chapter - "相关产品" cross-link section pointing to
doc - "复杂操作" chapter (#141) — dashboard / chart workflow (with two-call sequencing and
chart share getvsdashboard share geterror semantics) and two-stageexport datapolling (scope=all/table/viewparameter constraints)
- README Key Services sync (#140):
- New rows:
doc(16 commands),minutes(22 commands — addshot-word,mind-graph,replace-text,speaker,uploadsubgroups) aitableexpanded from 20 → 37 commands; surfaceschart,dashboard,export,import,viewsubgroups- Total command count updated from 86 → 141 across 14 products
- "Coming soon" list drops
docandminutes
- New rows:
Changed
aitable record querydocs rename--keyword→--queryto match the shipped binaryaitable record querydocs clarify--sortdirection semantics (avoids misuse oforder)aitable base listguidance strengthened — "only for recent browsing; usebase searchfor lookups"; intent decision prioritizesbase searchfor base discovery
[1.0.11] - 2026-04-20
Plugin subsystem hardening: faster cold startup, cleaner lifecycle, stricter isolation, and polished UX for PAT / i18n / error routing.
Added
feat: supports claw-like products— overlay path for Claw-style embedded editionsfeat(plugin): inject user identity (UserID, CorpID) into stdio plugin subprocessesfeat(auth): improve login UX for terminal auth denial cases— clearer messaging + retry affordancefeat: PAT scope error visualization and auto-retry with authorization polling(#113)- Human-readable error output (lark-cli style) with type/message/hint/authorization command
- JSON payload also available via
--format json - Auto-retry once the user completes scope authorization
Changed
perf(plugin): serve plugin MCP tool list from disk cache on startup— hot path skips Initialize+ListTools when snapshot existsperf(plugin): parallelize all plugin discovery and tighten cold timeouts— HTTP cold budget 4s → 700ms (auth) / 500ms (plain); stdio and HTTP fan out concurrentlyperf(plugin): share cache.Store across discovery— single*cache.Storeabove the fan-out instead of per-goroutine instancesrefactor(plugin): remove default/managed plugin privileged mechanism(#124) — third-party plugins install on an equal footing viadws plugin installrefactor(plugin): purge removed plugin settings instead of merely disabling—RemovePluginnow deletesEnabledPluginsandPluginConfigsentries
Fixed
fix(transport): cap plugin MCP startup at ~4s when endpoints are unreachable(#119) — eliminates the 10sdws --helpstall caused by compounding transport timeoutsfix(plugin): stop stdio child processes on exit and before removal— no more orphaned plugin subprocessesfix(pat): avoid shared PAT command state in root registration(#129)fix: -f json 模式下错误 JSON 从 stdout 改为输出到 stderr(#133) — restores CI stderr-based failure assertionsfix(cli): localize plugin/help command strings via i18n(#118, #134) — zh locale now shows consistent Chinese--help; wraps plugin module, help command, and OAuth client-id/secret flag descriptionschore: remove workspace and bundled artifacts(#127) — clean local-only repository leftovers
[1.0.9] - 2026-04-16
Plugin system launch + execution-pipeline overhaul. This is the largest release since 1.0.0: third-party MCP servers become first-class commands, the command pipeline grows to five stages, and the edition overlay gains the hooks needed for embedded hosts.
Added
Plugin system (new)
plugincommand family:install,list,info,enable,disable,remove,create,dev,config set/get/list/unset- Plugin manifest parsing/validation, managed/user directory-based identity
- MCP server conversion and injection into the dynamic routing registry
- Pipeline hook adapter for shell-based hooks
- Stdio transport: subprocess lifecycle,
DWS_PLUGIN_ROOT/DWS_PLUGIN_DATAvariable expansion - Stdio server tools automatically registered as CLI subcommands (e.g.
dws hello greet --name Peter) - Streamable-HTTP MCP tool discovery via
registerHTTPServer - Updater: managed plugin update check on CLI startup (10 s timeout, best-effort)
dws plugin createscaffold (plugin.json, SKILL.md, hooks.json);dws plugin devsource-dir registration without copySyncSkills— copies plugin skills to agent directories on startup- Auth Token Registry: per-server HTTP headers declared in
plugin.jsonfor third-party MCP servers (e.g. Alibaba Cloud Bailian) independent from DingTalk OAuth - Persistent plugin config (
dws plugin config ...): values persisted to~/.dws/settings.json, auto-injected as env vars;${KEY}inplugin.jsonresolves without manualexport - Build lifecycle:
buildfield compiles stdio servers to native binaries at install time - Command-name conflict protection: reserved built-in names (
auth,plugin,cache, …) and plugin-vs-plugin duplicate detection - Parallel service discovery (
sync.WaitGroup) — startup reduced from sequentialN*10sto parallelmax(10s)
Core commands & diagnostics
dws doctor— one-stop environment/auth/network diagnosticsdws config list— centralized view of scattered configuration- Structured perf tracing (upgraded from debug tool to diagnostics output)
feat(skill): restore find/get for legacy skill market API—skill find,skill get;skill addstill uses aihub download
Edition / overlay hooks
edition.Hooks.SaveToken/LoadToken/DeleteToken— delegate token persistence with keychain fallbackedition.Hooks.AuthClientID/AuthClientFromMCP— overlay can override the OAuth client ID and route auth through MCP endpointsedition.Hooks.AfterPersistentPreRun— wire non-MCP clients (e.g. A2A gateway) after root setupedition.Hooks.ClassifyToolResult— custom MCP result classification before the default business-error detection- Token marker file (
token.json) for embedded hosts to detect auth state without keychain access pkg/runtimetoken.ResolveAccessTokenmirroring MCP auth resolution; MCP identity headers exported viapkg/clifor auxiliary HTTP transportsExitCoderinterface — edition-specific errors carry custom exit codesRawStderrErrorinterface — errors that bypass CLI formatting and emit raw stderr (for desktop runtimes)
Changed
- Command execution pipeline: 3 → 5 stages (
Register → PreParse → PostParse → PreRequest → PostResponse) feat(schema): return structured degraded errors instead of silent empty catalog— newCatalogDegradederror with reasonsunauthenticated/market_unreachable/runtime_all_failed; auth pre-check short-circuits doomed MCP connectionsrefactor(auth): unify auxiliary token resolution with MCP cached path— sharedresolveAccessTokenFromDir; overlays reuse the process-level token cachefeat(plugin): improve CLI overlay resolution and plugin install robustnessplugin.jsonclifield now accepts a file path (e.g."cli": "overlay.json") in addition to inline JSONdescriptionfield onCLIToolOverridefor static fallback when MCPtools/listis unavailable- Windows install uses
cmd /Cinstead ofsh -cfor build commands
Fixed
fix(plugin): harden plugin system security boundaries- Reject
file:/// local paths in git URLs; allow onlyhttps/ssh - Reject symlink entries during ZIP extraction (path-traversal defense)
build.outputmust be a relative path within the plugin directory- Reject absolute paths in stdio command declarations
- Block dangerous env var names (
PATH,LD_PRELOAD, …) from plugin config injection
- Reject
fix(plugin): schema flag params, HTTP tool discovery, and integration testsfix(plugin): skip min version check in dev mode
[1.0.8] - 2026-04-07
AITable command surface expansion, installer alignment with npm conventions, and execution-timeout hardening.
Added
- AITable static helper commands (20 commands in total) replacing dynamic routing:
base:list,search,get,create,updatetable:get,create,updatefield:get,create,updaterecord:query,create,updatetemplate:searchattachment:upload
feat(install): align skill dirs with npm and add OpenClaw— skill install paths follow npm conventions; OpenClaw added to supported agents- Label rendering optimization for AITable records (
to #73551688) - README: npm install method documented
- README: note that
dws upgraderequires v1.0.7+
Changed
perf: optimize command timeout handling, instrumentation, and diagnostics
[1.0.7] - 2026-04-02
Self-upgrade, edition overlay foundation, and fail-closed auth enforcement.
Added
dws upgrade— self-upgrade via GitHub Releases; atomic replace; cross-platform (macOS/Linux/Windows)feat: edition layer for Wukong overlay— build-time edition hook lets downstream overlays customize auth UX, config dir, static server list, visible products, and extra root commandspkg/editiondefaults +pkg/editiontestcontract testsMakefiletargetedition-test; CI jobedition-tests- Static server injection skips market discovery when configured
- Deduplicates top-level commands so overlay wins
hideNonDirectRuntimeCommandsrespects editionVisibleProducts- Gated
auth loginsubcommand + hints for embedded editions - Optional token auto-purge; edition
ConfigDiroverride
dws version— human-readable multi-line output plus JSON with edition, architecture, build, commit- Tag reporting for case suites (
to #73551688) feat(auth): unify MCP retry constant and add retry to remaining endpoints
Changed
style(auth): redesign OAuth authorization pages UI
Fixed
fix(auth): switch CLI auth check from fail-open to fail-closed- When
/cli/cliAuthEnabledis unreachable (network error/timeout/5xx), OAuth callback now routes to the permission request page instead of silently marking "enabled" - Device Flow blocks login and asks the user to verify network connectivity
CheckCLIAuthEnabledretries with backoff (3 attempts, 0s/1s/2s) to tolerate transient issues
- When
[1.0.6] - 2026-04-01
Error diagnostics overhaul, destructive-command confirmation, and credential auto-persistence.
Added
- Interactive confirmation for destructive dynamic commands — prompts before delete/remove operations unless
--yesis set - Enhanced error diagnostics
ServerDiagnosticsstruct extractstrace_id,server_error_code,technical_detail,server_retryablefrom MCP responses- Pulls diagnostics from JSON-RPC
error.data, tool call result content, and HTTP headers (X-Trace-Id,X-Request-Id,x-dingtalk-trace-id) - Three verbosity levels for
PrintHuman: Normal (trace ID + server code), Verbose (+ technical detail), Debug (+ RPC code / operation / reason) - Local logging now includes sanitized request body, response body on error, retry attempts, and classification events
TruncateBody/SanitizeArguments/RedactHeadershelpers with sensitive-key substring detection
- Auth credential persistence
feat(auth): enhance device flow with CLI auth check and admin guidancefeat(auth): persist OAuth credentials for reliable token refreshfeat(auth): persist client credentials and optimize keychain access— auto-persist--client-id/--client-secret; keychain credential cache to avoid repeated reads; enhanced logout cleansapp.json+ keychain secrets +token.json
add report helper with flexible date parsing and defaultsfeat: to #73551688 支持消息通知- README: Official App mode (recommended, direct login without creating an app) + Custom App mode; admin guide for enabling CLI access
Changed
- Getting Started simplified with inline login commands; whitelist references removed from the IMPORTANT banner
- Version bump documentation updated to v1.0.5 internal; co-creation group QR code refreshed
Fixed
fix: resolve verbosity flag lookup, FileLogger lazy binding, and business error loggingresolveVerbosityusescmd.Flags()instead ofPersistentFlags()so subcommands inherit--verbose/--debugFileLoggerlazy-binds inexecuteInvocation(afterconfigureLogLevelinit)- Business errors (HTTP 200 +
success=false) now written to the file logger for offline diagnosis
- OAuth callback race condition (write response before sending code)
import path for errors package in skill_command.go
[1.0.4] - 2026-03-30
Token-refresh reliability and onboarding clarity.
Added
feat(auth): persist client credentials for token refresh—--client-id/--client-secretare stored for automatic refresh after expiration; client secret lives in the system Keychain with a file reference- README onboarding flow rewrite with step-by-step first-time setup and more realistic examples
- Agent skill reference polish: clearer examples, updated intent routing patterns, expanded
simple.mdonboarding, cross-skill reference fixes
[1.0.3] - 2026-03-29
Filtering power, schema rendering, and a native todo command family.
Added
- Nested / array-indexed output filtering
--fieldsnow accepts dot-notation (e.g.--fields response.content) and array index access (e.g.response.items[0])- New field-path parser with recursive extraction logic
schemacommand enhancements- Table format output for human consumption
- Product-level endpoint loading in the CLI loader
- Schema-text rendering wired into the runner output pipeline
todotask helper family — staticcreate/update/done/get/deletewithpreferLegacyLeafreplacing dynamic commands- MCP tool alignment:
create_personal_todo,update_todo_task,update_todo_done_status,query_todo_detail,delete_todo - ISO-8601 due-time parsing
- Hidden title aliases and delete confirmation
- Priority field on
todohelper - Expanded zh / en i18n coverage (fixes
en.jsonspacing/wording issues)
- MCP tool alignment:
- README restructured with collapsible feature sections
[1.0.2] - 2026-03-29
Deep workspace tooling upgrade: pipeline-based input correction, output filtering, enhanced stdin handling, and multi-endpoint routing.
Added
- Pipeline engine (
internal/pipeline) for pre-parse and post-parse input correctionAliasHandler: normalises model-generated flag casing (e.g.--userId→--user-id)StickyHandler: splits glued flag values (e.g.--limit100→--limit 100)ParamNameHandler: fixes near-miss flag typos (e.g.--limt→--limit)ParamValueHandler: normalises structured parameter values after parsing
- Output filtering via
--fieldsand--jqglobal flags (internal/output/filter.go)--fields: comma-separated field selection for top-level keys (case-insensitive)--jq: jq expression filtering powered bygojqlibrary
StdinGuardfor safe single-read stdin across multiple flags in one invocationResolveInputSourceunified resolver supporting@file,@-(explicit stdin), and implicit pipe fallback@file/@-syntax support for all string-typed override flags in tool commands- Chat helper support for
@fileinput to read message content from files - Tool-level endpoint routing (
dynamicToolEndpoints) for multi-endpoint products - Comprehensive test suites for pipeline handlers, stdin guard, canonical commands, and chat input
Changed
directRuntimeEndpointnow accepts tool name for finer-grained endpoint resolutioncollectOverridesresolves@file/@-for all string-typed flagsNewRootCommandrefactored toNewRootCommandWithEnginewith optional pipeline engineschemacommand no longer hidden (visible in help output)- Default output format changed from
tabletojson
[1.0.1] - 2026-03-28
Backward-compatible feature and security update after the initial 1.0.0 release.
Added
- JSON output support for
dws auth loginanddws auth status - Cross-platform keychain-backed secure storage and migration helpers
- Atomic file write helpers to avoid partial config and download writes
- Stronger path and input validation helpers for local file operations
- Install-script coverage for local-source installs
Changed
- Improved
auth loginhelp text, hidden compatibility flags, and interactive UX - Added root-level flag suggestions for common compatibility mistakes such as
--jsonand legacy auth flags - Updated AITable upload parsing to accept nested
contentpayloads - Refreshed bundled skills metadata for the new CLI version
[1.0.0] - 2026-03-27
First public release of DingTalk Workspace CLI.
Core
- Discovery-driven CLI pipeline: Market → Discovery → IR → CLI → Transport
- MCP JSON-RPC transport with retries, auth injection, and response size limits
- Disk-based discovery cache with TTL and stale-fallback for offline resilience
- OAuth device flow authentication with PBKDF2 + AES-256-GCM encrypted token storage
- Structured output formats: JSON, table, raw
- Global flags:
--format,--verbose,--debug,--dry-run,--yes,--timeout - Exit codes with structured error payloads (category, reason, hint, actions)
Supported Services
- aitable — AI table: bases, tables, fields, records, templates
- approval — Approval processes, forms, instances
- attendance — Attendance records, shifts, statistics
- calendar — Events, participants, meeting rooms, free-busy
- chat — Bot messaging (group/batch), webhook, bot management
- contact — Users, departments, org structure
- devdoc — Open platform docs search
- ding — DING messages: send, recall
- report — Reports, templates, statistics
- todo — Task management: create, update, complete, delete
- workbench — Workbench app query
Agent Skills
- Bundled
SKILL.mdwith product reference docs, intent routing guide, error codes, and batch scripts - One-line installer for macOS / Linux / Windows
- Skills installed to
~/.agents/skills/dws(home) or./.agents/skills/dws(project)
Packaging
- Pre-built binaries for macOS (arm64/amd64), Linux (arm64/amd64), Windows (amd64)
- One-line install scripts (
install.sh,install.ps1) - Project-level skill installer (
install-skills.sh) - Shell completion: Bash, Zsh, Fish