Unify runtime morphology on pkg/cmdutil.Morph (same function the generator uses),
add a SemanticAliasHandler that looks up the embedded generated table after
morphological normalization and rewrites synonyms to the command's canonical flag
(leaving blocked/ambiguous synonyms untouched for the did-you-mean path), and
thread the command CLIPath through the pipeline Context. Pilot the mechanism on
'calendar event list' by removing its hand-written hidden spelling variants; a
behaviour-preservation test locks the outcome.
Plain Required now validates the effective value (primary flag -> aliases
-> env) instead of only the primary flag, matching the declared fallback
semantics; whitespace-only values under Trim count as missing. Extracted
cmdutil.MissingRequiredFlagsError to keep the unified error format.
* fix: honor global --jq/--fields on product commands; round-2 QA fixes
Make the global --jq / --fields output filters actually work for the
product (MCP) commands. The helper Formatter used by every product
command ignored them, so they were silent no-ops there (they already
worked for `dws api`). Expose Fields()/JQ() on the ToolCaller interface
and apply the existing output.WriteFiltered path in the helper
Formatter's PrintJSON. The handful of bespoke utility commands
(auth/config/profile/...) still encode directly and are documented as
such.
Additional CLI fixes surfaced by the second real-machine QA pass:
- sheet write-image: emit clean JSON under --format json (suppress the
progress lines that leaked onto stdout, same as media-upload/export)
- sheet range batch-set-style: under --format json, collect per-item
results into a single JSON object instead of printing N separate ones
- chat download-media: create the output directory when missing and
strip URL-encoded path separators from the inferred filename so the
file actually lands instead of failing on a missing subdirectory
- pat chmod, aitable, sheet, chat, attendance: correct --help text
(real scope names, non-existent subcommands, flag requiredness,
alxs -> axls typo)
Helper scripts (mono and multi):
- minutes_extract_todos: parse dingtalkTodoList/actions (there is no
todos key), so todos are no longer silently dropped
- sync the multi copies of chat_export_messages / chat_history_with_user
(were crashing with AttributeError), minutes_list_parse /
minutes_recent_summary, and calendar_free_slot_finder to the fixed
mono versions
Skill docs (mono and multi): correct return-structure keys, flag names,
deprecated command routing (doc download -> drive download), enum values
and server-side limitations across products; update the global
reference to note --jq/--fields now apply to product commands.
* fix(skill): make skill setup --dry-run a no-op preview; doc/help fixups
skill setup ignored the global --dry-run flag and always wrote the skill
files (overwriting an existing install). Short-circuit into a preview
that lists the source, target dirs and selected sub-skills without
touching the filesystem.
Also correct a few doc/help mismatches found in the round-3 health check:
- attendance vacation balance/records quick-reference examples were
missing the required --leave-code flag
- mail mailbox list --help described the returned field as "mailboxes"
but the real field is "emailAccounts"
* docs: clarify --fields projects top-level/list keys, use --jq for nested
* docs: drop QA voice ("真机") and don't state env-specific quirks as absolute rules
The QA-driven doc/comment edits leaked test-process narration ("真机实测")
and this environment/account's quirks stated as universal rules into the
skill files, which are general-purpose instructions for any org/account.
Strip the "真机" narration everywhere; reword environment-specific findings
(PUBLIC sharing disabled by org policy, transient 1002, sender-open-dingtalk-id
behaviour) from absolute bans into conditional hints; keep genuinely
universal command behaviour, just without the QA voice.
Override leaves whose backing MCP tool isn't actually deployed rendered in
`dws <svc> --help` but failed at invocation with "tool not found" (43 phantom
commands across 391 overrides; attendance declared 38, only 4 deployed).
BuildDynamicCommands now takes an existingTools oracle (CLI slug -> live tool
set from the tools/ cache). A leaf whose tool is missing from its resolved
server's set is marked Hidden; groups left childless collapse. Safety rails:
acts only when a server's tool set is KNOWN and non-empty (cold cache / overlay
/ plugin paths pass nil and no-op, never blanking the tree); serverOverride
leaves resolve against the target server; pipeline leaves are never hidden.
Adds scripts/dev/check-phantom-overrides.py as a publish-time gate, and
phantom_guard_test.go covering hide / cold-cache-keep / serverOverride / pipeline
/ empty-group-collapse.
Switches the discovery version code bamboo -> cedar and aligns the open edition CLI with dws-wukong across communication (calendar book/acl/attendee, minutes tag, mail folder/template/contact, chat file upload, todo add-attachment, attendance transforms) and structured-office (aitable advperm/view/section/workflow/record, sheet/drive/wiki/doc) domains. Includes output-envelope parity, parse_bool/attendance_class_check_time transforms, --calendar-id support, CHANGELOG 1.0.43 and README command-index refresh. cedar config validated on pre and prod endpoints.
Attach the clawType tool argument to every user-identity send path
(send_personal_message text/rich-media/reply and
send_direct_message_as_user) so the IM server can render the
"Send from AI" indicator on delivered messages. The value comes from a
new edition hook (Hooks.ClawTypeValue, exposed via edition.ClawType())
that falls back to DefaultOSSClawType ("openClaw"); overlays such as
wukong set their own identity to get their branded indicator.
Also fixes the reply command, which hardcoded clawType="wukong" and
made open-source replies carry the Wukong AI identity.
Bot sends intentionally stay untouched: they already render as bot
messages and must not carry the user-identity claw tag.
* fix(auth): partition app.json filename by edition to isolate credentials
Two dws binaries sharing the same config directory (typically ~/.dws or
DWS_CONFIG_DIR) previously read and wrote a single app.json. Editions
that pin AuthClientID via hooks still go through the open-core
post-login persistence path, which records a bare ClientID without a
paired ClientSecret. The sibling edition reading the same path would
then adopt that foreign clientID via ResolveAppCredentials.
Mirror the strategy already used by the cache loader
(pkg/config.EditionPartition): GetAppConfigPath returns a filename
suffixed with the active edition name. Open-source keeps "app.json" for
backwards compatibility; sibling editions land on "app-<edition>.json".
LoadAppConfig / SaveAppConfig / HasAppConfig / DeleteAppConfig all
route through GetAppConfigPath, so this single change physically
isolates credential files end-to-end without any read-time heuristics.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(auth): address app config partition review
* fix(auth): clean legacy sibling app config
* test(auth): cover legacy app config cleanup guards
* fix(auth): close app config review follow-ups
---------
Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: update coverage badge [skip ci]
* chore: update coverage badge [skip ci]
* chore: update coverage badge [skip ci]
* chore: update coverage badge [skip ci]
* chore: update coverage badge [skip ci]
* 1.0.19 changelog
* stick opt
* fix(cli): utf-8 safe sticky suffix guard + changelog (#272)
SuffixLooksLikeValue used to read suffix[0] (a single byte) for both the
uuid format branch and the fallback "is the first rune a letter?" check.
For multi-byte UTF-8 leading runes — common in dws because value text is
often Chinese — this picked up only the first byte (0xE0..0xF4 lead),
which is not a letter and not a hex digit, so the function silently
returned true and let glued tokens like --name<CJK> get split into
--name <CJK>... This switches both branches to utf8.DecodeRuneInString
and adds a utf8.RuneError guard so invalid UTF-8 input is rejected too.
Also locks down the new behaviour in CHANGELOG ## [Unreleased]:
- Changed: schema-aware sticky flag splitting
- Added: available_flags field on unknown-flag errors
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(pat): A-core minimum viable loop for host-owned PAT
Enables third-party agents to integrate PAT via the smallest possible
contract surface. The flow a host needs to ship end-to-end:
1. Export DINGTALK_DWS_AGENTCODE in the spawned shell.
2. Run any `dws ...` business command.
3. On PAT hit, CLI exits with code 4 and writes a single-line stderr
JSON following docs/pat/contract.md §2.
4. Host parses the JSON and invokes `dws pat chmod <scope>...
--agentCode ... --grant-type ...` to grant.
5. Host replays the original command.
This PR intentionally ships ONLY the chmod path plus the host-owned
switch; the async flow (apply / status / scopes + authRequestId
registry + PAT_SCOPE_AUTH_REQUIRED active-request branch) lands in a
follow-up stacked PR (A-ext).
Scope of A-core:
- Host-owned PAT trigger: auth.HostOwnsPATFlow() keyed exclusively on
DINGTALK_DWS_AGENTCODE (DINGTALK_AGENT / DWS_CHANNEL / claw-type do
NOT participate).
- claw-type: hard-wired to "openClaw" in pkg/edition/default.go
MergeHeaders hook, matching historical main behavior and decoupled
from DINGTALK_AGENT.
- dws pat chmod: factory-built (newChmodCommand) so the PAT subcommand
tree has no shared package-level caller.
- stderr JSON classifier: internal/errors/pat.go covers PAT_NO_PERMISSION
/ PAT_LOW_RISK / PAT_MEDIUM_RISK / PAT_HIGH_RISK / PAT_SCOPE_*
selectors and fills data.hostControl for host consumption.
- Env contract: DINGTALK_SESSION_ID / DINGTALK_TRACE_ID /
DINGTALK_MESSAGE_ID carry HTTP trace headers (Chain B);
DWS_SESSION_ID (+ REWIND_SESSION_ID alias) is the lone fallback for
`dws pat chmod --session-id` (Chain A). The two chains are
independent; aliases do not cross-pollinate.
- Docs: docs/pat/{README,contract,host-integration}.md + refreshed
docs/architecture.md "PAT Architecture" chapter + docs/reference.md
PAT section. error-catalog.md folded into contract.md §6.
- Tests: host-owned signal, stderr contract, classifier, chmod factory,
retry / poll loop for the CLI-owned chmod path.
Compatibility: no breaking changes on main. All non-PAT commands are
untouched.
Follow-up PRs (in order):
- codex/pat-ext : dws pat apply / status / scopes + AsyncRegistry.
- codex/pat-oss-refactor (aka PR B) : oauth_helpers / secure_store
refactors split out per code review.
Made-with: Cursor
* fix(pat): unify host-owned stderr contract
* revert: drop PAT docs and changelog from pat core PR
* fix(pat): pin behavior auth endpoint
* fix(pat): recognize legacy tool miss
* fix(pat): fallback on gateway diagnostics
* fix(pat): treat authorization uri as opaque
* fix(pat): accept result envelope in device-flow polling
* feat(pat): add browser policy and poll compatibility
* fix(pat): keep CLI and host PAT contracts separate
* test(pat): isolate opaque uri retry env
* fix(pat): harden PAT result routing
* fix(pat): harden host-owned flow and runtime fallback
* fix(pat): preserve chmod failure on empty grant result
* refactor(pat): drop dead doc anchors, dedupe stderr injection helpers
Address two reviewer findings on top of the PAT-core series.
Fix:
- chmod: resolveSessionIDFromEnv silently selects DWS_SESSION_ID; drop
the slog.Warn that emitted both raw session ids into stderr /
~/.dws/logs.
- Remove every docs/pat/contract.md / error-catalog.md /
host-integration.md anchor from comments, help, and tests; those
files were never added by this branch. Comments are now self-contained
or point to docs/reference.md where applicable.
Refactor:
- internal/errors/pat.go: extract lookupCodeIn so getPATErrorCode and
getDWSGatewayErrorCode share one traversal; have ClassifyPatAuthCheck
delegate to getPATErrorCode instead of repeating the enum walk.
- internal/errors/pat.go + internal/app/pat_auth_retry.go: extract
ApplyHostMutations as the single injection point for data.hostControl
+ data.openBrowser; cleanPATJSON and enrichPATErrorForHostControl now
share it so the two stderr-JSON write paths cannot drift.
- internal/app/pat_auth_retry.go: drop buildPATScopeHostJSON and
buildHostControlState dead 1-line wrappers.
- internal/pat/chmod.go: drop unused patApply/Status/Scopes constants;
derive legacyToolArgs from toolArgs by clone + scopes->scope rename so
the two payloads stay in lock-step on every other field.
Net 15 files, +176 / -193. Verification:
env -u DINGTALK_DWS_AGENTCODE go test \
./internal/errors ./internal/auth ./internal/pat ./test/unit
env -u DINGTALK_DWS_AGENTCODE go test ./internal/app \
-run 'Test(IsPat|ExtractPat|PrintPat|PollPat|HandlePat|RetryWithPat|EnrichPAT|BuildPAT|DirectRuntime|ResolveIdentityHeaders)'
env -u DINGTALK_DWS_AGENTCODE go test -race \
./internal/pat ./internal/errors ./test/unit
env -u DINGTALK_DWS_AGENTCODE go test -race ./internal/app \
-run 'Test(HandlePat|RetryWithPat|EnrichPAT|BuildPAT|ResolveIdentityHeaders|DirectRuntime)'
go vet ./... && go build ./...
All green.
Made-with: Cursor
---------
Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
* fix(chat,cmdutil): restore group @-mentions and members list subcommand
Two CLI regressions surfaced after PR #170 promoted hardcoded helpers over
the discovery envelope:
1. issue #177 — `dws chat message send --group ... --at-users ...` failed
with `unknown flag: --at-users` on v1.0.16. The hardcoded helper that
replaced the v1.0.15 envelope leaf only declared --group / --user /
--open-dingtalk-id / --text / --title and silently dropped the envelope's
--at-users / --at-all / --at-mobiles flags, so any group @-mention call
bounced at cobra's flag parser before ever reaching the MCP tool.
Add the three flags back to `newChatMessageSendCommand` and forward
atUserIds / isAtAll / atMobiles to `send_message_as_user` in the
`--group` branch; reject the flags loudly outside `--group` to avoid
silently swallowing user intent in single-chat mode (single-chat tools
have no @-mention semantics).
2. issue #164 — `dws chat group members --id <openConversationId>` returned
`unknown flag: --id`. The helper had `members` as a "group with bare
RunE + --id/--cursor flags" while the envelope publishes `members` as a
leaf for `get_group_members`; the merge layer's shape-mismatch branch
treats envelope as authority and silently drops the entire helper
subtree (list / add / remove / add-bot all become unreachable).
Restructure helper-side `members` into a pure group container and
promote list to its own explicit subcommand:
dws chat group members list --id <openconversation_id>
And extend `pkg/cmdutil/MergeHardcodedLeaves` so the existing
OverridePriority annotation also applies to the leaf↔group shape
mismatch — when the helper group carries strictly higher priority it
replaces the dynamic leaf, mirroring the leaf↔leaf override semantics
that PR #170 already established. Without this the helper's `list`
subcommand stays unreachable and the user-visible bug persists.
Tests:
- `internal/helpers/chat_test.go`: forwards-at-mentions (group + each
flag variant), rejects-at-mentions-outside-group (single-chat
validation), members-list-subcommand (structure + execution).
- `internal/app/legacy_test.go`: pickCommands integration test that
reproduces the #164 shape mismatch end-to-end and asserts the helper
subtree survives.
- `pkg/cmdutil/leaf_merge_test.go`: positive + boundary (equal priority)
cases for the new leaf↔group promotion path.
Verified end-to-end against existing chat helper tests, app pickCommands
tests, cmdutil merge tests, cobracmd priority tests, compat dynamic
command tests — all green.
Skill docs (`skills/references/products/chat.md`) updated to teach the
new `members list` path with a migration note, and to surface the
restored `--at-mobiles` flag alongside `--at-all` / `--at-users`.
Closes#177Closes#164
* fix(chat): resolve merge conflict in command Long text
Combine --title-required wording from #174 with at-mention
documentation introduced in this PR. CI build was failing on
unresolved <<<<<<< / >>>>>>> markers left in chat.go.
* fix(app): graft hardcoded helpers into dynamic tree instead of dropping
pickCommands previously dropped an entire helper subtree whenever a
top-level product name collided with the dynamic overlay. The intent was
to keep the discovery envelope as runtime authority for leaves it
declared, but the side effect was that every helper-only sibling
disappeared too — commands like `chat message send-by-bot`,
`chat message recall-by-bot`, `chat message send-by-webhook`, and
`chat group members add-bot` had silently vanished from the open
edition since the pickCommands introduction, making PR #161's
send-by-bot routing fix unreachable for open-source users.
Switch to cmdutil.MergeHardcodedLeaves for same-named products: the
dynamic side still wins every leaf conflict, and helper-only subtrees
are grafted into the dynamic tree. Envelopes remain authoritative for
leaves they declare; helpers once again fill the gaps they don't.
Verified on a freshly built open-edition binary:
dws chat message send-by-bot --robot-code ... --users ... --text ...
returns a real processQueryKey and routes through canonical_product=bot
via helper_override, matching the v1.0.15 baseline.
* fix(chat,cmdutil): route chat message send by destination flag
`dws chat message send --user <userId>` failed with `不合法的参数` because
the envelope-generated dynamic command maps every destination to the
group-only tool `send_message_as_user`. Only `--group` was viable on the
open edition; single-chat (`--user`, `--open-dingtalk-id`) has never
worked end-to-end.
Fix is in two parts:
1. Add a hardcoded `chat message send` helper that dispatches by
destination flag — `--group` → `send_message_as_user`, `--user` /
`--open-dingtalk-id` → `send_direct_message_as_user`. This mirrors the
closed-source wukong overlay's chatMessageSendCmd so the open edition
lines up with the rest of the product.
2. Let hardcoded leaves opt into overriding the dynamic envelope in
`cmdutil.MergeHardcodedLeaves` when they carry a strictly higher
OverridePriority. The default remains "envelope is authority"; the
opt-in exists for the narrow case where the envelope exposes a single
dispatch path but the hardcoded leaf needs richer flag-based routing
(as here). The new send helper uses preferLegacyLeaf (priority 100)
so it wins against the dynamic leaf.
OverridePriority/SetOverridePriority move to pkg/cmdutil as the new
source of truth; internal/cobracmd/priority.go now delegates so the
annotation key stays single-sourced across the merge layer and helpers.
Verified end-to-end against a freshly built open-edition binary:
send --user 034766 --text … → send_direct_message_as_user → success
send --group <cid> --text … → send_message_as_user → success