The claudecode channel spawns claude with --setting-sources project to
keep the bot persona neutral, but that also drops the user-level
settings.json env block where third-party provider tools (cc-switch
etc.) store ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN. claude then
fell back to the official login and replied "Not logged in - Please
run /login" for anyone on a relayed model.
Re-expose that env block as process environment for the spawned CLI
(both one-shot and streaming paths) via the channel envFn hook.
Variables already exported by the operator are not overridden, and
user hooks/plugins still stay out of replies.
Fixes PeterGuy326#10.
The codex app-server read loop reports EOF before closing the message
channel, so when the process exits right after its final frame, both
channels are ready and select picks one at random — runTurn could drop
an already-received turn/completed and fail with "stream ended before
turn/completed: EOF" (the CI flake on this PR and #8). Drain buffered
frames first; reproduced at ~7%/300 runs before, 0/300 after.
Five operational gaps that bite when a connector-backed bot answers
questions in real groups:
- Single-instance lock: one connector per robot per machine (pid file,
stale-lock takeover). Duplicate Stream connections on one clientId get
messages load-balanced between them, so the bot answers intermittently
(verified live).
- Access policy: --allowed-users / --allowed-groups allowlists plus a
per-sender rate limit (--user-rate-limit, default 20/min). Every
message is an LLM call; without this anyone who can reach the bot can
spend on its behalf.
- Per-conversation FIFO queue: same-chat messages run in arrival order
(follow-ups need the previous turn's --resume session; parallel CLIs
racing one session corrupt it); different chats stay parallel.
- Picture messages: resolve the callback downloadCode via robot
messageFiles/download to a local temp file and hand the path to the
agent CLI — error screenshots are the top Q&A inbound and were
previously dropped silently. The claudecode persona now permits
reading attached files.
- --knowledge-dir: lightweight local retrieval (markdown chunking,
latin-token + CJK-bigram scoring) prepends top-k matching chunks to
each question while the agent keeps running from the clean scratch
dir, so replies stay inside DingTalk's AI-assistant response window.
All knobs have env mirrors (DWS_ALLOWED_USERS / DWS_ALLOWED_GROUPS /
DWS_USER_RATE_LIMIT / DWS_KNOWLEDGE_DIR) for service-style runs.
go build ./... clean; go test ./internal/helpers -race passes with new
tests for lock/gate/queue/knowledge/media. test/cli_compat, test/scripts
and test/unit fail identically on the unmodified base (pre-existing
fixture issues).
- ConnectOptions.CardTemplate said an empty value falls back to the public
openclaw template, but the code (newAICardClient/hasTemplate) treats empty
as cards-off, and the explicit "public" alias exists precisely for that
opt-in. Align the comment with the actual contract.
- hermes official-channel steps now warn about the trap verified live: a
devapp robot renders replies through the AI-assistant response window, so
a slow agent's plain-text reply gets the Done reaction (HTTP 200) but never
shows up, leaving the loading card stuck. Point users to enabling AI cards
on the hermes side.
Mirror hermes' semantics: without a configured card template, replies go as
plain text/markdown with the Thinking/Done reaction chips (which need no
template and always render); cards activate only when --card-template /
DWS_CARD_TEMPLATE is set ('public' opts into the openclaw shared template
explicitly). This removes the silent-failure trap where every card API call
succeeds but the client cannot render a template it is not authorized for —
the previous default left users staring at 内容加载失败 instead of falling
back to text.
The channel CLIs (claude/codebuddy/qodercli/...) may be missing on the
user's machine. Runtime already preflights at connect (auto-installing
package-manager CLIs, erroring with a hint otherwise), but agents had no
machine-readable way to check BEFORE starting the connector. dry-run now
reports a cli block ({required, installed, path, autoInstall, installHint})
per channel — desktop-app channels and openclaw/hermes report their
onboarding hint. Skill docs gain the agent preflight workflow: check
dry-run first; npm channels may auto-install; desktop-app channels must
have the app installed by the user before connecting.
Card templates are app-scoped — the hermes docs prescribe grabbing the
template ID from YOUR app's AI Card settings in the developer console, and
our live A/B confirmed another app's template renders 内容加载失败. Expose
the template as a flag (default: the openclaw public template, best-effort)
so operators can register an app-owned template for reliable, branded
rendering. Skill docs updated with the developer-console path.
The hardcoded devapp endpoint was checked BEFORE dynamic registration and
edition static/supplement lookups, short-circuiting every configurable
source — the TestDirectRuntimeEndpoint_Devapp*WithoutRegistry tests encode
the intended priority (built-in = last resort so operators can repoint the
product without a rebuild) and have been failing on the branch. Move the
built-in check after Priority 4; env override > dynamic > PAT > edition >
built-in. Also gofmt two files the Lint job flagged.
Bring the official-channel reply experience (hermes/openclaw) to every
stream-bridge channel:
- "🤔Thinking" reaction chip on the user's message while the agent runs,
swapped to "🥳Done" when the reply lands — the hermes text-emotion contract
(POST /v1.0/robot/emotion/reply|recall, emotionId 2659900).
- AI-card reply using the openclaw connector's public template + payload
contract (msgContent + flowStatus state machine). Card templates are
app-scoped: hermes' own template renders "内容加载失败" for any other app —
confirmed by live A/B on the same robot.
- Live streaming into the card where the channel CLI supports it (verified
per CLI): claude/codebuddy stream-json text deltas (thinking deltas
filtered), qodercli stream-json per-turn snapshots; codex/gemini/opencode
stay one-shot. Frames are full-content and throttled at 800ms per card.
- --reply-card flag (default on, env DWS_REPLY_CARD=0) with full fallback
chain: any card failure → plain text/markdown webhook reply; stuck cards
are best-effort marked failed.
- Deliver responses are checked for business-level failures inside HTTP 200
({"result":[{"success":false,...}]} observed live), and every card API
response body is logged for operability.
- workbuddy persona now forbids tool use: headless codebuddy otherwise
stalls on permission gates trying to write memory files.
- Skill docs: reply-card flag, version-publish approver-selection workflow
(check-approval → present approver list to the user → publish --approver).
Known issue under investigation: after a batch of invalid-template test
cards, the reporting device renders all subsequent cards as "内容加载失败"
even though every API call succeeds and identical payloads rendered earlier
the same day — suspected client-side card/template cache poisoning; pending
verification on a second client.
The SDK's default logger is a doNothingLogger, so the connector printed
nothing on connect/reconnect/read errors — a dead connection was
indistinguishable from a healthy idle one. Wire an ILogger that writes
Info/Warning/Error to stderr (debug frames stay silent). Verified live:
'[stream] connect success, sessionId=...' now appears at startup.
Users reported the linked bot feels dumb. Root cause: the forwarder was
hard-pinned to a stateless one-shot haiku run from an empty directory. Expose
the trade-offs as command-set flags instead of hardcoding them:
- --agent-memory (default on): per-conversation session resume. First message
of a DingTalk conversation mints a UUID and passes `--session-id`,
follow-ups pass `--resume`, so multi-turn context survives. Only on CLIs
with addressable sessions (claudecode/codebuddy/workbuddy — verified against
their --help); qodercli has --resume but no --session-id, so the qoder
family stays stateless. Broken sessions self-heal: on forward error the
conversation mapping is dropped and the next message starts fresh.
- --agent-model / DWS_AGENT_MODEL: override the channel CLI's model via each
spec's model flag (replaces claudecode's built-in haiku pin in place).
- --agent-workdir / DWS_AGENT_WORKDIR: run the agent from a knowledge
directory for context, instead of the clean temp dir (which stays the
default to keep cold-start inside DingTalk's reply window).
A DWS_AGENT_CMD override disables flag splicing entirely — that argv is
user-owned and we cannot know which flags it accepts. Dry-run now previews
the effective agent tuning (model/workdir/memory) per channel.
Bring the channel-aware Stream linking ("建联") from PR #407 into the devapp
command tree as `dws devapp robot connect`, so the full lifecycle
(create app -> create robot -> link to a local agent) lives under one
`dws devapp` domain instead of a separate `dws connect` command.
- Reuse, not duplicate: provisioning ("建号") stays on devapp's existing
`robot create/submit/result`; this commit ports only the linking half.
- connect_stream.go: the Go-native in-process Stream forwarder + channel
agent registry, ported verbatim from #407 (package helpers, self-contained).
- devapp_connect.go: trimmed channel routing (resolveConnectChannel /
launchConnector / buildConnectPlan / ...) plus the new subcommand. The
standalone `connect` cobra command, `connect start` and `connect bot create`
are intentionally NOT brought over — the capability is consolidated under
`dws devapp robot`.
- Credentials: `--robot-client-id` / `--robot-client-secret` for an existing
robot, or `--unified-app-id` to auto-fetch via devapp's credentials get.
The flags are named `robot-client-*` (not `client-id`/`client-secret`) to
avoid shadowing the global OAuth client-override persistent flags.
- Adds the dingtalk-stream-sdk-go v0.9.1 dependency.
- Skill docs (devapp.md, robot.md mono+multi) document `robot connect`.
Known follow-up (marked in code + docs): the get_open_dev_app_credentials
response field names used by `--unified-app-id` auto-fetch are not yet
confirmed against the real gateway; the path degrades safely (empty -> fall
back to explicit flags) and must be verified against pre-prod before relying
on it.
Remove the entire code surface of the aiapp (AI application: create/query/modify) product:
- Delete internal/helpers/aiapp.go and its test (the init() static command registration)
- Drop aiapp from generator coverage targets and knownRegistryProducts
- Remove aiapp from the operations-analyst persona services in personas.yaml
- Delete skills (mono references/products/aiapp.md + dingtalk-aiapp multi skill + script)
- Update README/README_zh product tables and counts (19->18 products, 20->19 multi skills) and run_skill_tests mapping
- Add a Removed entry to CHANGELOG
The service-discovery side (envelope/discovery.pre.json, Portal-synced and gitignored) removes the aiapp server block separately.
* fix(drive): drop client-side Content-Type fallback on presigned OSS PUT
The drive helper used to set req.Header["Content-Type"] = fallbackMIME
whenever the prepare_upload response returned an empty headers map.
DingTalk drive uses OSS presigned URLs whose StringToSign is computed
against an empty Content-Type at signing time, so any client-side
Content-Type makes the signature OSS recomputes at PUT time differ
from the server's presignature → HTTP 403 SignatureDoesNotMatch.
This broke every `dws drive upload` for any file whose mime detects
to a non-empty value (image/png, application/pdf, etc.), which is
basically everything in practice.
Reproduction (against DingTalk drive):
dws drive upload --file any.png
→ [1/3] 获取上传凭证 any.png (X 字节, image/png)...
→ [2/3] 上传文件到 OSS...
→ OSS 上传失败 HTTP 403: SignatureDoesNotMatch
Fix: trust the server's headers map as authoritative. Empty map means
"no client-side headers needed" — do not infer and add Content-Type.
Removes the hasContentType / fallbackMIME path entirely; httpPutDriveFile
signature loses the fallbackMIME parameter.
Manual verification:
curl -X PUT -H "Content-Type:" --data-binary @file <same-presigned-url>
→ HTTP 200 (proves the only difference was the client-side Content-Type)
Note: aitable.go's upload-file helper deliberately keeps its
Set("Content-Type", mimeType) call because its OSS endpoint uses a
different signing mode (server includes the client-declared mime in
its signature computation, verified by running aitable upload-file
across 12 file types — all succeed). The two helpers must not be
unified without re-validating both endpoints.
Tests:
- TestHttpPutDriveFile_NoContentTypeWhenServerHeadersEmpty: guards
that an empty server headers map results in no Content-Type on PUT.
- TestHttpPutDriveFile_PassthroughServerHeaders: guards that server
headers (Content-Type, x-oss-*) are forwarded verbatim.
* feat(aitable): unhide attachment upload-file + clarify prepare-only command
AI agents that discover commands only via --help (e.g. Lobster, generic
LLM agents) currently hit a dead end when trying to upload an attachment
to an AITable:
- `dws aitable attachment` exposes only `upload` (prepare-only),
which returns uploadUrl + fileToken but does not actually upload.
- The real one-shot command `attachment upload-file` (which performs
prepare + HTTP PUT + return fileToken automatically) is marked
Hidden:true, so it is invisible in --help output.
Agents that don't read skills/references/products/aitable.md therefore
get stuck after step 1 — they call `upload`, receive an uploadUrl they
have no idea how to consume, attempt various wrong things (e.g. write
the uploadUrl into the record's attachment field as if it were a token),
fail, and fall back to "please use the UI to upload" messages, which
makes dws look broken even though the capability is fully implemented.
This is the same UX gap that lark-cli avoids with its highly visible
`base +record-upload-attachment` command — discoverability via --help
is the difference between "works for any agent" and "only works if the
agent reads our skill docs".
Changes:
- newAITableUploadFileCommand: remove Hidden:true so the command
appears in `dws aitable attachment --help`. Tighten Short to
explicitly mention the 3 steps it bundles. Long now also calls
out the prepare-only sibling and recommends this command as the
default for AI agents.
- newAITableAttachmentUploadCommand (prepare-only): add a Long
description that explicitly states this command is only step 1
of a 3-step flow, lists what an agent must do after (HTTP PUT,
then write fileToken into record attachment field with the exact
[{"fileToken":"ft_xxx"}] shape), and points to upload-file as
the recommended one-shot alternative. Updated Short to flag that
no file is uploaded by this command.
- aitable_upload_file_test: add TestAITableUploadFileCommandIsDiscoverable
to guard against re-introducing Hidden:true. The test includes a
rationale comment explaining the agent-discoverability gap.
The drive Content-Type fix in the preceding commit and this aitable
discoverability fix together restore end-to-end attachment upload
functionality for both human operators and AI agents that only read
--help.
* feat(root): show 'dws upgrade' guidance in dws --help when commands are missing
AI agents (and users) reading `dws --help` see only the discovered MCP
service list and utility command list, with no hint about what to do if
none of the listed commands fit their task. The natural failure mode is
to give up or hack around — but in many cases the right action is just
`dws upgrade`, because new capabilities and bugfixes ship continuously
and a missing command is usually a stale binary issue.
Two changes restore visibility of the root command's Long description so
this guidance can be surfaced:
- internal/app/root.go: set root.Long to a one-paragraph hint —
"if you hit a missing command, an error, or cannot complete the
task, try `dws upgrade` first; both the DingTalk OpenAPI surface
and dws CLI evolve continuously."
- internal/app/root_help.go: the custom SetHelpFunc that renders the
root help (renderRootHelp) replaces cobra's default template, which
had been silently dropping root.Long from --help output. Restore
rendering by appending root.Long (when non-empty) after the command
list, separated by a blank line. This matches cobra's default
behavior for the Long field while preserving the custom services /
utilities sections renderRootHelp introduced.
- internal/app/visibility_test.go: new TestRenderRootHelpIncludesLong
guards against re-introducing the regression. Uses a sentinel Long
string and asserts renderRootHelp output contains it verbatim. If a
future change rewrites the help renderer without preserving Long
rendering, this test fails immediately and reminds the author the
upgrade hint must stay visible.
Verified locally:
$ ./dws --help | tail -3
Use "dws <service> --help" for more information about a discovered MCP
service or "dws <command> --help" for utility commands.
提示: 如果遇到能力缺失、命令报错、新功能未注册、或无法完成任务,
请先用 'dws upgrade' 升级到最新版本后再试. 钉钉 OpenAPI 和 dws CLI
持续迭代, 新能力和 bugfix 会先在新版本上线.
`send_message_as_user`'s schema also marks `title` as required, but
`buildChatMessageSendInvocation` only pre-validated it for direct
messages. Sending `dws chat message send --group <cid> --text "1"`
without `--title` therefore reached the API and returned the same
misleading `发群服务窗会话消息失败` business error that #250 fixed for
direct messages, just on the other branch.
Pre-validation now covers both branches: group sends without title
return `--title is required for group messages (--group)`, direct sends
keep the existing `--title is required for direct messages (...)`
message. `Long` help, the `--title` flag description, the first
`Example` line, and `skills/references/products/chat.md` (including the
deeper "drive → chat" workflow example) are realigned to
"群聊与单聊都必填". `internal/helpers/chat_test.go` gains a
`group-without-title` case, and the existing `group` / `positional-text`
success cases are updated to pass `--title`. No API request shape change.
* fix(chat): require --title for direct messages, fix misleading help
`dws chat message send --user <id> --text ...` (and the --open-dingtalk-id
variant) failed at the API layer with the cryptic "发群服务窗会话消息失败"
when --title was omitted, because send_direct_message_as_user requires a
title at the business layer. The CLI advertised the opposite: the --title
flag description said "可选" (optional) and one help example sent a direct
message without it.
- Validate --title up front for --user / --open-dingtalk-id sends:
"--title is required for direct messages (--user / --open-dingtalk-id)".
Group messages are unchanged (title stays optional there).
- Fix the long help, the --title flag description, the help examples, and
skills/references/products/chat.md to say title is required for direct
messages, optional for group messages.
- Tests: add --title to the direct-message routing cases that now require
it, and add rejection cases for direct sends without --title.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(changelog): add Unreleased entry for #250 chat send --title fix
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(attendance): add --stats-type flag to summary to fix C0002 error
The MCP tool get_attendance_summary requires statsType in QueryUserAttendVO
at the server-side business layer (DingTalk schema marks it required:[] but
service rejects with C0002 'statistics type error' when omitted).
Changes:
- internal/helpers/attendance.go: add --stats-type flag (week/month);
conditionally write statsType into QueryUserAttendVO when provided;
update Long description and flag help to mark --stats-type as required
- test/cli_compat/attendance_test.go: add execSummaryDryRun helper and
3 new tests verifying statsType is correctly threaded through VO
- skills/references/products/attendance.md: document --stats-type flag
with warning about server-side mandatory enforcement
Verification:
- go build ./... PASS
- go vet ./internal/helpers/... ./test/cli_compat/... PASS
- go test -run TestAttendanceSummary_should_pass_stats_type_* PASS (3/3)
- go test -run TestAttendanceSummary_should_not_pass_stats_type_* PASS
- Real CLI: dws attendance summary --stats-type month success:true
(C0002 fully gone; full attendance data returned)
Closes#227
* fix(attendance): enforce --stats-type as required at CLI layer (review feedback)
Address review feedback from #228:
1. Fail-fast validation: --stats-type is now required at the CLI layer
instead of being conditionally written into VO. Previously the doc
said 'required' but RunE silently let omission through, causing the
same C0002 the fix was supposed to prevent.
2. Enum validation: only 'week' or 'month' are accepted; any other value
is rejected at the CLI layer instead of being forwarded to the server.
3. Cosmetic: split the single-line Long description into multiple lines
for readability.
Test changes:
- Replaced TestAttendanceSummary_should_not_pass_stats_type_when_omitted
with two semantically-correct tests:
* TestAttendanceSummary_should_error_when_stats_type_missing
* TestAttendanceSummary_should_error_when_stats_type_invalid
- Updated other TestAttendanceSummary_* tests to pass --stats-type=month
to match the new fail-fast contract.
- Renamed should_pass_only_user_flag to should_pass_user_id_through_vo
(the old name no longer makes sense now that --stats-type is mandatory).
Skill doc:
- skills/references/products/attendance.md: clarified that the CLI now
rejects missing/invalid --stats-type at the client side (won't even
reach the server).
Verification:
- go build ./... PASS
- go vet ./internal/helpers/... ./test/cli_compat/ PASS
- go test -run 'TestAttendanceSummary_should_(pass_stats_type|error_when_stats_type)' -v
PASS (4/4)
- dws attendance summary --help new layout shown
---------
Co-authored-by: 猷诺 <bicheng.ybc@alibaba-inc.com>
- chat message send-by-bot --robot-code/--title/--text: append (必填) marker
(RunE already enforces these as required; help text was missing the tag,
causing downstream MCP wrappers to generate schemas with required fields
missing).
- report create --contents: description appended "key must exactly equal the
template field_name (look it up via report template detail --name <template>)";
Long now warns about the API's SYSTEM_ERROR on key mismatch; Examples
rewritten as a two-step pipeline (template detail → create).
Closes#106Closes#107
The todo task get command was calling query_todo_detail which returns
empty results. Changed to get_todo_detail to match the tool name
defined in discovery.json, restoring correct behavior.
Co-authored-by: 猷诺 <bicheng.ybc@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.
The backend MCP tools `send_direct_message_as_user` and
`send_message_as_user` both require `title` as a required field.
However, the skill documentation and --help Long text described
--title as "optional", causing AI models to omit it and trigger
a vague `business error: success=false`.
Update descriptions in:
- skills/references/products/chat.md
- internal/helpers/chat.go (command Long text)
Closes#173
* 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
* fix(compat): accept YAML flow as a fallback for json_parse transform
Users who run `dws aitable table create --fields [{fieldName: 标题, ...}]`
hit a shell glob error (`zsh: bad pattern: [{fieldName:`) because the
unquoted brackets are interpreted as a zsh pattern. Even when the shell
is placated with outer quotes, the strict-JSON parser still rejected the
natural form that drops quotes around keys and string values.
transformJSONParse now tries strict JSON first (unchanged fast path) and,
on failure, falls back to yaml.Unmarshal. YAML's flow syntax is a
superset of JSON that accepts `{key: value}` without surrounding quotes,
so a single set of outer quotes is all the user needs:
dws aitable table create --fields '[{fieldName: 标题, type: text}]'
Already-valid strict JSON is unaffected. The @file syntax (`--fields
@path.json`) continues to work through the same transform.
The terminal error message, if both parsers fail, now points users at
the two working forms (quoted YAML flow or @file).
* fix(helpers): route chat send-by-bot to bot product
`chat message send-by-bot` previously stamped its invocation with
CanonicalProduct="chat", routing it through the chat product's auth /
server dispatch. The command is semantically a bot operation — switch
it to "bot" so auth client selection and downstream routing match the
actual MCP server. Extends chat_test.go with single-chat and group-chat
routing assertions.
- Implement create/update/done/get/delete with preferLegacyLeaf to replace
dynamic commands; align MCP tools with production (create_personal_todo,
update_todo_task, update_todo_done_status, query_todo_detail, delete_todo)
- Add ISO-8601 due time parsing, hidden title aliases, delete confirmation
- Extend zh/en i18n for todo strings; fix multiple en.json spacing/wording issues
- Expand cli_compat todo tests; adjust .gitignore for cli_compat testdata
Made-with: Cursor
- Support schema output in table format for human-readable display
- Add product-level endpoint loading in CLI loader
- Wire schema-text rendering into runner output pipeline
- Add priority field support for todo task helper