Compare commits

...
Author SHA1 Message Date
赤川 aa4ae9a903 Merge pull request #1063 from DingTalk-Real-AI/codex/fix-996-multi-profile-skill-path
fix(ci): align multi-profile skill paths with canonical setup
2026-08-20 10:53:27 +08:00
chichuan 7a019c6fa3 fix(ci): align multi-profile skill paths 2026-08-20 10:46:17 +08:00
赤川 e2e4d6fc22 Merge pull request #1062 from DingTalk-Real-AI/codex/changelog-v1.0.59-beta.4
chore: prepare v1.0.59-beta.4 changelog
2026-08-20 10:30:46 +08:00
chichuan 2c0d6e4118 chore: prepare v1.0.59-beta.4 changelog 2026-08-20 10:25:13 +08:00
github-actions[bot] 379f625ca9 Merge pull request #1045 from DingTalk-Real-AI/codex/attendance-mail-shortcuts
feat(shortcut): harden Attendance and Mail workflows
2026-08-20 02:07:16 +00:00
赤川 9d27313f5e Merge branch 'main' into codex/attendance-mail-shortcuts 2026-08-20 09:47:51 +08:00
github-actions[bot] cc86d1e958 Merge pull request #1043 from guimingyue/oa_approval_list_by_admin
feat(oa): add approval list-by-admin with string time contract
2026-08-20 01:47:23 +00:00
mygui 5619cb150e Merge branch 'main' into oa_approval_list_by_admin 2026-08-20 09:28:43 +08:00
github-actions[bot] 5aaf2efb59 Merge pull request #1060 from DingTalk-Real-AI/codex/govern-command-path-migrations
ci: govern Help and Schema command migrations
2026-08-20 02:04:43 +08:00
chichuan d583247935 test(ci): cover command governance branches 2026-08-20 01:50:13 +08:00
chichuan dfc3b028d7 fix(ci): prove extracted command constants end to end 2026-08-20 00:53:23 +08:00
chichuan 95da8214a1 test(ci): reject command parameter target collisions 2026-08-19 23:24:29 +08:00
chichuan d8a3d5d6fd fix(ci): close command migration governance gaps 2026-08-19 22:57:11 +08:00
chichuan 5ed7c3adce Merge remote-tracking branch 'origin/main' into codex/govern-command-path-migrations 2026-08-19 22:37:21 +08:00
github-actions[bot] d1f1ab724b Merge pull request #1058 from Anonymity-0/feat/chat-group-role-single-flag
feat(chat): expose single group role flag
2026-08-19 22:16:58 +08:00
前津 67505c6c83 Merge remote-tracking branch 'upstream/main' into feat/chat-group-role-single-flag 2026-08-19 21:55:58 +08:00
github-actions[bot] 61c39efb85 Merge pull request #996 from typefield/fix/canonical-agent-skills
fix(skills): adopt canonical global installation
2026-08-19 21:48:33 +08:00
前津 5b412ac196 test(chat): cover role flag resolver branches 2026-08-19 21:48:28 +08:00
玉澜 c0e579fe6b fix(skills): prove backup ownership before adopting or pruning stamp roots
A stamp-shaped directory name is not ownership proof: pruneSkillBackups
counted and RemoveAll'd any 20260819-120000-shaped entry under
~/.dws/skill-backups, so a user or tool that created such a directory
lost its contents once DWS held five backups, and the Go backup path
(MkdirAll) adopted a same-named foreign root outright. The PowerShell
installers already implemented the correct contract; every other
surface now matches it.

Go stamps a freshly created root with the exact marker bytes the
install scripts write (.dws-skill-backup = "dws skill backup v1")
before any payload moves in, claims the root with mkdir so an existing
unproven root bumps to a collision suffix instead of being adopted,
and prunes only roots whose marker verifies — unmarked or wrongly
worded stamp-shaped directories are foreign data, preserved and never
counted against the keep limit. The shell installers (install.sh,
install-skills.sh, install-event.sh, install-devapp.sh) and the npm
installer apply the same rule in their backup collision loops, with
roots recorded as created by the running process exempt from marker
re-verification so a mid-run marker permission failure still reuses
this run's own root and keeps the sibling payload intact.

Regression tests cover every surface: pruning an unmarked/wrongly
marked stamp-shaped directory alongside marked ones, refusing to adopt
a foreign root (payload moves to a suffixed root, foreign data and its
nonexistent marker untouched), same-stamp reuse of a proven root, and
marker-write failure cleaning the empty fresh root.
2026-08-19 21:29:47 +08:00
前津 c2ff4ab242 test(chat): cover missing group role flag 2026-08-19 21:26:52 +08:00
前津 3fa85d19c9 docs: add group role flag release fragment 2026-08-19 21:22:55 +08:00
前津 33b76400bf chore(policy): consume group role flag migration 2026-08-19 21:01:34 +08:00
Anonymity-0 2b417e2f2a Merge branch 'main' into feat/chat-group-role-single-flag 2026-08-19 20:51:06 +08:00
chichuan c0e1ec576a ci: govern command path migrations 2026-08-19 20:48:43 +08:00
玉澜 2b3f482fdc Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills-p1 2026-08-19 19:58:56 +08:00
赤川 f79c066806 Merge branch 'main' into oa_approval_list_by_admin 2026-08-19 19:51:24 +08:00
玉澜 95645e4f2c fix(skills): no-clobber child moves in POSIX standalone publishers
copy_tree published staged children with mv, which replaces a
concurrently created same-name directory (POSIX rename succeeds over an
empty target) and whose rollback moved every dest child back — including
a concurrent writer's different-named entries — before deleting the
staging tree. Children now publish through kernel-level no-clobber
primitives (mkdir claim + recursion for directories with the recorded
mode restored, ln for regular files, ln -s for symlinks), a manifest
records exactly what this transaction published, the rollback retracts
only those entries in reverse order, and each level re-counts the
destination so a foreign different-named entry aborts the publish with
the destination retained. Read-only staged directories (0555 skill
trees) are made owner-writable for the move; the backup restore uses the
same discipline so a concurrent writer is refused without partially
draining the backup.

Regression tests cover both scripts: a concurrently created same-name
empty child directory and a different-named foreign entry mid-publish
are retained with the original backup kept; both fail against the
previous mv-based implementation.
2026-08-19 19:46:47 +08:00
前津 575303a5b0 docs(skill): remove stale group role flag guidance 2026-08-19 19:40:41 +08:00
github-actions[bot] d9b728f8e5 Merge pull request #1059 from Anonymity-0/feat/chat-group-role-flag-migration-approval
chore(policy): approve group role flag migration
2026-08-19 19:33:13 +08:00
前津 6240584ae2 chore(policy): approve group role flag migration 2026-08-19 19:13:40 +08:00
玉澜 cb46823280 Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills-p1 2026-08-19 19:08:24 +08:00
玉澜 fdcaa61587 test(skills): cover child-move edges for the 100% changed-code gates
The platform coverage gates execute only TestCrossPlatformCoverage-named
tests, so the child-move error and dispatch branches that the full local
suite covered incidentally were reported as uncovered changed code on
Windows (96.78% vs the 100% target). Adds a seam-driven edge suite for
the child-move fallback — source/claim/child stat and read failures,
per-child link and symlink collisions and publish failures, rollback
rename failure, foreign-entry abort, mode-restore failure, source shell
removal failure, nested-directory and simulated-symlink children, and
post-rename content drift — plus the retained-destination notice for a
dependent uncertain target in skill setup. The POSIX file identity impl
now consults the lstat seam so its degradation branches are coverable
the same way. Verified against the gate's own changed-line computation:
zero uncovered changed statements in internal/upgrade.
2026-08-19 18:54:04 +08:00
mygui a0495c169b Merge branch 'main' into oa_approval_list_by_admin 2026-08-19 18:50:35 +08:00
Anonymity-0 3e481d296c Merge branch 'main' into feat/chat-group-role-single-flag 2026-08-19 18:49:59 +08:00
前津 22f87296cd fix(chat): close role flag resolver 2026-08-19 18:46:38 +08:00
github-actions[bot] c198d8577d Merge pull request #976 from H3java/feat/recruit-job
feat: 新增招聘职位管理 to#85340676
2026-08-19 10:30:55 +00:00
玉澜 33828b7858 Merge remote-tracking branch 'fork/fix/canonical-agent-skills' into fix/canonical-agent-skills-p1 2026-08-19 18:28:04 +08:00
玉澜 0c80aa79e5 test(skills): make replacement-identity tests deterministic on Windows
The publish-confirmation and tunneled-replacement tests physically
removed and reseeded the destination to simulate a concurrent swap. On
NTFS the recreation can immediately reuse the freed MFT record, making
the file ID (volume serial + file index) compare equal and the proof
pass against a replaced object — the Windows coverage gate observed the
confirmation falling through to the fingerprint branch instead of the
identity branch. Both tests now force the replacement through the two
primitives the platform proof consults (os.SameFile on Unix, the file-ID
seam on Windows), matching the technique the tunneled-rollback case
already used for Unix inode recycling.
2026-08-19 18:27:39 +08:00
john da62d11b35 Merge branch 'main' into fix/canonical-agent-skills 2026-08-19 18:13:21 +08:00
赤川 a5d7fd05f1 Merge branch 'main' into feat/recruit-job 2026-08-19 18:12:36 +08:00
玉澜 cf13026f48 fix(skills): drop stale Statx identity test from merged remote line
skill_publication_identity_linux_test.go pinned the remote line's
Statx/birth-time identity design (skillPathStatx seam); the merged head
proves ownership with dev:ino plus the fingerprint backstop instead, so
the test no longer compiles on Linux. Caught by CI's Linux lint job,
which builds what macOS-local vet skips behind the linux build tag.
2026-08-19 18:10:23 +08:00
mingyue.gmy 20c901d7ae fix(oa): require processCode in list-by-admin --request payloads
- Reject --request payloads with a missing, empty, or non-string
  processCode; the backend answers a bad processCode with success:true
  and an empty list, so validate client-side like startTime
- Add regression cases to keep changed-code coverage at 100%
2026-08-19 18:06:27 +08:00
玉澜 7a858b9732 Merge remote branch (main evolution + npm/Shell no-clobber) into p1
Reconciles the two parallel evolutions of PR #996 with this session's
publication design as authoritative:

- internal/upgrade, internal/app: ours — mkdir-claim identity witness
  (dev:ino on POSIX, volume file ID on Windows), three-state ownership,
  ErrSkillPathPublicationUncertain, copy-fallback short-circuits. Drops
  the remote line's xattr publication-mark design and its six follow-up
  fixes (retract contracts, Statx token); skill_publication_mark_*.go
  removed accordingly.
- scripts/, build/npm/, test/scripts/, docs/rfc: theirs — same replayed
  install hardening plus main's evolution and the npm no-clobber child
  moves; no xattr dependency, consistent with the claim model.
- .changes: their npm/Shell/PowerShell narrative with the Go-design
  sentences rewritten for the uncertain-publication contract.

Verified: go build, go vet (tests compiled), gofmt, and package tests
for internal/upgrade, internal/app, test/scripts all green on this tree.
2026-08-19 18:06:06 +08:00
github-actions[bot] 00c337c438 Merge pull request #1052 from DingTalk-Real-AI/codex/fix-chat-user-mentions
fix(chat): preserve mentions and route direct media uploads
2026-08-19 18:05:50 +08:00
玉澜 27aca3ccc3 fix(skills): close no-replace fallback TOCTOU and surface uncertain publications
The mkdir->rename->remove->rename directory fallback had a TOCTOU window
between the second remove and the second rename: a concurrent writer
creating an entry at the destination was silently clobbered. The fallback
now claims the destination once with mkdir and never unlinks it: the
fast-path rename publishes over the claim (Linux), and platforms that
refuse directory renames (macOS, Windows) move the staged children into
the claim through atomic no-clobber primitives (mkdir/os.Link/os.Symlink),
consuming the emptied source shell on success.

renameSkillPathNoReplace now returns the mkdir-claim identity captured by
the child-move path. PublishSkillPathNoReplace uses it as a three-state
ownership witness: the atomic/fast paths keep the staged-inode proof, the
child-move path proves dest is still the mkdir claim, and a mismatch
reports the new ErrSkillPathPublicationUncertain sentinel with the
destination retained. The witness is real on POSIX now: darwin and linux
report the dev:ino file identity instead of the empty no-op.

Upstream consumers honor the sentinel: the mono/multi upgrade copy
fallbacks no longer retry over an uncertain destination (the retry would
displace the concurrent writer's object), and skill setup reports the
retained destination instead of claiming a rollback.

Rewrites the fallback tests that pinned the removed remove-and-retry flow
and adds regression coverage: concurrent claim entries abort with the
destination retained, wholesale replacement after child-move reports the
uncertain sentinel, staged-set transactions pass the sentinel through,
and both copy fallbacks short-circuit (ablation-verified).
2026-08-19 17:42:20 +08:00
xlb1130 84036678dd Merge branch 'main' into fix/85564002-chat-group-role-single-flag 2026-08-19 17:26:10 +08:00
长真 d5031a89f0 fix(chat): reject multiple public group role ids 2026-08-19 17:13:48 +08:00
mingyue.gmy e51ecc04f1 ci: trigger workflow rerun 2026-08-19 17:09:14 +08:00
恋川 ed1ebe5d03 chore: retrigger CI 2026-08-19 16:11:24 +08:00
恋川 0d22a4a1bd Merge remote-tracking branch 'upstream/main' into feat/recruit-job 2026-08-19 14:43:50 +08:00
mygui 586ad0a5df Merge branch 'main' into oa_approval_list_by_admin 2026-08-19 14:41:40 +08:00
克谨 83f3b4f385 Merge remote-tracking branch 'origin/main' into codex/fix-chat-user-mentions 2026-08-19 14:39:42 +08:00
Dennis 6d88c9968e docs(attendance): clarify schedule availability 2026-08-19 14:37:38 +08:00
Dennis 36c61fd8ac fix(attendance): withhold unverifiable schedule query 2026-08-19 14:37:35 +08:00
Dennis 272b6b8a70 test(shortcut): lock public catalog count 2026-08-19 14:37:33 +08:00
Dennis c3328411c9 fix(shortcut): close mail review and schema compatibility 2026-08-19 14:37:31 +08:00
Dennis 83cfd10416 docs(shortcut): record final live review evidence 2026-08-19 14:37:29 +08:00
Dennis 9d43a12e08 fix(shortcut): address Attendance and Mail review findings 2026-08-19 14:37:27 +08:00
Dennis 7900e27946 fix(shortcut): preserve CLI compatibility for unavailable leaves 2026-08-19 14:37:25 +08:00
Dennis 42f54832b0 docs(shortcut): refresh rebased evidence references 2026-08-19 14:37:23 +08:00
恋川 e217901a6b fix(recruit): validate education list filter 2026-08-19 14:37:22 +08:00
Dennis 5f6ca90821 docs(shortcut): sync live evidence and generated lists 2026-08-19 14:37:21 +08:00
Dennis cad437aabd fix(attendance): filter validated record overfetch 2026-08-19 14:37:19 +08:00
Dennis 47d71375f6 fix(attendance): align live identity and availability 2026-08-19 14:37:17 +08:00
Dennis 69540c3372 fix(mail): preserve shortcut query schema property 2026-08-19 14:37:15 +08:00
Dennis 3a2b738c06 fix(shortcut): close attendance and mail release gates 2026-08-19 14:37:13 +08:00
Dennis 725e60f07c feat(mail): harden and align shortcut workflows 2026-08-19 14:37:11 +08:00
Dennis 8b4453adf9 feat(attendance): harden shortcut contracts and live evidence 2026-08-19 14:37:09 +08:00
github-actions[bot] 13d0ae66a6 Merge pull request #1044 from DingTalk-Real-AI/fix/param-hallucination
feat(calendar): expand reviewed parameter alias coverage
2026-08-19 14:27:17 +08:00
克谨 63854705fd fix(chat): route direct media upload targets 2026-08-19 14:22:02 +08:00
克谨 f3b0fcdc4c test(calendar): verify aliases preserve confirmation 2026-08-19 13:53:59 +08:00
恋川 109a2891de Merge remote-tracking branch 'upstream/main' into feat/recruit-job 2026-08-19 13:48:45 +08:00
玉澜 4e106cd5ad Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills 2026-08-19 13:44:46 +08:00
玉澜 9858844158 fix(skills): no-clobber child moves in npm publish 2026-08-19 13:37:11 +08:00
克谨 00ca448aa2 docs(release): add chat mention fix fragment 2026-08-19 13:34:45 +08:00
克谨 afe01d4b70 Merge remote-tracking branch 'origin/main' into codex/fix-chat-user-mentions 2026-08-19 13:30:47 +08:00
克谨 4c6db326f5 fix(chat): preserve and validate user mention tokens 2026-08-19 13:30:40 +08:00
克谨 f10d552fd7 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-19 12:28:10 +08:00
github-actions[bot] 8b8756b00e Merge pull request #999 from wxianfeng/feat/oa-approval-instance-cc
feat(event): support OA approval CC events
2026-08-19 04:23:05 +00:00
克谨 ec59cf8065 test(calendar): run alias payloads in platform gate 2026-08-19 12:14:05 +08:00
炳昱 2ffddbd5a0 feat(event): support OA approval CC events 2026-08-19 12:08:35 +08:00
john 843edd700d Merge branch 'main' into fix/canonical-agent-skills 2026-08-19 11:36:13 +08:00
恋川 58ff0248b6 fix(recruit): handle minimal terminal pages 2026-08-19 11:05:29 +08:00
克谨 1d3c56f9fa Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-19 10:51:30 +08:00
克谨 502317db68 test(calendar): cover suggestion time aliases 2026-08-19 10:47:50 +08:00
github-actions[bot] 66516755e6 chore: update beta formula for v1.0.59-beta.3 [skip ci] 2026-08-19 02:39:35 +00:00
mygui ab0d1d2ad6 Merge branch 'main' into oa_approval_list_by_admin 2026-08-19 10:17:51 +08:00
恋川 6c895c23ed fix(recruit): validate pagination cursor responses 2026-08-19 10:17:18 +08:00
克谨 6e3f528f48 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-19 10:09:47 +08:00
克谨 d70e6b85b6 test(calendar): isolate exhaustive alias payload coverage 2026-08-19 10:09:37 +08:00
mingyue.gmy 358e1ab065 fix(oa): require startTime in --request and validate endTime independently
- Reject --request payloads missing startTime (documented required) so
  endTime can no longer bypass validation when startTime is absent
- Align --request time ordering with simple mode: endTime must be
  strictly after startTime
- Cover all five previously uncovered branches (pageSize absent,
  startTime absent, malformed endTime, valid time pair, empty --start
  flag) to reach 100% changed-code coverage
2026-08-19 10:03:29 +08:00
赤川 5e71a4ea52 Merge pull request #1048 from DingTalk-Real-AI/codex/changelog-v1.0.59-beta.3
docs: seal changelog for v1.0.59-beta.3
2026-08-19 10:01:03 +08:00
chichuan 0793238d47 docs: seal changelog for v1.0.59-beta.3 2026-08-19 09:58:00 +08:00
恋川 510b120630 fix(recruit): require job creator identity 2026-08-19 09:31:26 +08:00
恋川 f253f865c7 Merge remote-tracking branch 'upstream/main' into feat/recruit-job 2026-08-19 09:29:22 +08:00
克谨 c8f83533fb Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-19 09:21:44 +08:00
玉澜 8e34134dbc Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills 2026-08-19 08:03:42 +08:00
玉澜 65885dd262 fix(skills): abort degraded publish on foreign claim entries 2026-08-19 05:54:20 +08:00
玉澜 b354b371c9 fix(skills): prune backups without following reparse points 2026-08-19 00:15:31 +08:00
玉澜 15d289d3f3 fix(skills): keep sibling backups when marker write fails 2026-08-19 00:15:27 +08:00
github-actions[bot] 08e80bcb89 Merge pull request #1038 from pengzhihan47-star/codex/aitable_opt_pr
feat(aitable): streamline agent routes and table setup
2026-08-18 23:18:05 +08:00
柏智 39d9a65616 test(aitable): cover platform recovery behavior 2026-08-18 23:03:05 +08:00
柏智 a325ca80d8 fix(aitable): harden recovery and retry cancellation 2026-08-18 22:43:42 +08:00
玉澜 57b5845eb3 fix(skills): verify content fingerprint in shell rollback 2026-08-18 22:30:51 +08:00
克谨 75f08da197 feat(calendar): expand parameter alias normalization 2026-08-18 22:10:23 +08:00
mingyue.gmy fab84af434 docs(changelog): add release fragment for oa approval list-by-admin 2026-08-18 21:32:00 +08:00
mingyue.gmy 2dd724f1e1 feat(oa): add approval list-by-admin with string time contract
- Add dws oa approval list-by-admin leaf with simple flags and
  advanced --request modes backed by get_process_instances_by_admin
- Send startTime/endTime as yyyy-MM-dd HH:mm:ss strings per the
  2026-08 MCP contract update; ISO-8601 flag inputs auto-convert
- Enforce pageSize cap (20) and string time format/order client-side;
  PreRunE reports flag-group violations in Chinese before Cobra's
  built-in English validation
- Extend coverage tests and document the command in mono/multi OA
  skill references
2026-08-18 21:10:23 +08:00
柏智 b246b7d83b Merge remote-tracking branch 'upstream/main' into codex/aitable_opt_pr 2026-08-18 21:07:09 +08:00
玉澜 3ac9b83565 Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills 2026-08-18 21:00:14 +08:00
柏智 f4cb8aa282 fix(aitable): harden agent routes and composite contracts 2026-08-18 20:59:39 +08:00
github-actions[bot] c15480c452 Merge pull request #1039 from pengzhihan47-star/codex/pr1035-drive-tree-orphan-fix
fix(skills): remove obsolete drive tree helper
2026-08-18 12:48:27 +00:00
玉澜 234253e75f test(skills): cover linux statx identity without btime 2026-08-18 20:42:04 +08:00
玉澜 6a4803d85a fix(skills): verify backup ownership marker before pruning 2026-08-18 20:42:01 +08:00
pengzhihan47-star c0b013afa9 Merge branch 'main' into codex/pr1035-drive-tree-orphan-fix 2026-08-18 20:31:10 +08:00
github-actions[bot] 34d33e0492 Merge pull request #1036 from DingTalk-Real-AI/codex/remove-calendar-todo-review-html
docs: remove Calendar/Todo shortcut review HTML
2026-08-18 12:26:50 +00:00
Dennis4477 be15dd05df Merge branch 'main' into codex/remove-calendar-todo-review-html 2026-08-18 20:26:11 +08:00
github-actions[bot] 3578e4019b Merge pull request #969 from wxianfeng/feat/85349380-primary-param-p0
feat: migrate first DWS Primary parameters with compatibility (#85349380)
2026-08-18 20:19:15 +08:00
柏智 ede8e3c555 fix(skills): remove stale drive orphan allowlist 2026-08-18 20:04:59 +08:00
玉澜 d11e69fbdb test(skills): cover copy fallback dest scan branches 2026-08-18 19:41:33 +08:00
玉澜 a3566f39c4 test(skills): cover unix publication identity fallbacks 2026-08-18 19:33:56 +08:00
玉澜 a192e988c4 fix(skills): refuse unplanned dests in copy fallback 2026-08-18 19:33:51 +08:00
pengzhihan47-star 7a1b85ab62 Merge branch 'main' into codex/aitable_opt_pr 2026-08-18 19:28:19 +08:00
pengzhihan47-star 490818dfe9 Merge branch 'main' into codex/pr1035-drive-tree-orphan-fix 2026-08-18 19:27:53 +08:00
wxianfeng 1ab8f113a5 test: close primary migration coverage gaps to #85349380 2026-08-18 19:25:17 +08:00
wxianfeng 4f4ea43549 fix: reconcile primary migration with current main #85349380 2026-08-18 19:25:17 +08:00
wxianfeng 4fd67c52dc docs: update primary parameter guidance to #85349380 2026-08-18 19:25:17 +08:00
wxianfeng a3b06befbc test: enforce primary parameter compatibility to #85349380 2026-08-18 19:25:17 +08:00
wxianfeng 290f39ecb8 feat: migrate doc and todo primary parameters to #85349380 2026-08-18 19:25:17 +08:00
wxianfeng 7fbe7593c8 feat: migrate chat primary parameters to #85349380 2026-08-18 19:25:17 +08:00
wxianfeng 9fb61f8e99 feat: migrate aisearch query primary to #85349380 2026-08-18 19:25:17 +08:00
github-actions[bot] 2287abe644 Merge pull request #1026 from Justper/oa_attachment_dws
add oa attachment dws
2026-08-18 19:24:33 +08:00
pengzhihan47-star b3991d473e Merge branch 'main' into codex/pr1035-drive-tree-orphan-fix 2026-08-18 19:21:28 +08:00
昭逸 32bd2118af Merge remote-tracking branch 'upstream/main' into oa_attachment_dws
to #666
2026-08-18 19:06:13 +08:00
昭逸 f290a2101e fix drive.md to #666 2026-08-18 19:06:01 +08:00
pengzhihan47-star c8490da527 Merge branch 'main' into codex/aitable_opt_pr 2026-08-18 18:50:32 +08:00
github-actions[bot] f26806bc55 Merge pull request #968 from wxianfeng/chore/85349380-primary-param-approval
chore: approve first Primary flag migrations (#85349380)
2026-08-18 18:27:07 +08:00
玉澜 91d2e29925 test(skills): cover publication mark ownership branches
Windows coverage gate only runs TestCrossPlatformCoverage*, and the
xattr mark helpers are Unix-only. Inject seams so marked dest is
retracted on owned drift, left in place when the mark is gone, and
the helper error paths are exercised on every platform.
2026-08-18 17:50:39 +08:00
长真 26638cbd98 fix(chat): complete group role set-user flag compatibility 2026-08-18 17:44:51 +08:00
玉澜 2f05649277 fix(skills): keep concurrent dest across inode reuse
Linux overlayfs recycles device+inode, so SameFile and a lone inode
token treated a replacement as owned and retracted it. Stamp staged
inodes with an xattr mark, prove Linux/Darwin identity with birth
time, and make shell copied-set rollback check dest first with inode
plus child names.
2026-08-18 17:34:49 +08:00
wxianfeng c53e1f465d ci: retain legacy Drive tree helper to #85349380 2026-08-18 17:23:33 +08:00
长真 6c8e7e082b fix(chat): expose single group role set flag 2026-08-18 17:13:50 +08:00
柏智 176a556355 fix(aitable): verify declared field structures 2026-08-18 17:12:15 +08:00
昭逸 8609963ef8 Merge remote-tracking branch 'upstream/main' into oa_attachment_dws
to #666
2026-08-18 17:03:46 +08:00
玉澜 63a6de7b49 fix(skills): prove npm rollback ownership before quarantine
Match the Go dest-first identity check so a concurrent replacement is
never moved into .rollback-*; only a post-quarantine mismatch is
restored with no-replace. Cover both races in the npm smoke suite.
2026-08-18 16:40:21 +08:00
柏智 f57d9a51f4 fix(aitable): secure recovery commands 2026-08-18 15:59:59 +08:00
玉澜 46b641f227 fix(skills): retract leftover dest and qualify Windows junctions
Record dest on occupy and retract it when confirmation, verify, or
staging cleanup fails. Restore unmatched quarantine with a no-replace
publish. Event/devapp copy uses mkdir-claim; shell rollback claims dest
before delete. Release copy now says npm/PowerShell create junctions and
Go uses os.Symlink, with copy fallback when linking is unavailable.
2026-08-18 15:53:56 +08:00
柏智 9dc7f64b87 test(aitable): cover table bootstrap confirmation 2026-08-18 15:18:29 +08:00
wxianfeng b334794168 chore: approve primary flag migrations to #85349380 2026-08-18 15:18:21 +08:00
柏智 5aaf22782c fix(skills): remove obsolete drive tree helper 2026-08-18 15:04:22 +08:00
柏智 089c5491ec feat(aitable): streamline agent routes and table setup 2026-08-18 14:41:37 +08:00
玉澜 71da2dfded Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills 2026-08-18 13:48:35 +08:00
玉澜 21395ed12d fix(skills): retract unrecorded dest after cross-device publish
Cross-filesystem Skill moves now record publication identity as soon
as the staging path is renamed onto dest. A later mode-restore, copy
verification, or staging-cleanup failure retracts that proven dest so
retries are not blocked by an untracked leftover. A failed retract
reports an uncertain state naming both retained locations.
2026-08-18 13:48:23 +08:00
github-actions[bot] 7186a69b78 Merge pull request #1035 from pengzhihan47-star/codex/aitabel_drive_opt
docs(skills): optimize drive and wiki routes
2026-08-18 13:28:13 +08:00
柏智 9c202c7eae docs(skills): restore compressed safety and space routes 2026-08-18 13:12:33 +08:00
柏智 2969fb3c21 docs(drive): align publish guard with runtime 2026-08-18 13:04:37 +08:00
柏智 149a2481f4 docs(drive): restore high-risk permission guards 2026-08-18 13:02:07 +08:00
玉澜 5f2344d16d fix(skills): claim shell copy publications atomically and verify rollback identity
The shell mono/multi set publishers staged each Skill directory and
published it with a plain mv after the backup; anything another process
created at the destination between the backup and the move was silently
replaced, and restore_multi_skill_set then blind-deleted manifest paths,
so a concurrently replaced object could also be destroyed during
rollback. Publish through an atomic mkdir claim instead — EEXIST refuses
any occupant, staged children move into the claim one by one, and a
failed child move relocates them and removes only the claim. The
published manifest now records <dest>:<inode>, and rollback deletes a
destination only when its inode still matches the publication, skipping
concurrently replaced paths with a warning. Also fixes a latent
unbound-variable expansion where a shell variable was followed directly
by a full-width parenthesis in a message. Regression tests publish a
first Skill, replace it with a foreign directory, fail the second
publication, and assert rollback retains the foreign object untouched
while restoring the rest from backups.
2026-08-18 13:00:03 +08:00
柏智 4da2f382ec docs(drive): clarify commit unknown recovery 2026-08-18 12:43:20 +08:00
柏智 a3a96a6bd4 ci: retry cancelled coverage check 2026-08-18 12:38:09 +08:00
柏智 548809f72e Merge remote-tracking branch 'upstream/main' into codex/aitabel_drive_opt 2026-08-18 12:05:26 +08:00
玉澜 e79efc70af Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills 2026-08-18 12:00:32 +08:00
github-actions[bot] 7568d05434 Merge pull request #1028 from yutongShe/feat/comment-p0-validation
feat: add Doc and Sheet comment lifecycle commands
2026-08-18 04:00:12 +00:00
柏智 6aaa15be3c docs(skills): clarify drive transfer evidence 2026-08-18 11:36:51 +08:00
yutongShe ac8e41aa5f Merge branch 'main' into feat/comment-p0-validation 2026-08-18 11:31:51 +08:00
柏智 57bc1bcea8 Merge remote-tracking branch 'upstream/main' into codex/aitabel_drive_opt 2026-08-18 11:28:43 +08:00
github-actions[bot] f1c5a887b6 Merge pull request #1008 from abucraft/codex/aitable-record-stats
feat(aitable): add server-side record statistics
2026-08-18 03:27:15 +00:00
玉澜 7fa4ee7bac docs(skills): describe the actual degraded no-replace publication
The RFC section on filesystems that reject the atomic no-replace rename
still described the retired existence-check-plus-plain-rename fallback
and its accepted race window. The implementation (and the npm and shell
surfaces) claim the destination with mkdir or a hard link — or create
the link directly at the destination — and never release the claim mid
transaction, so a concurrently created object is refused rather than
overwritten. Record that contract and its only relaxed property (child
moves are not all-or-nothing visible) so future maintainers do not
port the racy description back into code.
2026-08-18 11:26:37 +08:00
昭逸 7c76e4fc03 test(oa): harden attachment delivery policy checks to #666 2026-08-18 11:23:51 +08:00
柏智 606f712a52 docs(skills): align wiki storage intent routes 2026-08-18 11:19:22 +08:00
恋川 5b9234d8fe fix(recruit): align job creation contract 2026-08-18 11:16:04 +08:00
柏智 dac4f6c029 docs(skills): fail closed on wiki space pagination 2026-08-18 11:15:11 +08:00
恋川 619319517a Merge remote-tracking branch 'upstream/main' into feat/recruit-job 2026-08-18 11:08:45 +08:00
镜玄 b7a6abb780 ci: retry cancelled coverage supporting job 2026-08-18 11:07:16 +08:00
yutongShe 7dab8df861 Merge branch 'main' into feat/comment-p0-validation 2026-08-18 11:06:49 +08:00
玉澜 74513bae2f Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills 2026-08-18 10:57:19 +08:00
昭逸 288212748c Merge branch 'oa_attachment_dws' of github.com:Justper/dingtalk-workspace-cli into oa_attachment_dws
to #666
2026-08-18 10:42:07 +08:00
昭逸 ef2c3ac163 Merge remote-tracking branch 'upstream/main' into oa_attachment_dws
to #666
2026-08-18 10:41:42 +08:00
柏智 b057c89a70 Merge remote-tracking branch 'upstream/main' into codex/aitabel_drive_opt 2026-08-18 10:41:37 +08:00
柏智 6ddfa59a28 docs(skills): fix wiki member verification example 2026-08-18 10:41:29 +08:00
昭逸 721a40b05e fix(oa): declare attachment result contracts
- add success and failure outcomes for three attachment commands
- define business data schemas and mark downloadUri as sensitive
- migrate attachment commands to unified result output
- verify compact and full Schema result projections
- cover success, malformed response, and tool error paths
to #666
2026-08-18 10:41:21 +08:00
玉澜 fcbddb0904 fix(skills): create shell-published links at the destination atomically
The POSIX shell installers staged shared Skill links and published them
with mv after an existence check; a file or symlink another process
created at the destination between the check and the move was silently
replaced, and the inode confirmation could not detect the loss. Publish
by creating each link directly at its destination instead — symlink(2)
refuses an occupied path with EEXIST, so the creation itself is the
atomic no-replace check. A directory that appears at the destination
turns ln -s into a container; the nested link is removed after an
identity check and the transaction rolls back, leaving the foreign
directory untouched. Applied to install.sh, install-skills.sh,
install-event.sh, and install-devapp.sh. Also covers the remaining
retraction branches of the Go shell-removal fallback so changed-code
coverage is complete. Regression tests inject a concurrent occupant at
the publish instant for regular-file and directory cases and assert the
foreign object and its contents stay completely unchanged.
2026-08-18 10:20:15 +08:00
Dennis d04511b8a6 Merge remote-tracking branch 'origin/main' into codex/remove-calendar-todo-review-html 2026-08-18 10:19:09 +08:00
李晟 f913c95ed1 Merge branch 'main' into codex/aitable-record-stats 2026-08-18 10:13:08 +08:00
github-actions[bot] effde76227 Merge pull request #1031 from DingTalk-Real-AI/fix/param-hallucination
feat(cli): expand AITable parameter alias normalization
2026-08-18 10:12:11 +08:00
李晟 43f0813acd Merge branch 'main' into codex/aitable-record-stats 2026-08-18 10:10:10 +08:00
柏智 33d8cd7e36 docs(skills): clarify drive copy routing 2026-08-18 10:08:01 +08:00
Dennis cfbe5b9b0d docs: remove calendar todo shortcut review 2026-08-18 09:54:21 +08:00
YanChangzhi 6e85983ad4 Merge branch 'main' into oa_attachment_dws 2026-08-18 09:53:09 +08:00
柏智 edbb175d4e docs(skills): optimize drive and wiki routes 2026-08-18 09:49:00 +08:00
克谨 b7bc0acb14 test(cli): cover AITable destructive alias gates 2026-08-18 09:44:42 +08:00
克谨 48e5d603bc Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-18 09:43:31 +08:00
玉澜 e84743615f fix(skills): retract a child move when the source shell cannot be removed
On filesystems without atomic no-replace rename, the degraded
publication moves the source children into a fresh claim and leaves an
emptied source shell for the caller to remove once the move is
confirmed. If that removal failed, moveSkillPathRecoverably reported a
plain failure claiming both locations were preserved while the data
existed only at the destination, so backupAndRemoveSkillDir never
recorded the backup and the original path was left empty. Move the
children back into the shell and withdraw the destination instead; a
failed retraction reports the data location explicitly. Restores the
contract that a failed move keeps the source intact.
2026-08-18 09:30:19 +08:00
玉澜 a102447eb5 Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills 2026-08-18 08:20:15 +08:00
玉澜 2b131a1031 fix(skills): create canonical links at the destination atomically
publishCanonicalLinkNoReplace checked the destination with lstat and
then published, leaving a window the comment claimed did not exist: on
Windows renameSync replaces a concurrent object outright (libuv passes
MOVEFILE_REPLACE_EXISTING), and on POSIX ln -P source target links INTO
a directory that appeared at the target, leaving a stray link inside
foreign data that the rollback list never recorded. Create the symlink
or junction directly at the destination instead — link creation fails
with EEXIST when anything occupies the path and never treats the target
as a container, so the publication itself is the atomic no-replace
check. Identity confirmation re-reads the live link before the
publication enters the rollback list. Covered by injected concurrent
creators at the publish instant on POSIX and simulated Windows,
asserting the foreign object and its contents stay completely
unchanged.
2026-08-18 08:17:46 +08:00
github-actions[bot] 12ff9d6138 Merge pull request #1032 from DingTalk-Real-AI/codex/calendar-pagination-result-followup
fix(calendar): keep pagination out of result data
2026-08-18 01:15:35 +08:00
Dennis ea18feb0a8 fix(calendar): keep pagination out of result data 2026-08-18 00:50:23 +08:00
玉澜 5fdaea5f36 Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills 2026-08-18 00:43:13 +08:00
玉澜 e8a320a06b fix(skills): claim npm copy publish destinations atomically
The mono and multi set copy publishers checked destination existence
with lstat and then called Node's rename, which replaces the target on
every platform (libuv passes MOVEFILE_REPLACE_EXISTING on Windows). A
file, symlink, or empty directory created between the check and the
rename was silently overwritten, and the identity confirmation could not
recover it because the publication record only proved the staged object
arrived. Claim the destination with mkdir — which fails with EEXIST if
anything occupies the path, so the claim itself is the existence check —
and move the staged children into the claim, restoring the source mode
on it. A failed child move relocates the children back and removes only
the claim. Covered for mono, multi, and simulated Windows, including an
injected concurrent creator at the claim instant.
2026-08-18 00:24:18 +08:00
github-actions[bot] c5e3c2ec56 Merge pull request #1030 from DingTalk-Real-AI/codex/calendar-todo-shortcut-alignment
feat(shortcut): align Calendar and Todo workflows
2026-08-18 00:17:15 +08:00
玉澜 59268a42a6 fix(skills): retract the published link when source removal fails
The no-replace file fallback links the destination and then removes the
source. If the removal fails, the caller treats the publish as failed,
but no publication record exists to roll the new destination back, and
a backup restore would refuse the occupied path. Remove the destination
behind an identity check — only the proven linked object may be deleted
— and report when the retraction itself fails or the destination was
concurrently replaced.
2026-08-17 23:51:17 +08:00
玉澜 06661af43f fix test: published file ID must differ from staged for Windows proof
The previous wrapper returned the first observed ID for both paths, so
expected == actual still held on Windows and the proof accepted the
swap. Return a distinct ID for the second probe.
2026-08-17 22:11:24 +08:00
玉澜 951dd27f0c test(skills): fake same file IDs across staged and published paths
The constant file-ID stub made both IDs equal, so the Windows proof
(expected == actual) accepted the publication and the subtest failed
there; Unix stayed green because its proof ignores the ID strings and
the swapped os.SameFile seam already forced the failure. Return the
first observed ID for both paths so staged and published identities
differ on every platform while real IDs still flow through the wrapper.
2026-08-17 22:10:37 +08:00
玉澜 bc5e5db7ef test(skills): pin publish identity rejection through the identity seam
The physical same-content swap relied on the recreated destination
getting a fresh inode, but CI runners' ext4/overlayfs recycle inodes
eagerly, so the swap was undetectable on Linux and the subtest failed
there (while passing on macOS). Swap the same-file identity seam instead
so the confirmation's fast-path rejection contract is pinned on every
platform.
2026-08-17 21:46:42 +08:00
玉澜 a0accff258 test(skills): assert claim mode matches source across platforms 2026-08-17 21:33:35 +08:00
Dennis 92c80f81f9 fix(calendar): align attendee and agenda contracts 2026-08-17 21:30:31 +08:00
克谨 70ed89c6bf test(cli): preserve AITable confirmation gates 2026-08-17 21:28:01 +08:00
玉澜 f7a3e606f7 fix(skills): never prune shell installers' current-run backups
The four standalone installers pruned the oldest excess stamp
directories regardless of origin, so a migration retiring more than
five batches destroyed its own rollback material mid-run — the same
data loss already fixed for Go via the run-root registry and present
in install.js/install.ps1 as currentRunBackupRoots. Every installer now
records the stamp directories it creates and pruning only removes
earlier-run batches, which is what the changelog already promises.
2026-08-17 21:24:46 +08:00
玉澜 1d1aca5fd1 test(skills): cover no-replace fallback error and rollback branches 2026-08-17 21:24:43 +08:00
恋川 b199fd29cb test(recruit): cover missing request job id 2026-08-17 20:53:32 +08:00
克谨 07b14aa72a feat(cli): expand AITable parameter alias normalization 2026-08-17 20:40:30 +08:00
Dennis d245ea4c84 Merge remote-tracking branch 'origin/main' into codex/calendar-todo-shortcut-alignment 2026-08-17 20:30:27 +08:00
玉澜 93703113cb fix(skills): hold the no-replace claim instead of unlinking and retrying
The degraded directory publication claimed the destination with mkdir, then
— on platforms whose rename refuses to replace a directory (macOS refuses
even an empty target, verified empirically) — removed the claim and retried
a plain rename. Between the unlink and the retry a foreign directory could
appear at the destination and be silently overwritten, breaking the
no-replace contract the fallback exists to provide.

Hold the claim for the whole transaction instead: rename over the claim
where the platform permits it (Linux), otherwise move the source children
into the claim one by one. The destination is never unlinked, so a
concurrent creator can only ever lose the mkdir race; every child rename
targets a nonexistent path inside the empty claim, and a failed move
restores the children and removes only the claim.

The child move legitimately changes the publication's identity, which the
confirmation now handles: a rename that consumed the staged path is still
proven by identity, while a child move is proven by the pre-rename content
fingerprint. The emptied source shell doubles as the signal distinguishing
the two shapes; moveSkillPathRecoverably removes it to keep move semantics.
2026-08-17 20:21:10 +08:00
玉澜 e5ed9e6e39 fix(skills): never prune backups taken by the running migration
The backup stamp has second precision and pruning kept only the newest 5
stamps, so a canonical migration that retires copies across many Agent
roots deleted its own earlier backups mid-run. That silently voided the
reversibility guarantee the transaction depends on for rollback: a probe
retiring 8 paths lost 3 of them permanently.

Record every stamp directory this process creates, keyed by normalized
absolute path, and prune only the oldest foreign stamps.
2026-08-17 20:21:06 +08:00
玉澜 7924e84fb6 fix(skills): fall back to copy when link publication fails
Creating the staged symlink usually succeeds, so the link strategy really
fails at publish time: renameSkillPathNoReplace has no atomic no-clobber
primitive for a symlink source and refuses it whenever the kernel flag is
unavailable (NFS, FUSE, overlayfs). Gating the copy fallback on staging
alone therefore left every non-universal Agent unconfigured on exactly the
filesystems the fallback exists to support.

Retry the whole target transaction as a direct copy after a failure in any
phase, but only when the failed attempt fully restored the originals. The
converter also re-adds the replacement backups the link plan deliberately
skips for destinations already pointing at canonical, which a copy must
replace and no-replace publication would otherwise reject with EEXIST.
2026-08-17 20:21:03 +08:00
玉澜 b4129c467d test(skills): cover Windows same-file identity seam with synthetic info
skillPathSameFileIdentityImpl on Windows always returns false and is
never reached through skillPathIdentityProven (which uses file IDs
exclusively). Add a direct seam call with synthetic os.FileInfo to
exercise the Windows return-false path and the Unix os.SameFile path
with nil Sys().
2026-08-17 20:21:00 +08:00
玉澜 a12abdfb54 refactor(skills): collapse Windows identity error paths for coverage
Restructure skillPathFileIdentityImpl to use nested if-err-nil with a
named return and skillPathIdentityProven to use a single expression.
Error conditions now fall through to the bare return instead of
occupying separate coverage blocks, eliminating 5 uncovered statements
that the Windows coverage gate flagged at 99.4193%.
2026-08-17 20:20:58 +08:00
玉澜 d9283b9a82 style: gofmt alignment after adding skillPathSameFileIdentity seam 2026-08-17 20:20:55 +08:00
玉澜 f1d40e26e1 fix(skills): open reparse points in Windows file ID query and stabilize tunneled test
Add FILE_FLAG_OPEN_REPARSE_POINT to the Windows CreateFile call in
skillPathFileIdentityImpl so symlinks are opened as reparse points
rather than followed to their target. Staged symlinks carry relative
targets computed for the final destination, which may not resolve from
the staging directory; following them caused CreateFile to fail,
yielding an empty file ID that rejected publication and broke canonical
skill layout migration on Windows.

Make skillPathSameFileIdentity a seam variable so the tunneled
replacement test can deterministically simulate the identity change on
Unix. On tmpfs (used by Linux CI runners), os.SameFile can return true
for a recreated file due to inode reuse, making the test flaky. On
Windows the swap is a no-op because skillPathIdentityProven compares
file IDs from GetFileInformationByHandle and ignores
skillPathSameFileIdentity.
2026-08-17 20:20:52 +08:00
玉澜 0db91cfc44 fix(skills): prove Windows rollback identity via stable file ID
NTFS file tunneling can restore the original creation time for a
recreated same-named object, which defeated the creation-time
incarnation check and allowed rollback to delete a concurrent
replacement. Replace the platform-specific identity pair with a single
skillPathIdentityProven function:

- Unix: delegates to os.SameFile (inode/dev), ignoring file ID strings
- Windows: compares VolumeSerialNumber:FileIndexHigh:FileIndexLow from
  GetFileInformationByHandle, which uniquely identifies the file on the
  volume for its lifetime and is unaffected by tunneling

When the file ID cannot be obtained at publish time, identity is not
proven and the auto-delete is refused. Add a regression test that
simulates tunneled creation time and verifies rollback still refuses
the concurrent replacement.
2026-08-17 20:20:49 +08:00
玉澜 f88be7ae21 test(skills): cover non-EEXIST link error on Windows
On Windows isNoReplaceRenameUnsupported always returns false, so the
fallback is never entered from the invalid-path test. Force the fallback
and swap skillPathLink to a non-EEXIST error to cover line 94 on all
platforms.
2026-08-17 20:20:46 +08:00
玉澜 e3313095ba test(skills): cover all no-replace fallback branches for 100% coverage
Add tests for mkdir non-EEXIST error, remove failure after rename
failure, first-rename-succeeds path (Linux behavior), retry-rename
path, and non-regular source safe-fail. All 24 changed executable
statements now covered on both macOS and Windows.
2026-08-17 20:20:44 +08:00
玉澜 c7882d7f72 fix(skills): eliminate TOCTOU in no-replace rename fallback
The fallback path for filesystems without RENAME_NOREPLACE/EXCL (NFS,
FUSE, overlayfs) used Lstat-then-Rename, which could overwrite a
concurrently created destination between the check and the rename.

Replace the TOCTOU-prone check with truly atomic no-clobber primitives:
- Directories: os.Mkdir atomically claims the destination (fails with
  EEXIST if occupied). On Linux rename(2) replaces the empty dir
  directly; on Darwin/Windows rename refuses existing dirs so the empty
  dir is removed and the rename retried — any concurrent creation
  between remove and rename is detected by the second rename failing.
- Files: os.Link atomically fails if the destination exists, then
  os.Remove completes the move.

Add concurrent-creation test covering the mkdir→rename race window.
2026-08-17 20:20:41 +08:00
玉澜 92196738d3 test(skills): cover Windows stat-error branch in no-replace fallback
The !os.IsNotExist(statErr) branch in renameSkillPathNoReplace was
uncovered on Windows. Inject errNoReplaceRenameUnsupported for the
atomic rename and os.ErrPermission for skillPathLstat so the stat-error
path is exercised on every platform.
2026-08-17 20:20:38 +08:00
玉澜 0a4da58d8f fix(ci): unset XDG_CONFIG_HOME for npm installer smoke test
The smoke test creates temp home directories with .config/kimchi markers
for agent detection. On Linux CI runners XDG_CONFIG_HOME may point to the
runner's real config path, causing resolvedAgentTargets to look outside
the temp home. Unset it so detection resolves against the test's temp dir.
2026-08-17 20:20:35 +08:00
玉澜 f14332f143 fix(skills): guard pruneSkillBackups against non-DWS directories
Restrict backup pruning to directories whose names match the DWS stamp
format (YYYYmmdd-HHMMSS with optional -N suffix) across all 8 installer
surfaces (Go, npm, 4 shell, 2 PowerShell). Unknown directories in
~/.dws/skill-backups are now preserved. Also fixes Windows coverage test
portability and covers the remaining macOS changed-code gap (retire
warning loop in runUpgrade).
2026-08-17 20:20:32 +08:00
玉澜 62883b7940 fix(skills): make obsolete-copy retirement non-fatal and harden install
A universal Agent whose obsolete private copy cannot be retired installs
nothing there, yet every entry point counted that retirement failure as an
install failure — aborting `npm install`, `dws skill setup`, and the shell
installers even when the canonical store and all links published correctly,
and skipping the skills-state write. Route retirement failures to a separate
warning path across all surfaces (Go upgrade + skill setup, npm, PowerShell,
install.sh, install-skills.sh, install-event.sh, install-devapp.sh).

Also:
- Add a checked-rename fallback for filesystems that reject the atomic
  no-replace flag (NFS, FUSE, overlayfs); the no-clobber contract is kept and
  the previously unsupported platforms build and work.
- PowerShell multi-mode links only bundle skills, never the shared canonical
  store, so third-party/user skills are no longer fanned into every Agent root.
- Prune ~/.dws/skill-backups to the newest 5 on every surface; encode
  HOME-relative backup names on PowerShell to preserve origin.
- Add simulated-win32 junction coverage and rewrite the tautological
  no-replace test; remove dead code whose tests gave false coverage.
- Soften the overstated Windows ownership-proof comment (NTFS tunneling).
2026-08-17 20:20:29 +08:00
玉澜 6e20bc765f test(skills): cover Windows no-replace path errors 2026-08-17 20:20:27 +08:00
玉澜 ecaefb416f fix(skills): retain Windows reparse link publication 2026-08-17 20:20:24 +08:00
玉澜 f16feed896 fix(skills): compare Windows publication identity stably 2026-08-17 20:20:21 +08:00
玉澜 d0a9dad079 test(skills): cover post-publish identity reuse 2026-08-17 20:20:19 +08:00
玉澜 e6c54cf777 fix(skills): distinguish reused publication inodes 2026-08-17 20:20:15 +08:00
玉澜 7a97354d93 fix(skills): make publication rollback race-safe 2026-08-17 20:20:13 +08:00
玉澜 a46958c788 fix(skills): make PowerShell rollback race-safe 2026-08-17 20:20:10 +08:00
玉澜 b664ace2f2 test(skills): junction-safe rollback and per-agent degrade regressions
- extend the silent-rollback contract to install-event.sh
- static contract: Restore-MultiSkillSet removes published paths lexically
  (section-scoped so identity-anchor refactors keep the guarantee) and link
  staging dirs are cleaned via Remove-LinkStageRoot / Remove-DevLinkStageRoot
- install-event.sh integration test: an uninstallable agent target is
  skipped loudly while later agents still receive links
- pwsh probe: Test-SamePhysicalSkillRoot must dereference junctions and
  symlinks (junction idempotency asserted where junctions are creatable)
2026-08-17 20:20:08 +08:00
玉澜 a789a2eea7 fix(skills): junction-safe PowerShell rollback and per-agent degrade
- install.ps1: remove published junctions lexically in Restore-MultiSkillSet
  (Windows PowerShell 5.1 follows reparse points during Remove-Item -Recurse
  and could delete canonical store contents); clean link staging dirs
  lexically in Publish-CanonicalSkillLinks and Move-SkillPathRecoverably
- install.ps1: Test-SamePhysicalSkillRoot now dereferences junctions via
  Get-PhysicalSkillPath (mirrors EvalSymlinks/realpathSync/cd -P), so reruns
  recognize already-published junctions instead of backup churn
- install-event.sh: replace silent 'mv ... 2>/dev/null || true' rollback with
  the loud backup-retained failure contract already enforced for devapp
- event/devapp sh+ps1: link→copy fallback and per-agent failures now degrade
  per agent like install.sh (skip loudly, continue, report at the end)
  instead of aborting mid-loop or swallowing errors
- tests: junction-lexical removal contract, event per-agent degrade
  integration test, pwsh junction physical-root recognition + rerun
  idempotency (no backup churn)
2026-08-17 20:20:06 +08:00
玉澜 e4a1feccf0 test(skills): retain rollback identity anchor 2026-08-17 20:20:04 +08:00
玉澜 3f39a9ddb1 Revert "test(skills): retain rollback identity anchor"
This reverts commit ce73a5b452.
2026-08-17 20:20:02 +08:00
玉澜 b080b6e7c5 test(skills): retain rollback identity anchor 2026-08-17 20:19:59 +08:00
玉澜 dc180f6d07 fix(skills): retain link identity anchors through rollback 2026-08-17 20:19:57 +08:00
玉澜 7b64576ea1 fix(skills): protect shell link rollback from races 2026-08-17 20:19:55 +08:00
玉澜 437dd234b2 fix(skills): fail upgrade unconditionally when canonical publish fails
A failed canonical publish only failed the upgrade when
hasDependentSkillRoot reported a non-universal link target; that helper
explicitly skipped universal agents, which are exactly the direct consumers
of ~/.agents/skills. On a universal-only machine (e.g. only Codex
installed), UpgradeSkillLocations* returned a nil error with nothing
installed, contradicting the documented "canonical publication is
mandatory and fails the upgrade loudly" contract.

Canonical publish failures now return an error unconditionally in both the
mono and multi branches, and hasDependentSkillRoot is removed. The test
that pinned the old standalone-does-not-fail-fast behavior now asserts
error propagation in both modes.
2026-08-17 20:19:53 +08:00
玉澜 04f78bcb15 fix(skills): remove ineffective app detection gate 2026-08-17 20:19:50 +08:00
玉澜 9abcdb4deb Revert "fix(skills): make app-bundle detection gate HOME-independent"
This reverts commit 37cd629335.
2026-08-17 20:19:48 +08:00
玉澜 c0da89e674 fix(skills): make app-bundle detection gate HOME-independent
The allowSystemApps gate (homeDir == systemHome) was effectively a no-op in
production: systemHome came from os.UserHomeDir, which honors the $HOME env
override just like homeDir, so the two were always equal and the gate never
fired when $HOME was overridden.

ResolveSystemHomeDir now prefers the OS user database (getpwuid on Unix),
which is independent of $HOME, falling back to $HOME only when the user record
cannot be resolved. Production behavior is unchanged (a real $HOME still
matches); an isolated/overridden HOME now correctly skips machine-wide
/Applications discovery for zcode/minimax. The app surface references the same
shared resolver.

This is the correct fix for the hermeticity concern (machine-wide state leaking
into an isolated HOME): there is no cross-surface production inconsistency to
port — script installers always operate on the real user HOME in practice, so
they need no gate.
2026-08-17 20:19:46 +08:00
玉澜 09fc5d993d test(skills): cover Windows chmod failure branch 2026-08-17 20:19:44 +08:00
玉澜 8f3a9e9d4a test(skills): cover Windows permission preparation seams 2026-08-17 20:19:41 +08:00
玉澜 567ea5d77c test(skills): make mode checks portable on Windows 2026-08-17 20:19:39 +08:00
玉澜 fc18f8fd04 fix(skills): preserve read-only backup trees 2026-08-17 20:19:37 +08:00
玉澜 a39ad4e6af fix(skills): make backups cross-filesystem safe 2026-08-17 20:19:34 +08:00
玉澜 301429e3aa test(ci): cover canonical skill platform branches 2026-08-17 20:19:32 +08:00
玉澜 0af5751d75 fix(skills): harden canonical agent installation 2026-08-17 20:19:30 +08:00
玉澜 79f7ee80e0 fix(skills): complete canonical agent compatibility 2026-08-17 20:19:28 +08:00
玉澜 44d640bbae fix(skills): use canonical global installation 2026-08-17 20:19:25 +08:00
恋川 06b4f3ba31 merge main into feat/recruit-job to #85340676 2026-08-17 20:00:13 +08:00
恋川 9f983be1ac fix(recruit): validate job response identity to #85340676 2026-08-17 19:55:12 +08:00
dxb 9e3a5d6fbd Merge pull request #1029 from DingTalk-Real-AI/fix/chat-sender-identity-contract
fix(chat): preserve unverified sender identity semantics
2026-08-17 19:10:30 +08:00
Dennis 33623d09d9 Merge remote-tracking branch 'origin/main' into codex/calendar-todo-shortcut-alignment 2026-08-17 18:48:47 +08:00
Dennis b20055a0b5 test(shortcut): close calendar todo coverage gaps 2026-08-17 18:48:39 +08:00
之桐 caf81b7984 feat(comments): add doc and sheet lifecycle commands 2026-08-17 17:50:29 +08:00
栩朝 fc05976d33 fix(chat): align chat message selection intent 2026-08-17 17:30:12 +08:00
栩朝 021da02474 fix(chat): preserve unverified sender identity semantics 2026-08-17 17:30:12 +08:00
github-actions[bot] a5b9e5a13f Merge pull request #928 from Anonymity-0/feat/bot-group-reply
feat(chat): support bot group message replies
2026-08-17 17:25:23 +08:00
昭逸 5742239c74 Merge remote-tracking branch 'upstream/main' into oa_attachment_dws
to #666
2026-08-17 17:24:06 +08:00
Dennis 3dce49020e docs(shortcut): refresh integrated gate counts 2026-08-17 17:21:53 +08:00
恋川 80bca147e0 Merge remote-tracking branch 'upstream/main' into feat/recruit-job 2026-08-17 17:21:31 +08:00
李晟 4ec2635830 Merge branch 'main' into codex/aitable-record-stats 2026-08-17 17:18:31 +08:00
昭逸 f319906f29 fix(oa): close attachment coverage gaps to #666 2026-08-17 17:17:29 +08:00
Dennis fac92c252e Merge remote-tracking branch 'origin/main' into codex/calendar-todo-shortcut-alignment 2026-08-17 17:10:11 +08:00
Anonymity-0 9f8c525008 Merge branch 'main' into feat/bot-group-reply 2026-08-17 16:59:05 +08:00
github-actions[bot] 207d4dd7e5 Merge pull request #980 from cywan1998/feat/calendar-event-share-info
feat(calendar): add event share-info command
2026-08-17 08:57:50 +00:00
Dennis 8db297fe4b fix(calendar): preserve agenda schema compatibility 2026-08-17 16:53:07 +08:00
Dennis dc2aec7696 fix(calendar): preserve room-find flag compatibility 2026-08-17 16:44:06 +08:00
fengbai 9a6b7d4d41 Merge branch 'main' into feat/calendar-event-share-info 2026-08-17 16:41:08 +08:00
恋川 2cea069f55 fix(recruit): scope lossless number decoding to #85340676 2026-08-17 16:09:43 +08:00
Dennis 404af112b7 fix(release): format shortcut change fragment 2026-08-17 16:09:19 +08:00
前津 5947016cc1 feat(chat): support bot group message replies 2026-08-17 16:09:08 +08:00
Dennis 5425d1565f feat(shortcut): align calendar and todo workflows 2026-08-17 16:01:14 +08:00
github-actions[bot] 386426bb92 Merge pull request #1012 from DingTalk-Real-AI/dws_0814_1723
fix(skill): update doc and drive descriptions for clearer routing
2026-08-17 07:45:22 +00:00
李晟 1a58e3c3e6 Merge branch 'main' into codex/aitable-record-stats 2026-08-17 15:28:57 +08:00
恋川 208a6c0273 Merge remote-tracking branch 'upstream/main' into feat/recruit-job 2026-08-17 15:28:04 +08:00
john 3cea671a54 Merge branch 'main' into dws_0814_1723 2026-08-17 15:27:17 +08:00
玉澜 4e8469a175 test(skills): cover Windows same-file identity seam with synthetic info
skillPathSameFileIdentityImpl on Windows always returns false and is
never reached through skillPathIdentityProven (which uses file IDs
exclusively). Add a direct seam call with synthetic os.FileInfo to
exercise the Windows return-false path and the Unix os.SameFile path
with nil Sys().
2026-08-17 15:25:32 +08:00
镜玄 9d8b338833 fix(aitable): validate stats filters consistently 2026-08-17 15:21:45 +08:00
昭逸 ea92e0212b merge main to #666 2026-08-17 15:17:01 +08:00
恋川 b7a07abcb1 test(recruit): cover cursor through response pipeline to #85340676 2026-08-17 15:14:22 +08:00
github-actions[bot] f06ea4d9e2 Merge pull request #960 from DingTalk-Real-AI/codex/doc-reread-audit
fix(doc): harden mutation readback verification
2026-08-17 07:06:53 +00:00
玉澜 2292a49c6a refactor(skills): collapse Windows identity error paths for coverage
Restructure skillPathFileIdentityImpl to use nested if-err-nil with a
named return and skillPathIdentityProven to use a single expression.
Error conditions now fall through to the bare return instead of
occupying separate coverage blocks, eliminating 5 uncovered statements
that the Windows coverage gate flagged at 99.4193%.
2026-08-17 15:00:25 +08:00
Dennis a82d945f54 fix(doc): reject explicit revert failure states 2026-08-17 14:48:13 +08:00
Dennis 6846326445 fix(doc): reject revert request echo evidence 2026-08-17 14:48:11 +08:00
Dennis 54c2054a5c fix(doc): ignore generated JSONML defaults 2026-08-17 14:48:09 +08:00
Dennis e5bf332b05 fix(doc): address readback review findings 2026-08-17 14:48:06 +08:00
Dennis 9ed55978d9 fix(doc): cancel readback retry waits 2026-08-17 14:48:04 +08:00
Dennis 7ffbbc4a51 test(doc): cover stable pagination identities 2026-08-17 14:48:02 +08:00
Dennis 2db73a8185 fix(doc): distinguish identical pagination pages 2026-08-17 14:48:00 +08:00
Dennis a62332be93 fix(doc): trust only explicit inserted block IDs 2026-08-17 14:47:58 +08:00
Dennis 1083093cbc test(doc): complete readback coverage evidence 2026-08-17 14:47:56 +08:00
Dennis c6ebe307cd fix(doc): verify inline media from jsonml readback 2026-08-17 14:47:54 +08:00
Dennis 3ee66d4373 fix(doc): harden mutation readback verification 2026-08-17 14:47:51 +08:00
玉澜 cf43cf1b47 style: gofmt alignment after adding skillPathSameFileIdentity seam 2026-08-17 14:41:55 +08:00
玉澜 344104268a fix(skills): open reparse points in Windows file ID query and stabilize tunneled test
Add FILE_FLAG_OPEN_REPARSE_POINT to the Windows CreateFile call in
skillPathFileIdentityImpl so symlinks are opened as reparse points
rather than followed to their target. Staged symlinks carry relative
targets computed for the final destination, which may not resolve from
the staging directory; following them caused CreateFile to fail,
yielding an empty file ID that rejected publication and broke canonical
skill layout migration on Windows.

Make skillPathSameFileIdentity a seam variable so the tunneled
replacement test can deterministically simulate the identity change on
Unix. On tmpfs (used by Linux CI runners), os.SameFile can return true
for a recreated file due to inode reuse, making the test flaky. On
Windows the swap is a no-op because skillPathIdentityProven compares
file IDs from GetFileInformationByHandle and ignores
skillPathSameFileIdentity.
2026-08-17 14:39:59 +08:00
github-actions[bot] a0be395ccc Merge pull request #1006 from DingTalk-Real-AI/codex/fix-aitable-pagination-minutes-unshare
fix(shortcut): harden Aitable pagination and Minutes unshare
2026-08-17 06:37:16 +00:00
ruigong 93dbd768f7 fix(skill): add explicit recent-edited route to drive SOP-1 2026-08-17 14:18:03 +08:00
Dennis c1a549cd64 fix: close delete readback continuations 2026-08-17 14:13:51 +08:00
Dennis 5a414999ef fix: validate record query previews 2026-08-17 14:13:49 +08:00
Dennis 7aa8240629 fix: preserve record query preview contract 2026-08-17 14:13:47 +08:00
Dennis 37b9a1dc31 fix: bound exact aitable record queries 2026-08-17 14:13:45 +08:00
Dennis 2ab8748c4d test: use native minutes path separators 2026-08-17 14:13:43 +08:00
Dennis f041275811 fix: make minutes polling portable 2026-08-17 14:13:41 +08:00
Dennis f486105836 fix: bound empty aitable pagination 2026-08-17 14:13:38 +08:00
Dennis e14de2b4c2 test: close shortcut fix review gates 2026-08-17 14:13:36 +08:00
Dennis fe2f3ca92f fix: harden aitable pagination and minutes unshare 2026-08-17 14:13:33 +08:00
github-actions[bot] 8e4519cacd Merge pull request #1014 from FloralTide/codex/fix-windows-event-bus
fix(event): support Windows bus lifecycle
2026-08-17 14:12:46 +08:00
恋川 89027aa2e2 fix(recruit): distinguish business failures and unwrap once to #85340676 2026-08-17 14:05:14 +08:00
昭逸 857279e076 将附件相关dws迁移到oa.go中,并补充skill描述 to #666 2026-08-17 14:03:42 +08:00
玉澜 e45608bb13 fix(skills): prove Windows rollback identity via stable file ID
NTFS file tunneling can restore the original creation time for a
recreated same-named object, which defeated the creation-time
incarnation check and allowed rollback to delete a concurrent
replacement. Replace the platform-specific identity pair with a single
skillPathIdentityProven function:

- Unix: delegates to os.SameFile (inode/dev), ignoring file ID strings
- Windows: compares VolumeSerialNumber:FileIndexHigh:FileIndexLow from
  GetFileInformationByHandle, which uniquely identifies the file on the
  volume for its lifetime and is unaffected by tunneling

When the file ID cannot be obtained at publish time, identity is not
proven and the auto-delete is refused. Add a regression test that
simulates tunneled creation time and verifies rollback still refuses
the concurrent replacement.
2026-08-17 14:03:28 +08:00
玉澜 ff6e2347f6 test(skills): cover non-EEXIST link error on Windows
On Windows isNoReplaceRenameUnsupported always returns false, so the
fallback is never entered from the invalid-path test. Force the fallback
and swap skillPathLink to a non-EEXIST error to cover line 94 on all
platforms.
2026-08-17 13:39:12 +08:00
玉澜 2d29f6601b test(skills): cover all no-replace fallback branches for 100% coverage
Add tests for mkdir non-EEXIST error, remove failure after rename
failure, first-rename-succeeds path (Linux behavior), retry-rename
path, and non-regular source safe-fail. All 24 changed executable
statements now covered on both macOS and Windows.
2026-08-17 13:22:58 +08:00
玉澜 fa887ccd26 fix(skills): eliminate TOCTOU in no-replace rename fallback
The fallback path for filesystems without RENAME_NOREPLACE/EXCL (NFS,
FUSE, overlayfs) used Lstat-then-Rename, which could overwrite a
concurrently created destination between the check and the rename.

Replace the TOCTOU-prone check with truly atomic no-clobber primitives:
- Directories: os.Mkdir atomically claims the destination (fails with
  EEXIST if occupied). On Linux rename(2) replaces the empty dir
  directly; on Darwin/Windows rename refuses existing dirs so the empty
  dir is removed and the rename retried — any concurrent creation
  between remove and rename is detected by the second rename failing.
- Files: os.Link atomically fails if the destination exists, then
  os.Remove completes the move.

Add concurrent-creation test covering the mkdir→rename race window.
2026-08-17 13:11:15 +08:00
炳昱 16abb481e8 Merge remote-tracking branch 'upstream/main' into codex/fix-windows-event-bus 2026-08-17 13:00:07 +08:00
炳昱 7ad82bbf0a fix(event): accept bus exit at stop timeout boundary 2026-08-17 13:00:07 +08:00
玉澜 30202e2b81 Merge remote-tracking branch 'origin/main' into fix/canonical-agent-skills 2026-08-17 12:30:34 +08:00
玉澜 1203409185 test(skills): cover Windows stat-error branch in no-replace fallback
The !os.IsNotExist(statErr) branch in renameSkillPathNoReplace was
uncovered on Windows. Inject errNoReplaceRenameUnsupported for the
atomic rename and os.ErrPermission for skillPathLstat so the stat-error
path is exercised on every platform.
2026-08-17 12:23:40 +08:00
chichuan 104eb715c4 Merge pull request #989 from maoqxxmm/codex/sheet-dropdown-source-range
feat(sheet): support SourceRange dropdowns and read completion
2026-08-17 12:19:00 +08:00
chichuan 97ea887ea5 Merge branch 'main' into codex/sheet-dropdown-source-range 2026-08-17 11:49:42 +08:00
玉澜 0ba55350d8 fix(ci): unset XDG_CONFIG_HOME for npm installer smoke test
The smoke test creates temp home directories with .config/kimchi markers
for agent detection. On Linux CI runners XDG_CONFIG_HOME may point to the
runner's real config path, causing resolvedAgentTargets to look outside
the temp home. Unset it so detection resolves against the test's temp dir.
2026-08-17 11:48:11 +08:00
玉澜 14c2569cfb fix(skills): guard pruneSkillBackups against non-DWS directories
Restrict backup pruning to directories whose names match the DWS stamp
format (YYYYmmdd-HHMMSS with optional -N suffix) across all 8 installer
surfaces (Go, npm, 4 shell, 2 PowerShell). Unknown directories in
~/.dws/skill-backups are now preserved. Also fixes Windows coverage test
portability and covers the remaining macOS changed-code gap (retire
warning loop in runUpgrade).
2026-08-17 11:42:43 +08:00
RuiGong01 03838a3430 Merge branch 'main' into dws_0814_1723 2026-08-17 11:39:56 +08:00
github-actions[bot] bfeb9f6af0 chore: update beta formula for v1.0.59-beta.2 [skip ci] 2026-08-17 03:35:41 +00:00
毛球 e26f278112 Merge branch 'main' into codex/sheet-dropdown-source-range 2026-08-17 11:17:45 +08:00
RuiGong01 0d34150333 Merge branch 'main' into dws_0814_1723 2026-08-17 11:16:56 +08:00
chichuan e6b5938bd8 Merge pull request #1025 from DingTalk-Real-AI/codex/changelog-v1.0.59-beta.2
docs: seal changelog for v1.0.59-beta.2
2026-08-17 11:03:27 +08:00
chichuan 4f95373420 docs: seal changelog for v1.0.59-beta.2 2026-08-17 10:59:11 +08:00
炳昱 afb90009f6 Merge remote-tracking branch 'upstream/main' into codex/fix-windows-event-bus 2026-08-17 10:49:25 +08:00
RuiGong01 9e8b58cbb6 Merge branch 'main' into dws_0814_1723 2026-08-17 10:47:33 +08:00
github-actions[bot] 6411d26a95 Merge pull request #1023 from DingTalk-Real-AI/fix/app-partition-parallel-jobs
fix(ci): parallelize app test partitions and drop race from the schema partition
2026-08-17 02:45:57 +00:00
毛球 31117d1b89 Merge branch 'main' into codex/sheet-dropdown-source-range 2026-08-17 10:43:01 +08:00
炳昱 e3553fe7a5 test(event): cover bus ownership validation failures 2026-08-17 10:40:21 +08:00
RuiGong01 fe724e96e8 Merge branch 'main' into dws_0814_1723 2026-08-17 10:39:02 +08:00
炳昱 067aff179f Merge remote-tracking branch 'upstream/main' into codex/fix-windows-event-bus 2026-08-17 10:32:08 +08:00
炳昱 353454abb2 fix(event): verify bus owner before fallback stop 2026-08-17 10:32:04 +08:00
john ae1565c0ff Merge branch 'main' into fix/canonical-agent-skills 2026-08-17 10:23:34 +08:00
chichuan 96b9cbce02 Merge branch 'main' into fix/app-partition-parallel-jobs 2026-08-17 10:22:20 +08:00
chichuan 36877d00dc Merge pull request #1024 from DingTalk-Real-AI/perf/schema-json-projection
perf: skip redundant JSON validation when projecting typed Schema values
2026-08-17 10:21:48 +08:00
xiatian a9a97c2746 Merge remote-tracking branch 'upstream/main' into codex/sheet-dropdown-source-range 2026-08-17 09:43:54 +08:00
RuiGong01 f72979f4a9 Merge branch 'main' into dws_0814_1723 2026-08-17 09:41:16 +08:00
chichuan 55d94d3b58 perf: skip redundant JSON validation when projecting typed Schema values
typedJSONValue marshaled a typed value and then routed the result through
rawJSONValue, which runs json.Valid before decoding. On that path the input is
whatever json.Marshal has just produced, so the validation scan can only ever
succeed: it re-read every marshaled document for nothing.

The decode step is now shared by both entry points. rawJSONValue keeps its
json.Valid check, because it still accepts untrusted input, while typedJSONValue
decodes what it marshaled directly. Across the 1121-tool set this removes about a
third of the Schema Catalog projection work: the internal/app schema suite goes
from 26.0s to 17.2s uninstrumented, and from 291.1s to 241.0s under -race.

The delivered Catalog is byte-for-byte unchanged. check-generated-drift,
check-schema-catalog and check-schema-binary each regenerate the same
source_hash sha256:93b8d44eb163bd2898c78397d22af92d378e3dc4e20f56b33277b51e4342e2e6,
and the two error contracts are preserved: typedJSONValue still rejects a value
json.Marshal cannot encode, and rawJSONValue still rejects invalid JSON.
2026-08-16 22:21:16 +08:00
chichuan bfd0976b31 fix(ci): run the app test partitions as parallel shards
The five internal/app partitions ran end to end inside one job, so the app
shard's wall clock was the sum of all five: 780s in CI, of which the schema
partition owned 357s. Each partition is now its own matrix shard, so they run
concurrently and the shard's wall clock is set by its slowest partition rather
than by their total. Every partition shard still selects the same single
internal/app package, so the impacted-package query maps the shard name back to
app and the partition only chooses which tests run.

The helper gains a partition argument and a list-partitions mode. APP_PARTITIONS
is the single source of truth for the set, and the discovery pass still runs in
every job, so each one independently verifies that the partition patterns cover
every top-level test exactly once before running the one it was asked for.

Two fail-closed checks guard the split, because the helper's own coverage check
can no longer prove the whole package ran once the partitions are separate jobs:

- The helper cross-checks APP_PARTITIONS against the coverage counters in both
  directions, so a counted partition that nothing dispatches and a dispatchable
  partition with no counter both fail instead of silently skipping tests.
- TestCIAppRacePartitionMatrixMatchesHelper pins the workflow's app-<partition>
  shards to list-partitions output in both directions, so a partition cannot
  lose its job while every job stays green.

The discovery loop variable is renamed from partition to spec: it would
otherwise shadow the partition requested on the command line, which run mode
reads after the discovery pass completes.
2026-08-16 22:18:04 +08:00
chichuan 4a33e7e893 fix(ci): drop race instrumentation from the app schema partition
The schema partition's 52 tests assert structural Schema-to-Cobra contracts over
a single goroutine: none of them call t.Parallel or start a goroutine, so the
race detector has no concurrent access to observe there. The process-global lazy
metadata that does need race coverage (schema_source_root's atomic.Value, the
parameter-binding lazy loaders) is exercised by internal/cli's concurrent tests,
which stay instrumented.

The instrumentation was not free here. The partition shares a single sync.Once
Catalog build whose work is allocation-heavy, and -race made it roughly 11x
slower: 26s -> 291s locally, and 357s of the app shard's 780s in CI. Within that
partition TestFinalSchemaToolsHaveExecutableBaseCommands alone accounted for
262s, not because the test is expensive but because it is the first caller to pay
for the shared snapshot; its 1121 subtests together measure 0.00s.

run_partition now takes the instrumentation mode explicitly and fails closed on
an unrecognized value, so a typo cannot silently drop -race from a partition that
is supposed to carry it.
2026-08-16 22:17:14 +08:00
github-actions[bot] ee74765383 Merge pull request #1019 from DingTalk-Real-AI/feat/help-feedback-entry
feat: add feedback survey entry to root help
2026-08-16 08:09:01 +08:00
chichuan 35239259fb Merge branch 'main' into feat/help-feedback-entry 2026-08-16 06:57:18 +08:00
github-actions[bot] 85bf2dfc8a Merge pull request #1021 from DingTalk-Real-AI/fix/test-focused-shard-matrix
fix(ci): shard the focused test job instead of one long-lived run
2026-08-15 23:33:12 +08:00
chichuan c4f2ab631b fix(ci): assert the focused path's shard shape in the workflow contract
The workflow contract pinned the focused path by literal: the job name
`Test (changed packages)`, the unsharded
`list "$TEST_BASE_REF" "$TEST_HEAD_REF"` call, and a single
`go test -timeout=15m` line standing in for internal/app's package-level
headroom. Sharding the job changed all three literals, so `Test (workflow
and release contracts)` failed on this branch even though every shard
selection test passed.

Each invariant the contract guarded still holds, so the assertions are
updated to the new shape rather than relaxed:

- the focused job must still exist, now as the matrix job, named the way
  the contract already names `Test (race: ${{ matrix.shard }})`;
- package selection must still derive from the authoritative synthetic
  merge base/head, now with an explicit shard argument, so pointing it at
  any other ref still fails the contract;
- internal/app's headroom is asserted through the process-isolating
  helper and the per-shard budgets, mirroring the assertions already
  applied to test-race. That is stronger than the old single -timeout: it
  pins the mechanism that keeps the suite inside its budget rather than
  the number alone. release-scripts membership is asserted too, because
  its dedicated job only runs at full-suite or release-sensitive scope,
  so losing it here would silently stop testing test/scripts changes.

The shard comparisons in the focused job are quoted so that job reads
verbatim like test-race's.

Ablating the implementation one change at a time turns the contract red
in all five cases: removing the app helper call, dropping release-scripts
from the matrix, selecting from HEAD~1, collapsing the matrix back to a
single unsharded job, and dropping the cli/smoke timeout budget.
2026-08-15 22:58:30 +08:00
chichuan 308e71c783 fix(ci): pass focused shard packages through a file
Reading the package list with `mapfile < file` has unambiguous line
semantics. Routing it through a step output and a here-string instead
would append an extra empty array element if the value ever carried a
trailing newline, and that element would reach go test as an empty
package argument. The step output now carries only a single-line boolean,
and the list travels through RUNNER_TEMP. An explicit empty-entry guard
fails closed if the file is ever malformed.

This job cannot execute on its own pull request — editing a workflow
routes the revision to full_suite, which skips the focused path — so the
implementation deliberately avoids depending on platform-specific
trailing-newline behavior that local verification cannot observe.
2026-08-15 22:32:39 +08:00
chichuan ecce09b355 fix(ci): shard the focused test job instead of one long-lived run
The focused path tested every impacted package in a single job with a
plain `go test -race`, so internal/app ran inside one long-lived process
alongside all of its reverse dependencies. That is exactly the shape
scripts/ci/run-app-race-tests.sh exists to avoid: a single app test
process retains every constructed command tree in framework registries,
so the run grows to 900s and the job stays alive long enough to be
reclaimed by the runner. Recent focused runs failed with SIGTERM after
9-10 minutes without a single test failure, and one earlier run failed
at `internal/app 902.651s`, 2.65s past the package timeout.

Fan the same package plan across the shard matrix test-race already
uses, and run each shard the way test-race runs it: internal/app through
the process-isolating helper, cli/smoke with their wider package budget,
release-scripts without race and with archive tooling.

changed-test-packages.sh gains `list-shard`, which intersects the
impacted set with scripts/ci/test-packages.sh shard membership so shard
definitions stay single-sourced — and so an unknown shard name aborts
there rather than reporting an empty selection, which would let a
mistyped shard skip every test while reporting success.

release-scripts is in the matrix on purpose: its dedicated job only runs
at full-suite or release-sensitive scope, so omitting it here would stop
testing test/scripts changes altogether. A test pins that the shard
selections partition the impacted set exactly, so shard-plan drift
cannot silently shrink focused coverage.
2026-08-15 22:10:28 +08:00
chichuan 1d02ff805d refactor: keep the feedback label out of i18n
Every neighbouring string in the root help listing — service
descriptions, utility descriptions, global flag usage — is hardcoded
Chinese. Routing only the feedback label through i18n therefore rendered
it in English on any host whose LANG is not zh_*, leaving a lone English
line inside an otherwise Chinese screen.

Hardcode the label and drop the two locale entries it needed. A test
assertion now pins the Chinese label so the indirection cannot return
unnoticed.
2026-08-15 16:38:57 +08:00
chichuan 4d843cf7a4 feat: add feedback survey entry to root help
`dws --help` now closes with a Feedback section that links the
user-experience survey form, tagged with source=dws-cli so submissions
arriving through the CLI can be told apart from other channels.

The entry is deliberately root-only: this CLI is driven mostly by AI
agents, and repeating a survey link in every subcommand help would be
pure context noise. A guard test pins that boundary.

The URL is printed on its own unwrapped line — it is longer than the
help rule width, and breaking it would stop terminals from recognizing
it as a clickable hyperlink.
2026-08-15 16:23:27 +08:00
8560830d3e feat: add privacy-safe clitrack telemetry (#1009)
Co-authored-by: zearlin <ruomiao.linrm@alibaba-inc.com>
Co-authored-by: chichuan <30925823+haofeng0705@users.noreply.github.com>
2026-08-15 15:58:31 +08:00
玉澜 7581955892 fix(skills): make obsolete-copy retirement non-fatal and harden install
A universal Agent whose obsolete private copy cannot be retired installs
nothing there, yet every entry point counted that retirement failure as an
install failure — aborting `npm install`, `dws skill setup`, and the shell
installers even when the canonical store and all links published correctly,
and skipping the skills-state write. Route retirement failures to a separate
warning path across all surfaces (Go upgrade + skill setup, npm, PowerShell,
install.sh, install-skills.sh, install-event.sh, install-devapp.sh).

Also:
- Add a checked-rename fallback for filesystems that reject the atomic
  no-replace flag (NFS, FUSE, overlayfs); the no-clobber contract is kept and
  the previously unsupported platforms build and work.
- PowerShell multi-mode links only bundle skills, never the shared canonical
  store, so third-party/user skills are no longer fanned into every Agent root.
- Prune ~/.dws/skill-backups to the newest 5 on every surface; encode
  HOME-relative backup names on PowerShell to preserve origin.
- Add simulated-win32 junction coverage and rewrite the tautological
  no-replace test; remove dead code whose tests gave false coverage.
- Soften the overstated Windows ownership-proof comment (NTFS tunneling).
2026-08-15 14:26:11 +08:00
xiatian 9fbd8addbe Merge remote-tracking branch 'upstream/main' into codex/sheet-dropdown-source-range 2026-08-15 13:40:55 +08:00
xiatian 92195a58a3 fix(sheet): align SourceRange review contract 2026-08-15 13:40:47 +08:00
github-actions[bot] fb9ff7de73 Merge pull request #1017 from typefield/feat/flag-input-file-stdin
feat(corecmd): support @file / stdin input sources on string flags
2026-08-15 12:50:22 +08:00
玉澜 5ee80cdb97 docs(rfc): warn about Input value-space collisions
Fifth-review addition: declaring InputFile silently claims the whole
@-prefixed value space, which matters in this product because at-mention
style values are common (--at-user @zhangsan would report a file read
failure), and declaring InputStdin makes a literal "-" unreachable. Both
are decided at declaration time and cannot be fixed downstream, so record
them next to the confirmation rule in the author rules.
2026-08-15 12:34:33 +08:00
玉澜 bf2c0653ed docs: record Input in the flag/help/schema homology field table
Fourth-review fix: the FlagSpec sub-field table in the homology doc is
the named authority for "what each field does and whether it reaches
Schema parameters", and RFC §5.0.2 asserts declaration fields embed into
dws.schema.*. Input satisfied neither entry, leaving its deliberate
non-projection indistinguishable from an oversight. Add the table row and
the §5.0.2 exception note so the capability stays a declared fact (Usage
prose) rather than inviting an invented annotation.
2026-08-15 12:29:26 +08:00
玉澜 e4daddf9cf test(corecmd): name Input tests for the platform coverage gate
Third-review fix for a CI blocker: run-platform-coverage-gate.sh only
executes ^(TestAllShortcuts|TestCrossPlatformCoverage) yet enforces 100%
coverage of changed production lines, so the TestResolveInputFlags names
left every new input.go statement reported as uncovered. Rename them to
the gate prefix, drop three unreachable pflag Set error branches that no
test could ever cover, and add the reachable stdin read-failure case.
Verified: changed code coverage 100.0000% (67 statements).
2026-08-15 12:23:54 +08:00
玉澜 e92309f7c4 fix(corecmd): match Input name selection to rawValue usability exactly
Second-review fix: explicitInputFlagName judged usability with an
unconditional TrimSpace while rawValue only trims when Trim is set. For
a non-Trim flag a whitespace main value is usable and shadows a changed
alias; the resolver could then rewrite the shadowed alias (and fail on
its @path) while the fallback chain still read the main value. Mirror
rawValue's usable() exactly and pin the shadow case with a regression
test whose alias path does not exist.
2026-08-15 12:14:10 +08:00
玉澜 7a58b0d19a fix(corecmd): align Input prefix check with Trim semantics
Self-review fixes: a Trim flag receiving " @path" judged usability on the
trimmed value (rawValue) while the source prefix check saw the raw value,
so the token would ship as a literal. Trim before the prefix check. Also
build the file-read error once with a conditional hint option, and pin
the default-value/env passthrough plus Trim edge with regression tests.
2026-08-15 12:11:55 +08:00
玉澜 78e6f11d72 docs(rfc): add @file / stdin Input flag usage guide to §5.3
Document the landed corecmd.Input transitional form: declaration shape
(FlagSpec/LeafFlag/shortcut.Flag), runtime resolution semantics and
ordering, author rules (help prose, confirmation interaction with
stdin, construction-time validation), and the delta table against the
target typed InputSource design.
2026-08-15 12:06:02 +08:00
玉澜 9a8a41a318 feat(corecmd): support @file / stdin input sources on string flags
Port the lark-cli Flag.Input capability: a KindString flag may declare
Input sources ("file" for @path, "stdin" for -) and the framework
rewrites the explicit token into the payload content before
required/enum/constraint/Validate checks. @@value escapes to a literal
@value; a single stdin consumer per invocation is enforced; a leading
UTF-8 BOM is stripped. Shortcut.Flag gains the same declaration and the
adapter maps it through; LeafSpec inherits it via the LeafFlag alias.
2026-08-15 10:47:11 +08:00
github-actions[bot] af8e6a9ccc Merge pull request #1015 from DingTalk-Real-AI/codex/wiki-shortcut-search-adapter
fix(wiki): document search parameter adapter
2026-08-15 01:30:26 +08:00
Dennis 547020f47e ci: shard shortcut reverse dependencies 2026-08-15 01:14:51 +08:00
Dennis d5eee82816 fix(wiki): document search parameter adapter 2026-08-15 00:07:00 +08:00
github-actions[bot] 0d8763b917 Merge pull request #1005 from DingTalk-Real-AI/codex/wiki-shortcut-workflows
feat(wiki): publish and harden 20 shortcut workflows
2026-08-14 23:49:35 +08:00
Dennis 600404abd0 fix(wiki): require interactive e2e confirmation 2026-08-14 23:32:43 +08:00
Dennis 247926d0fa fix(wiki): enforce auto-page item cap 2026-08-14 23:02:59 +08:00
Dennis 9ef2a4e652 fix(wiki): publish executable shortcut examples 2026-08-14 22:18:53 +08:00
Dennis d4daf9525c fix(wiki): verify copied node identity 2026-08-14 22:18:51 +08:00
Dennis 63a89e68fa test(wiki): lock confirmation before remote calls 2026-08-14 22:18:49 +08:00
Dennis 29b73a7d5e fix(wiki): close shortcut review gaps 2026-08-14 22:18:47 +08:00
Dennis 3488e11129 docs(wiki): keep review product-neutral 2026-08-14 22:18:45 +08:00
Dennis 596bdce3a1 feat(wiki): align and harden shortcut workflows 2026-08-14 22:18:43 +08:00
玉澜 24bbdda423 test(skills): cover Windows no-replace path errors 2026-08-14 20:22:32 +08:00
RuiGong01 0b012788c7 Merge branch 'main' into dws_0814_1723 2026-08-14 20:15:22 +08:00
玉澜 276658590b fix(skills): retain Windows reparse link publication 2026-08-14 20:14:04 +08:00
玉澜 16d20b8178 fix(skills): compare Windows publication identity stably 2026-08-14 20:07:33 +08:00
玉澜 dd89c67f4d Merge remote-tracking branch 'upstream/main' into fix/canonical-agent-skills 2026-08-14 20:00:47 +08:00
玉澜 75bf44b7be test(skills): cover post-publish identity reuse 2026-08-14 19:58:14 +08:00
github-actions[bot] 58eea98f6c Merge pull request #1013 from DingTalk-Real-AI/codex/chat-reference-card-hardening
fix(chat): split references and harden card updates
2026-08-14 19:52:22 +08:00
玉澜 1bae872341 fix(skills): distinguish reused publication inodes 2026-08-14 19:47:04 +08:00
炳昱 e742a6c269 Merge remote-tracking branch 'upstream/main' into codex/fix-windows-event-bus 2026-08-14 19:40:58 +08:00
栩朝 b53b84616e fix(cli): match ambiguous from flag exactly 2026-08-14 19:32:54 +08:00
玉澜 d1ecdcd551 Merge remote-tracking branch 'upstream/main' into fix/canonical-agent-skills 2026-08-14 19:24:09 +08:00
玉澜 a47740ca1c fix(skills): make publication rollback race-safe 2026-08-14 19:23:59 +08:00
栩朝 15a2fea0dc fix(chat): split references and harden card updates
Split chat message and group references by task, update intent routing and context budget, distinguish accepted card updates from verified writes, and explain the ambiguous chat --from flag.
2026-08-14 18:38:31 +08:00
chichuan 05868610f0 Merge branch 'main' into codex/sheet-dropdown-source-range 2026-08-14 18:33:24 +08:00
github-actions[bot] d8da9a2e9f Merge pull request #1011 from DingTalk-Real-AI/ci-coverage-speedup
ci: shard full-suite coverage and cache merge-base profile
2026-08-14 18:32:09 +08:00
chichuan 1a6ae856ec Merge branch 'main' into ci-coverage-speedup 2026-08-14 18:16:14 +08:00
炳昱 22649e96ef test(event): cover Unix spawn validation on Windows 2026-08-14 18:11:42 +08:00
chichuan 9c6407ae74 ci: align baseline coverage cache paths 2026-08-14 18:06:49 +08:00
炳昱 f68a11f11d test(event): cover Windows lifecycle edges 2026-08-14 18:05:34 +08:00
玉澜 4ad321557f Merge remote-tracking branch 'upstream/main' into fix/canonical-agent-skills 2026-08-14 18:04:34 +08:00
昭逸 3f2fc2e5f0 Merge remote-tracking branch 'upstream/main' into oa_attachment_dws
to #666
2026-08-14 17:56:53 +08:00
炳昱 abe5129306 fix(event): support Windows bus lifecycle 2026-08-14 17:56:51 +08:00
玉澜 34dee96833 fix(skills): make PowerShell rollback race-safe 2026-08-14 17:51:12 +08:00
李晟 ef27877628 Merge branch 'main' into codex/aitable-record-stats 2026-08-14 17:47:44 +08:00
github-actions[bot] b9b8cc2c77 Merge pull request #954 from xlb1130/fix/85200556-im-id-flags-v3
fix(chat): converge IM ID flags
2026-08-14 09:44:45 +00:00
chichuan 7b7bd556e9 Merge branch 'main' into feat/calendar-event-share-info 2026-08-14 17:43:49 +08:00
昭逸 6a2e9dd10e 新增审批附件相关dws,预览授权、下载授权、获取下载链接 to #666 2026-08-14 17:41:02 +08:00
ruigong d534ee242c fix(skill): scope doc/drive descriptions to entity-content vs file management 2026-08-14 17:33:54 +08:00
xlb1130 e02fdbdc8f Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-14 17:29:22 +08:00
github-actions[bot] ce529c9337 chore: update beta formula for v1.0.59-beta.1 [skip ci] 2026-08-14 09:20:43 +00:00
玉澜 fc455f800c test(skills): junction-safe rollback and per-agent degrade regressions
- extend the silent-rollback contract to install-event.sh
- static contract: Restore-MultiSkillSet removes published paths lexically
  (section-scoped so identity-anchor refactors keep the guarantee) and link
  staging dirs are cleaned via Remove-LinkStageRoot / Remove-DevLinkStageRoot
- install-event.sh integration test: an uninstallable agent target is
  skipped loudly while later agents still receive links
- pwsh probe: Test-SamePhysicalSkillRoot must dereference junctions and
  symlinks (junction idempotency asserted where junctions are creatable)
2026-08-14 17:14:13 +08:00
镜玄 42b5004bf8 ci: retrigger pull request checks 2026-08-14 16:51:52 +08:00
玉澜 3556d28fdd fix(skills): junction-safe PowerShell rollback and per-agent degrade
- install.ps1: remove published junctions lexically in Restore-MultiSkillSet
  (Windows PowerShell 5.1 follows reparse points during Remove-Item -Recurse
  and could delete canonical store contents); clean link staging dirs
  lexically in Publish-CanonicalSkillLinks and Move-SkillPathRecoverably
- install.ps1: Test-SamePhysicalSkillRoot now dereferences junctions via
  Get-PhysicalSkillPath (mirrors EvalSymlinks/realpathSync/cd -P), so reruns
  recognize already-published junctions instead of backup churn
- install-event.sh: replace silent 'mv ... 2>/dev/null || true' rollback with
  the loud backup-retained failure contract already enforced for devapp
- event/devapp sh+ps1: link→copy fallback and per-agent failures now degrade
  per agent like install.sh (skip loudly, continue, report at the end)
  instead of aborting mid-loop or swallowing errors
- tests: junction-lexical removal contract, event per-agent degrade
  integration test, pwsh junction physical-root recognition + rerun
  idempotency (no backup churn)
2026-08-14 16:48:34 +08:00
xlb1130 6952b22f45 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-14 16:39:56 +08:00
chichuan 3aa06e32fa ci: shard full-suite coverage and cache merge-base profile
The Coverage context was the PR critical path (~17 min end to end):
coverage-current re-ran the whole suite serially (-p 1, ~13 min) and
coverage-baseline re-ran it again at the merge-base (~13 min) although
that profile is a pure function of the base commit.

- coverage-current now owns only the scoped (standard-tier) profile;
  full-suite candidate profiles come from a 5-way shard matrix
  (app/cli/generators/helpers/remaining) that keeps -p 1 inside each
  shard on isolated runners. scripts/ci/test-packages.sh list-coverage
  defines the shards and verify proves the union equals the previous
  single-run package set exactly once.
- the aggregate Coverage job reassembles the disjoint shard profiles
  into coverage.txt before make coverage-gate, failing closed when a
  shard file is missing, so gate semantics (100% changed-code +
  scope-matched overall non-regression) are byte-compatible.
- coverage-baseline restores the merge-base full-suite profile from an
  exact-key cache (merge-base SHA + resolved Go version) written by the
  last green main push; any miss falls back to recomputing in the
  merge-base worktree. Exact key only - no prefix fallback, a near-miss
  profile would compare the candidate against the wrong commit.
- new contract tests pin the shard matrix, the assembly step, the
  exact-key cache pair, and the absence of restore-keys; the package
  plan test also covers the coverage shard partition.
2026-08-14 16:24:00 +08:00
chichuan 97fc783cc0 Merge pull request #1010 from DingTalk-Real-AI/codex/changelog-v1.0.59-beta.1
docs: seal changelog for v1.0.59-beta.1
2026-08-14 16:23:53 +08:00
镜玄 3a3cf00072 test(aitable): cover stats validation branches 2026-08-14 16:21:09 +08:00
chichuan a18b1e5fe4 docs: seal changelog for v1.0.59-beta.1 2026-08-14 16:11:55 +08:00
玉澜 618eb842a2 test(skills): retain rollback identity anchor 2026-08-14 16:11:48 +08:00
玉澜 465acf1406 Revert "test(skills): retain rollback identity anchor"
This reverts commit ce73a5b452.
2026-08-14 16:11:13 +08:00
玉澜 ce73a5b452 test(skills): retain rollback identity anchor 2026-08-14 16:09:24 +08:00
fengbai 90473284b8 fix(calendar): remove shell comment from share-info example
- Move the eventId lookup hint into Long description
- Keep example commands free of shell comments to pass example policy gate
2026-08-14 15:51:20 +08:00
玉澜 175b51cec0 fix(skills): retain link identity anchors through rollback 2026-08-14 15:45:51 +08:00
xlb1130 b17e030d1f Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-14 15:42:27 +08:00
玉澜 53a71b08c7 fix(skills): protect shell link rollback from races 2026-08-14 15:35:25 +08:00
github-actions[bot] 03258ca045 Merge pull request #899 from DingTalk-Real-AI/fix/drive-latest-incomplete-scan
fix(drive): --latest 扫描不完整时拒绝产出 Top-N 并杜绝 sortTime 泄露
2026-08-14 07:18:38 +00:00
xlb1130 afd8422580 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-14 15:16:17 +08:00
fengbai 07aa2c883a fix(calendar): address CR comments for share-info
- Fix Example indentation (tab -> 2 spaces)
- Remove unsubstantiated default en-US from --language help/docs
- Add test asserting calendarId/language are omitted when only --id is passed
2026-08-14 15:10:20 +08:00
chichuan 4b3e0e5046 Merge branch 'main' into fix/drive-latest-incomplete-scan 2026-08-14 14:52:33 +08:00
镜玄 5abef59c7c feat(aitable): add server-side record statistics 2026-08-14 14:46:21 +08:00
玉澜 3ef735bb3c Merge remote-tracking branch 'upstream/main' into fix/canonical-agent-skills 2026-08-14 14:44:08 +08:00
恋川 44e18a4062 Merge remote-tracking branch 'upstream/main' into feat/recruit-job 2026-08-14 14:37:05 +08:00
恋川 ccbacf84b3 test(helpers): cover trailing MCP JSON responses 2026-08-14 14:36:49 +08:00
玉澜 7cfaa1ca74 fix(skills): fail upgrade unconditionally when canonical publish fails
A failed canonical publish only failed the upgrade when
hasDependentSkillRoot reported a non-universal link target; that helper
explicitly skipped universal agents, which are exactly the direct consumers
of ~/.agents/skills. On a universal-only machine (e.g. only Codex
installed), UpgradeSkillLocations* returned a nil error with nothing
installed, contradicting the documented "canonical publication is
mandatory and fails the upgrade loudly" contract.

Canonical publish failures now return an error unconditionally in both the
mono and multi branches, and hasDependentSkillRoot is removed. The test
that pinned the old standalone-does-not-fail-fast behavior now asserts
error propagation in both modes.
2026-08-14 14:27:57 +08:00
恋川 d8f8f29062 fix(recruit): unwrap connector result envelopes 2026-08-14 14:06:08 +08:00
chichuan a6f69a06ce fix(drive): --latest 扫描不完整时拒绝产出 Top-N 并杜绝 sortTime 泄露
P1-a sortTime 泄露进输出契约 —— 采集端无条件写内部排序字段 sortTime,而 emit 仅在单层(reqDepth==1)经 stripDriveDepthDecorations 整体剥离。depth>1 的所有路径都把 sortTime 漏进 stdout;#971 引入的 --type/时间区间过滤同样读该字段,泄露面随之扩大。修法:在 emitDriveDepthResult 尾部无条件 delete,一处覆盖正常 emit / SIGINT 取消 / unrecoverable partial 三条路径。采集端保持不动(内部字段,排序与筛选时才读)。

P1-b 不完整扫描仍以退出码 0 产出「Top-N」 —— 尾部拒绝 guard 只拦全局截断,不拦递归途中目录读取失败;后者把可恢复失败记进 errs[] 后照常 emit,Top-N 落在漏扫子树的不完整集合上却冒充全局最新。修法:guard 扩为 latest>0 && (truncated || len(errs)>0),走新增 driveLatestIncompleteError(LATEST_SCAN_TRUNCATED / LATEST_SCAN_INCOMPLETE 双 token,二者同真时都带,目录失败详情排在截断之前);unrecoverable 分支在 latest>0 时不吐 partial,直接回根因错误。

恢复命令必须能原样复现原候选集:driveLatestScope 快照查询域(--workspace / --space-id)、扫描根(--folder)与全部过滤条件(--pattern / --type / --start / --end),缺任一项,用户照抄后就在另一个集合上取 Top-N,看起来成功却答非所问。扫描根取 runDriveListDepth 实际使用的 rootFolderID 而非重读 flag:用户可能传 URL,解析后的 ID 才是真正被扫的目标。「按原范围重跑」原样带回原 --folder,原调用在空间根时不带。

拒绝产出后 errors[] 不再进 stdout,目录名与服务端错误文本从 JSON(编码会转义)挪进纯文本 stderr —— 原样透传会让 ANSI/OSC 序列被终端执行,可清屏、伪造彩色成功、隐藏后续输出、改窗口标题,Agent 场景还会污染上下文。改为复用仓库既有的 output.SanitizeForTerminal(canonical 实现在 pkg/validate),再把它按设计保留的换行与制表符折成空格。Reason 无需处理:它是 classifyDriveDepthReason 的固定三值映射。latest=0 的既有路径仍把原值放进 errors[] JSON,不受影响。

Windows 下恢复命令的注入面:POSIX 单引号在 cmd.exe 里不是引用,--space-id 传入 sp-7 加 & 加 whoami 时,单引号包裹后的片段粘贴进 cmd 仍会执行 whoami;而唯一做真 shell 往返验证的测试被 build tag 排除在 Windows 之外。不采用「按目标 shell 生成引用」的路线:cmd.exe 的双引号挡不住 %VAR% 展开,PowerShell 的内嵌单引号写法又与 POSIX 不同,且生成命令时无法知道用户会粘贴进哪个 shell。改为平台分流 —— POSIX 构建继续单引号内联;Windows 构建只内联全部由白名单字符组成的值,含元字符的值不进命令,降级为占位符加 strconv.Quote 展示行并标注非可执行(与 internal/auth 展示 profile 标识的既有做法同一思路)。安全性由此不再依赖引用是否正确,而依赖「不受信任的值不进入可执行命令」这个更强的不变量。

顺带修掉白名单里的一个漏洞:% 原本免引用(当初为 URL 的 %20),但 cmd.exe 会无条件展开 %VAR%,于是 %PATH% 这类值会被判为安全并原样内联。% 已移除,POSIX 侧只是多一对无害引号;并新增逐字符断言,锁定白名单不含 POSIX sh / PowerShell / cmd.exe 三套元字符,同时作为该缺陷的回归锁。

两条平台策略写成与构建平台无关的纯函数,平台文件只做一行编译期绑定,因此 Windows 形态能在 POSIX 机器上端到端验证 —— 否则该分支在 POSIX 上永不可达,平台覆盖率门禁会直接报未覆盖(第一版实测 97.3451%)。另做了一次本地全量模拟:临时把 POSIX 绑定切到 Windows 策略后跑全部测试,唯一失败的是专门断言绑定的那条,据此确认没有断言会在 Windows runner 误报,并借此修掉两条原本只在 POSIX 下成立的断言。

SIGINT 取消路径刻意不套用该防线:取消由用户主动发起、退出码 130 已明确告知结果不完整,partial 是用户的预期产物。已加注释说明并补测试锁定该契约。

skill 文档(mono/multi 两份 drive.md)原在过滤章节声明「触顶截断 truncated=true、退出码 0」,同章节又说明可与 --latest 组合 —— 组合后该描述不再成立,故补一条拒绝产出的说明,并注明 Windows 下的占位符形态,避免 agent 按旧契约预期退出码或误解析。

测试命名统一 TestCrossPlatformCoverage 前缀:平台覆盖率门禁 run-platform-coverage-gate.sh 只跑匹配 ^(TestAllShortcuts|TestCrossPlatformCoverage) 的测试。本 PR 因新增带平台名的 go:build 文件被判定 platform_sensitive,Coverage (macOS) / (Windows) 由 SKIPPED 转为实跑;不带该前缀时新增语句在平台 profile 里是零覆盖,实测 69.0476%,改名后 100.0000%(当前 114 条语句仍为 100%)。已在测试文件头写明该前缀是门禁约定而非命名风格。

发布说明按 .changes fragment 机制落在 .changes/899-drive-latest-incomplete-scan.md,不改 CHANGELOG.md。
2026-08-14 14:02:36 +08:00
github-actions[bot] 2016e7f6dc Merge pull request #992 from afterglxw/feat/global-dws
feat/global dws
2026-08-14 13:38:12 +08:00
余辉 95986bbfc5 Merge remote-tracking branch 'origin/main' into feat/global-dws 2026-08-14 13:05:43 +08:00
余辉 322077be89 fix(auth): preserve explicit MCP override on intl login 2026-08-14 13:05:32 +08:00
长真 a7a0a97115 test(chat): align open id fixtures with current format 2026-08-14 12:25:07 +08:00
玉澜 f8af8dc1dc Merge remote-tracking branch 'upstream/main' into fix/canonical-agent-skills 2026-08-14 12:04:50 +08:00
玉澜 bb6fd2f256 fix(skills): remove ineffective app detection gate 2026-08-14 11:58:27 +08:00
玉澜 580c4d201b Revert "fix(skills): make app-bundle detection gate HOME-independent"
This reverts commit 37cd629335.
2026-08-14 11:43:57 +08:00
恋川 4f754133a5 fix(recruit): align pagination and size contracts 2026-08-14 11:43:39 +08:00
xlb1130 cbaa8c9bf5 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-14 11:39:54 +08:00
长真 0f5ecb609b fix(cli): restore audit join user guard 2026-08-14 11:38:26 +08:00
玉澜 37cd629335 fix(skills): make app-bundle detection gate HOME-independent
The allowSystemApps gate (homeDir == systemHome) was effectively a no-op in
production: systemHome came from os.UserHomeDir, which honors the $HOME env
override just like homeDir, so the two were always equal and the gate never
fired when $HOME was overridden.

ResolveSystemHomeDir now prefers the OS user database (getpwuid on Unix),
which is independent of $HOME, falling back to $HOME only when the user record
cannot be resolved. Production behavior is unchanged (a real $HOME still
matches); an isolated/overridden HOME now correctly skips machine-wide
/Applications discovery for zcode/minimax. The app surface references the same
shared resolver.

This is the correct fix for the hermeticity concern (machine-wide state leaking
into an isolated HOME): there is no cross-surface production inconsistency to
port — script installers always operate on the real user HOME in practice, so
they need no gate.
2026-08-14 11:33:55 +08:00
恋川 b447aac84b Merge remote-tracking branch 'upstream/main' into feat/recruit-job 2026-08-14 11:31:33 +08:00
github-actions[bot] 5094c63755 Merge pull request #971 from DingTalk-Real-AI/feat/drive-sync-family
feat(drive): add local/Drive folder status, pull, push and sync
2026-08-14 11:11:46 +08:00
余辉 4a78e7c1d9 fix(auth): reconcile managed MCP login region 2026-08-14 10:56:47 +08:00
恋川 9703a21a2d fix(recruit): normalize connector list response 2026-08-14 10:43:50 +08:00
玉澜 0c0e2b3ce1 test(skills): cover Windows chmod failure branch 2026-08-14 10:32:20 +08:00
chichuan 0c2a9cb2b3 test(drive): cover walkLocalTree's WalkDir error path via a seam
The new root-type guard shifted `filepath.WalkDir`'s outer error branch into
the diff, and neither the macOS nor the Windows runner reaches it naturally —
raising Windows coverage to 99.9365% and blocking the gate. Add a
`statusWalkDir` seam and a `TestCrossPlatformCoverage` regression that swaps
in a WalkDir returning a sentinel error, asserting it is surfaced unchanged.

Verified locally: changed code coverage back to 100.0000%.
2026-08-14 10:31:34 +08:00
玉澜 7d16c9693f test(skills): cover Windows permission preparation seams 2026-08-14 10:22:03 +08:00
玉澜 1dee02d900 test(skills): make mode checks portable on Windows 2026-08-14 10:08:40 +08:00
chichuan c9d4783968 fix(drive): recheck source identity after PUT and reject symlink status root
Two follow-ups to the latest CR:

* push/sync uploads (`pushUploadFilePinned`): the PUT-time check pinned inode,
  size, and mtime before dispatch but nothing rechecked the source after PUT
  succeeded — only the root itself. An editor overwrite, truncate-rewrite, or
  mmap-in-place during transfer would land a mixed old/new byte stream in OSS
  and still be committed, corrupting the remote file in overwrite/local-wins.
  Now stat the still-open handle again before `commit_upload`; any change in
  inode/size/mtime aborts the commit. Post-PUT stat failures also abort.

* status root (`walkLocalTree`): `filepath.WalkDir` refuses to follow the root
  when it is itself a directory symlink and reports it as a non-regular entry,
  so the walker silently returned an empty local index and status flagged
  every remote file as `new_remote`. Fail closed before the walk: the root
  must be a real directory; symlinks and non-directories are rejected with a
  clear message. A `statusRootLstat` seam keeps the rejection regressible on
  platforms that cannot create directory symlinks (Windows without admin).

Both fixes come with `TestCrossPlatformCoverage*` regressions and take the
platform coverage gate from 99.9356% back to 100.0000% (1553 statements).
2026-08-14 10:07:58 +08:00
余辉 2116122c95 Merge remote-tracking branch 'origin/main' into feat/global-dws
# Conflicts:
#	internal/app/root_help_test.go
2026-08-14 10:04:25 +08:00
玉澜 7664f04fda fix(skills): preserve read-only backup trees 2026-08-14 08:50:14 +08:00
玉澜 8177a06296 Merge remote-tracking branch 'upstream/main' into fix/canonical-agent-skills 2026-08-14 08:43:04 +08:00
玉澜 c674366aea fix(skills): make backups cross-filesystem safe 2026-08-14 08:42:54 +08:00
chichuan 4c3450792a Merge branch 'main' into feat/drive-sync-family 2026-08-14 08:35:35 +08:00
github-actions[bot] f55f9bc3a6 Merge pull request #998 from DingTalk-Real-AI/codex/open-dingtalk-id-format-routing
fix(chat): harden openDingTalkId target routing
2026-08-14 01:48:08 +08:00
栩朝 be001949e4 test(chat): complete sender routing coverage 2026-08-14 01:31:16 +08:00
栩朝 bcd91aca1f test(chat): align time defaults with current open ID format 2026-08-14 01:10:17 +08:00
栩朝 12e6632692 fix(chat): preserve sender identity uncertainty 2026-08-14 01:01:53 +08:00
栩朝 a5111f486b fix(chat): harden openDingTalkId target routing 2026-08-14 01:01:53 +08:00
长真 d0e6aba319 fix(cli): cover alias exclude guard branches 2026-08-14 00:23:18 +08:00
xlb1130 b0b18986b1 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-14 00:10:33 +08:00
github-actions[bot] 7a9348f9aa Merge pull request #973 from xlb1130/feat/85378080-chat-message-time-defaults
feat(chat): default message query time ranges
2026-08-14 00:01:32 +08:00
长真 6c78db7467 fix(chat): document Shanghai time message default 2026-08-13 23:37:29 +08:00
xlb1130 b539e15e6d Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 23:29:03 +08:00
xlb1130 abecb0dee1 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 23:28:09 +08:00
长真 d17f50b9de fix(cli): keep real flags out of alias blocked list 2026-08-13 23:26:08 +08:00
github-actions[bot] c9426622f0 Merge pull request #985 from xlb1130/chore/85411130-idempotency-key-ledger
chore(policy): add chat message send idempotency flag ledger
2026-08-13 23:13:51 +08:00
长真 5b01f29f2f Merge remote-tracking branch 'origin/feat/85378080-chat-message-time-defaults' into feat/85378080-chat-message-time-defaults 2026-08-13 22:35:02 +08:00
xlb1130 5efb6210b0 Merge branch 'main' into chore/85411130-idempotency-key-ledger 2026-08-13 22:32:50 +08:00
长真 113e084a8d fix(chat): align default message time timezone 2026-08-13 22:31:57 +08:00
chichuan 2734e3e1ce test(drive): cover fs.WalkDir callback error short-circuit
The Windows coverage gate reported changed-code coverage at 99.9360% because
drive_push.go:471-473 — the branch that surfaces an error passed to the
fs.WalkDir callback as its third argument — was not exercised. macOS runners
happen to exercise it via directory-lstat failures, Windows runners do not.

Add walk_callback_receives_error under
TestCrossPlatformCoverageDrivePushFinalWalkAndCommandGates, which swaps
walkPinnedLocalFS to invoke the callback with a non-nil err and asserts the
error is bubbled up unchanged.

Verified locally that the new subtest hits drive_push.go:471.17,473.4 with
count=1.
2026-08-13 22:22:28 +08:00
xlb1130 a76492e16e Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 22:16:29 +08:00
xlb1130 10417396f1 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 22:15:34 +08:00
chichuan 41a3724e9e Merge branch 'main' into feat/drive-sync-family 2026-08-13 22:07:16 +08:00
github-actions[bot] a0cc9b4b51 Merge pull request #942 from avicii-chen/feat/list-filter
feat(drive): add drive list --type/--start/--end client-side filtering
2026-08-13 14:06:12 +00:00
chichuan ce5815a606 Merge branch 'main' into fix/canonical-agent-skills 2026-08-13 22:01:48 +08:00
玉澜 d211b79a80 test(ci): cover canonical skill platform branches 2026-08-13 21:49:35 +08:00
chichuan 97b6022017 test(drive): make pinned-root TOCTOU reproductions runnable on Windows
Windows keeps the pinned directory locked while a handle inside it is open
(os.Root plus the pull temp file or the upload source), so renaming that
directory fails with a sharing violation. Every "pinned root/ancestor was
swapped" reproduction in the drive mirror tests relied on such a rename, so 13
tests failed on windows-latest. That, not a coverage shortfall, is why
Coverage (Windows) exited 1 before the gate ever ran.

Each reproduction now falls back to injecting the equivalent identity change
when the rename is refused. pinnedPullRoot.verify() and verifyParent() read
current identity only through pullPathStat / pullRootLstat, so pointing those
seams at another directory hits the same fail-closed branches. Unix still
performs the real move and loses no strength.

Assertions that need an actual replacement tree now branch on the helper's
return value. forcePinnedFallbackForTest makes the fallback path itself
regressible on any platform, and a dedicated test covers it.

Verified locally with the fallback forced on: all 13 tests pass and changed
code coverage stays at 100%.
2026-08-13 21:35:48 +08:00
juanxincai 45df573d0e Merge branch 'main' into feat/list-filter 2026-08-13 21:30:04 +08:00
玉澜 de8df0fa6f fix(skills): harden canonical agent installation 2026-08-13 21:22:57 +08:00
github-actions[bot] 608edfa309 Merge pull request #974 from DingTalk-Real-AI/fix/param-hallucination
feat(cli): standardize Doc and Drive parameter aliases
2026-08-13 13:17:11 +00:00
长真 e8ef510d3a Merge remote-tracking branch 'origin/chore/85411130-idempotency-key-ledger' into chore/85411130-idempotency-key-ledger 2026-08-13 21:09:19 +08:00
长真 8c6266f158 chore(policy): consume idempotency flag migration 2026-08-13 21:06:44 +08:00
长真 91f0fb7b11 fix(chat): declare idempotency key alias 2026-08-13 21:03:03 +08:00
xlb1130 410a63ea9a Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 20:59:34 +08:00
xlb1130 570d2e6756 Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 20:59:30 +08:00
长真 c3ffb9c831 fix(chat): validate list-all time defaults 2026-08-13 20:57:40 +08:00
长真 58c382efb7 Merge remote-tracking branch 'origin/fix/85200556-im-id-flags-v3' into fix/85200556-im-id-flags-v3 2026-08-13 20:57:29 +08:00
长真 3598586bc0 fix(cli): block plural id flag normalization 2026-08-13 20:56:43 +08:00
juanxincai e6821176a4 Merge branch 'main' into feat/list-filter 2026-08-13 20:55:23 +08:00
chichuan 504db23823 test(drive): cover platform-only branches missed by the platform coverage gate
The platform coverage gate runs only TestAllShortcuts and
TestCrossPlatformCoverage*, so several changed statements had no platform
test exercising them:

- drive_pull.go: the smart-policy re-check that skips publication when the
  target is refreshed in place (same inode) while the download is running.
- drive_pull.go: the post-publish verifyParent failure, where the result is
  already on disk and must not be rolled back.
- drive_replace_unix.go: rename(2) replacement of an existing target; the
  Windows side already had the symmetric test.
- drive_status_windows.go: the filepath.Clean rewrite guard had no input
  reaching it, because isSafeRemoteSegment filters separators upstream.

macOS changed-code coverage: 99.8053% -> 100.0000% (1541 statements).
2026-08-13 20:55:06 +08:00
长真 44857449d6 Merge remote-tracking branch 'upstream/main' into chore/85411130-idempotency-key-ledger 2026-08-13 20:50:03 +08:00
克谨 29f2f1c813 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-13 20:48:23 +08:00
xlb1130 d21f18af04 Merge branch 'main' into chore/85411130-idempotency-key-ledger 2026-08-13 20:48:16 +08:00
github-actions[bot] dd604455cc Merge pull request #990 from xlb1130/chore/85411130-idempotency-key-ledger-only
chore(policy): add idempotency flag migration ledger
2026-08-13 12:46:25 +00:00
克谨 26049a158a Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-13 20:41:21 +08:00
xlb1130 b066a14f0c Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 20:35:07 +08:00
xlb1130 95f9d168f1 Merge branch 'main' into chore/85411130-idempotency-key-ledger-only 2026-08-13 20:26:51 +08:00
玉澜 9ff31cdd55 Merge remote-tracking branch 'upstream/main' into fix/canonical-agent-skills 2026-08-13 20:23:05 +08:00
玉澜 fde37f7896 fix(skills): complete canonical agent compatibility 2026-08-13 20:21:56 +08:00
juanxincai 6d58520f57 Merge branch 'main' into feat/list-filter 2026-08-13 20:18:35 +08:00
chichuan 8984a1c454 Merge branch 'main' into feat/drive-sync-family 2026-08-13 20:09:49 +08:00
github-actions[bot] 91090a13b9 chore: update formula for v1.0.58 [skip ci] 2026-08-13 11:51:41 +00:00
juanxincai 395712490d Merge branch 'main' into feat/list-filter 2026-08-13 19:38:56 +08:00
chichuan 29c00341fa Merge pull request #997 from DingTalk-Real-AI/codex/fix-sealed-stable-compat
fix(ci): preserve delivered stable compatibility baseline
2026-08-13 19:26:10 +08:00
juanxincai 2eef6fdaa2 Merge branch 'main' into feat/list-filter 2026-08-13 19:14:48 +08:00
chichuan 14a2175434 fix(ci): preserve delivered stable compatibility baseline 2026-08-13 19:04:57 +08:00
xlb1130 94d4b5dcc9 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 18:58:13 +08:00
长真 5cbf18713a docs(changes): expand chat im flag migration note 2026-08-13 18:57:44 +08:00
长真 78d94380e7 docs(changes): note chat im id flag migration 2026-08-13 18:54:00 +08:00
玉澜 31902a987e fix(skills): use canonical global installation 2026-08-13 18:50:27 +08:00
卷心菜 973671bdf1 chore: trigger auto CR re-review 2026-08-13 18:36:38 +08:00
余辉 4b555515cd Merge remote-tracking branch 'origin/main' into feat/global-dws 2026-08-13 18:11:38 +08:00
余辉 ec83d8ff53 fix(auth): harden international login routing 2026-08-13 18:10:23 +08:00
xiatian 8cd2b0259d Merge remote-tracking branch 'upstream/main' into codex/sheet-dropdown-source-range 2026-08-13 18:04:51 +08:00
xlb1130 2d24f74980 Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 18:03:28 +08:00
长真 90278ab2fc test(chat): cover message default end window 2026-08-13 18:02:50 +08:00
chichuan 671a41437d Merge branch 'main' into feat/drive-sync-family 2026-08-13 17:58:26 +08:00
chichuan 1b06d0105a Merge pull request #995 from DingTalk-Real-AI/codex/changelog-v1.0.58
docs: seal changelog for v1.0.58
2026-08-13 17:53:40 +08:00
chichuan 18fad57bbe docs: seal changelog for v1.0.58 2026-08-13 17:44:56 +08:00
xlb1130 658f1e8e34 Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 17:40:20 +08:00
长真 a32608f964 fix(chat): use local time for message defaults 2026-08-13 17:39:32 +08:00
xiatian 4b8d94c24e ci: retry interrupted app race shard 2026-08-13 17:15:22 +08:00
github-actions[bot] c3ef04988b chore: update beta formula for v1.0.58-beta.6 [skip ci] 2026-08-13 09:10:23 +00:00
余辉 9f1b3e8254 Merge remote-tracking branch 'origin/main' into feat/global-dws 2026-08-13 17:09:16 +08:00
xiatian 76e5a8c4d9 Merge remote-tracking branch 'upstream/main' into codex/sheet-dropdown-source-range 2026-08-13 16:37:12 +08:00
xlb1130 1f2fbca4de Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 16:34:19 +08:00
xlb1130 c718b051c2 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 16:34:16 +08:00
john 76d54d6df6 Merge pull request #993 from DingTalk-Real-AI/codex/changelog-v1.0.58-beta.6
docs: seal v1.0.58-beta.6 changelog
2026-08-13 16:33:34 +08:00
xlb1130 58a8dddf31 Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 16:33:03 +08:00
xlb1130 6a93f14e0a Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 16:29:35 +08:00
克谨 0dc6735da2 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-13 16:25:39 +08:00
chichuan a36189d31e docs: seal v1.0.58-beta.6 changelog 2026-08-13 16:19:04 +08:00
长真 913b7cf9a9 chore(cli): refresh generated param aliases 2026-08-13 16:18:11 +08:00
长真 e46c4d0d71 Merge remote-tracking branch 'origin/feat/85378080-chat-message-time-defaults' into feat/85378080-chat-message-time-defaults 2026-08-13 16:16:39 +08:00
长真 35f399e2cf fix(chat): use Shanghai time for message defaults 2026-08-13 16:16:00 +08:00
chichuan d52d16dba4 Merge pull request #987 from DingTalk-Real-AI/codex/fix-release-seal-ci-path
ci: fast-path release seal fragment archival
2026-08-13 16:15:00 +08:00
xiatian 6abffce4e5 fix(sheet): preserve dropdown schema compatibility 2026-08-13 16:13:30 +08:00
余辉 c3a3b59ad2 Merge remote-tracking branch 'fork/feat/global-dws' into feat/global-dws 2026-08-13 16:11:20 +08:00
余辉 5b0cd561ff Merge remote-tracking branch 'origin/main' into feat/global-dws 2026-08-13 16:09:08 +08:00
余辉 6b3f2e29bd docs: add international region usage guide 2026-08-13 16:08:35 +08:00
xiatian 86b78e45d7 Merge remote-tracking branch 'upstream/main' into codex/sheet-dropdown-source-range 2026-08-13 16:08:28 +08:00
afterglxw c2c260b3a8 Merge branch 'main' into feat/global-dws 2026-08-13 15:56:35 +08:00
xlb1130 9ff74c852a Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 15:56:17 +08:00
克谨 9be59ddfec Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-13 15:50:15 +08:00
chichuan 8c00068364 fix(drive): harden folder mirror safety 2026-08-13 15:49:59 +08:00
xlb1130 a354144412 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 15:40:39 +08:00
恋川 5ef52503ae fix(recruit): align result and cursor contracts 2026-08-13 15:37:49 +08:00
chichuan d77fa91c69 Merge remote-tracking branch 'origin/main' into codex/fix-release-seal-ci-path 2026-08-13 15:37:08 +08:00
长真 9eeb0681ff test(chat): cover list-all time defaults in platform gate 2026-08-13 15:31:35 +08:00
chichuan 9ea527a7c4 ci: reject truncated release seal file lists 2026-08-13 15:25:11 +08:00
长真 d525648b45 fix(chat): support read-status conversation aliases 2026-08-13 15:24:27 +08:00
chichuan f78f1b83e7 Merge pull request #991 from typefield/agent/fix-release-validator
fix: align package verifier with Agent skill roots
2026-08-13 15:24:10 +08:00
卷心菜 75bd518447 fix(drive): honor --type folder in --latest top-N and harden filter mutexes 2026-08-13 15:19:46 +08:00
玉澜 3a6fa9a00c Merge remote-tracking branch 'origin/agent/fix-release-validator' into agent/fix-release-validator 2026-08-13 15:04:12 +08:00
玉澜 dc43d0d6d4 Merge remote-tracking branch 'upstream/main' into agent/fix-release-validator 2026-08-13 15:02:03 +08:00
chichuan bb69ed76df Merge branch 'main' into agent/fix-release-validator 2026-08-13 15:01:15 +08:00
余辉 427d0cc1fc docs: add international region release note 2026-08-13 15:00:00 +08:00
chichuan a26b16b30e test: scope release seal env assertions 2026-08-13 14:58:44 +08:00
玉澜 e0c9b4910d fix: align package verifier with Agent skill roots 2026-08-13 14:57:51 +08:00
余辉 1f6010f998 aicr endpoint bugfix 2026-08-13 14:50:58 +08:00
余辉 d9ba74aac0 compatible with global auth 2026-08-13 14:49:09 +08:00
xlb1130 c118a6a795 Merge branch 'main' into chore/85411130-idempotency-key-ledger-only 2026-08-13 14:48:52 +08:00
余辉 90070840f1 compatible with global auth 2026-08-13 14:46:34 +08:00
余辉 14818775c5 DWS support global 2026-08-13 14:46:34 +08:00
xlb1130 3d6c93196a Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 14:44:37 +08:00
长真 dc762dc6e3 Merge remote-tracking branch 'origin/feat/85378080-chat-message-time-defaults' into feat/85378080-chat-message-time-defaults 2026-08-13 14:43:07 +08:00
长真 45a80185f6 fix(chat): pass explicit list-all times through 2026-08-13 14:42:26 +08:00
chichuan a1dc997004 Merge branch 'main' into codex/fix-release-seal-ci-path 2026-08-13 14:41:29 +08:00
chichuan b525497da8 fix: pass release seal classification to policy 2026-08-13 14:31:50 +08:00
卷心菜 273a3ab5dd chore: migrate drive list changelog entries to release fragments 2026-08-13 14:12:13 +08:00
卷心菜 647bdb251c test(drive): cover drive list filter/pattern edge branches 2026-08-13 14:12:13 +08:00
卷心菜 9c59206d2f feat(drive): add drive list --type/--start/--end client-side filtering 2026-08-13 14:12:13 +08:00
长真 9edc587e96 chore(policy): to #85411130 add idempotency flag migration ledger 2026-08-13 13:55:53 +08:00
恋川 8ee9fc3f48 fix: 补充招聘结果与分页契约 to#85340676 2026-08-13 13:50:18 +08:00
xiatian 5065e4bfb6 Merge remote-tracking branch 'upstream/main' into codex/sheet-dropdown-source-range 2026-08-13 13:49:52 +08:00
克谨 db2caf6544 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-13 13:46:08 +08:00
chichuan ea9e31a59f Merge pull request #986 from DingTalk-Real-AI/codex/changelog-v1.0.58-beta.5
release: seal v1.0.58-beta.5 changelog
2026-08-13 13:45:14 +08:00
chichuan e58b85ea45 test: cover release seal CI fast path 2026-08-13 13:44:23 +08:00
长真 f3f1174407 chore(ci): rerun pr checks 2026-08-13 13:36:42 +08:00
chichuan e3fef0b6d4 ci: fast-path release seal fragment archival 2026-08-13 13:34:43 +08:00
xlb1130 54535bec11 Merge branch 'main' into chore/85411130-idempotency-key-ledger 2026-08-13 13:29:00 +08:00
长真 d32bbe009d fix(chat): expose idempotency key for message send 2026-08-13 13:28:00 +08:00
chichuan c7236a1844 release: seal v1.0.58-beta.5 changelog 2026-08-13 13:18:21 +08:00
xlb1130 0ea3d9810e Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 13:11:31 +08:00
xlb1130 ce6d5fb538 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 13:11:23 +08:00
github-actions[bot] 0a063e3ebd Merge pull request #979 from wxianfeng/feat/85384225-agent-version-ext
feat: forward Agent version and extension context
2026-08-13 05:07:40 +00:00
xlb1130 1e13413f79 Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 13:03:01 +08:00
长真 43882bf959 fix(chat): preserve schema compatibility for im flags 2026-08-13 13:02:34 +08:00
chichuan e19c54f77e Merge branch 'main' into feat/85384225-agent-version-ext 2026-08-13 12:47:15 +08:00
长真 891dde7d03 Merge branch 'main' into chore/85411130-idempotency-key-ledger 2026-08-13 12:28:59 +08:00
github-actions[bot] fbc34509f8 Merge pull request #970 from DingTalk-Real-AI/codex/im-page-all
feat(chat): unify shortcut auto-pagination controls
2026-08-13 04:18:43 +00:00
长真 def6ed4d2f test(chat): align list-all time expectations 2026-08-13 12:16:16 +08:00
长真 7bf8ce79bd chore(policy): to #85411130 add idempotency flag migration ledger 2026-08-13 12:07:06 +08:00
xlb1130 55c6a09bbc Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 11:58:53 +08:00
昊淼 ad0cf639c4 Merge branch 'main' into feat/85384225-agent-version-ext 2026-08-13 11:49:27 +08:00
xiatian 2778bef5bd feat(sheet): support source range dropdowns and read completion 2026-08-13 11:46:58 +08:00
Dennis 2f8e136dc0 fix(chat): fail closed on bounded legacy pages 2026-08-13 11:35:39 +08:00
Dennis fdbd11e0ea docs(changelog): add IM pagination release note 2026-08-13 11:35:37 +08:00
Dennis d07bf39586 fix(chat): bound automatic page delays 2026-08-13 11:35:35 +08:00
Dennis eee41a9b45 fix(chat): preserve safe pagination continuations 2026-08-13 11:35:33 +08:00
Dennis 896801634f fix(chat): preserve max-results visibility 2026-08-13 11:35:30 +08:00
Dennis a203572ee3 feat(chat): unify shortcut auto-pagination controls 2026-08-13 11:35:27 +08:00
克谨 ed6e7e493c Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-13 11:33:07 +08:00
github-actions[bot] 6c0ba91414 Merge pull request #963 from DingTalk-Real-AI/codex/drive-readback-verification
fix(drive): verify upload and move readback
2026-08-13 03:26:54 +00:00
chichuan a55880ce82 fix(drive): reject unsafe remote names 2026-08-13 11:10:53 +08:00
长真 ec4a730287 Merge remote-tracking branch 'origin/feat/85378080-chat-message-time-defaults' into feat/85378080-chat-message-time-defaults 2026-08-13 10:55:22 +08:00
长真 19a21b8f7e fix(chat): avoid explicit zone in list-all formatting 2026-08-13 10:54:51 +08:00
xlb1130 286376df93 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-13 10:54:21 +08:00
xlb1130 fa00da3507 Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-13 10:53:55 +08:00
昊淼 472d3d321b Merge branch 'main' into feat/85384225-agent-version-ext 2026-08-13 10:40:21 +08:00
克谨 a7ac4a264e Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-13 10:40:05 +08:00
chichuan 88cd453db6 Merge branch 'main' into feat/drive-sync-family 2026-08-13 10:38:37 +08:00
john 0b68450709 Merge branch 'main' into codex/drive-readback-verification 2026-08-13 10:38:17 +08:00
john 346444ea38 Merge pull request #981 from typefield/fix/interface-integrity-ledger-validation
fix: restore interface migration ledger compatibility
2026-08-13 10:37:25 +08:00
恋川 19e19bcd2b feat: 新增招聘职位管理 to#85340676 2026-08-13 10:26:03 +08:00
wxianfeng 54dc8fadb7 feat: forward agent version and extension context 2026-08-13 10:13:04 +08:00
chichuan 6fdf6e0678 fix(drive): reject sync path type conflicts 2026-08-13 10:01:10 +08:00
玉澜 b469bb127a docs: clarify hidden canonical promotion 2026-08-13 09:37:22 +08:00
玉澜 c6e810e4d9 fix: restore interface migration ledger compatibility 2026-08-13 09:34:48 +08:00
Dennis 98d03455b1 fix(drive): bind readback to requested objects 2026-08-13 00:12:07 +08:00
Dennis fad41d4d99 fix(drive): verify upload and move readback 2026-08-13 00:12:02 +08:00
xlb1130 b8deec9087 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-12 23:47:39 +08:00
长真 dbee2de1d5 fix(chat): align im id flag migration scope 2026-08-12 23:45:05 +08:00
xlb1130 1a9945f299 Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-12 23:18:30 +08:00
长真 b92ac4db0f fix(chat): preserve list-all time format 2026-08-12 23:16:33 +08:00
chichuan 3e27af8e21 Merge remote-tracking branch 'origin/main' into feat/drive-sync-family 2026-08-12 23:11:48 +08:00
chichuan 4d13905cb8 fix(drive): fail closed on invalid remote folders
Use explicit platform replace semantics for pull and sync, and reject recursive folder entries without a supported non-empty node ID.
2026-08-12 23:06:55 +08:00
克谨 9a3796c401 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-12 22:46:16 +08:00
克谨 6bf78f1783 test(ci): isolate app race partitions 2026-08-12 22:46:05 +08:00
github-actions[bot] 5fed80fc0f Merge pull request #966 from wxianfeng/feat/85349380-primary-param-governance
feat: support safe Primary flag rename governance (#85349380)
2026-08-12 14:40:01 +00:00
xlb1130 bb68baf0a9 Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-12 22:39:59 +08:00
chichuan 18c8e8390c fix(drive): reject duplicate remote paths
Reserve each remote file or folder rel_path exactly once so pagination and traversal order cannot silently discard mirror entries.
2026-08-12 22:30:09 +08:00
长真 657f9ee368 ci(test): extend app race shard timeout 2026-08-12 22:29:22 +08:00
昊淼 1727025f67 Merge branch 'main' into feat/85349380-primary-param-governance 2026-08-12 22:23:32 +08:00
chichuan ae6d9aa16d fix(drive): reject push path type conflicts
Check opposite-type remote entries before dry-run planning or actual writes, and cover both file-folder conflict directions.
2026-08-12 22:04:57 +08:00
chichuan 357b0955b1 Merge remote-tracking branch 'origin/main' into feat/drive-sync-family
# Conflicts:
#	skills/multi/dingtalk-drive/SKILL.md
2026-08-12 21:33:52 +08:00
克谨 221e42b103 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-12 21:23:34 +08:00
github-actions[bot] 715f5346da Merge pull request #975 from DingTalk-Real-AI/dws_optimization
fix(skill): clarify document-space routing in doc/drive/wiki descript…
2026-08-12 13:21:57 +00:00
fengbai 8aee08268d test(calendar): add event share-info dry-run and required-flag tests 2026-08-12 21:17:32 +08:00
fengbai 6a4744073c feat(calendar): add event share-info command 2026-08-12 21:07:59 +08:00
克谨 bcc324cc8f Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-12 21:02:57 +08:00
RuiGong01 f875b1bc87 Merge branch 'main' into dws_optimization 2026-08-12 20:54:37 +08:00
长真 a55bd9bff8 fix(chat): complete pending id flag migrations 2026-08-12 20:53:53 +08:00
克谨 cf8dd167a4 fix(cli): preserve scoped space aliases 2026-08-12 20:49:57 +08:00
chichuan 1dabfa1dc6 fix(drive): keep pull partial results on stdout 2026-08-12 20:48:45 +08:00
长真 d82e12d09e Merge remote-tracking branch 'origin/feat/85378080-chat-message-time-defaults' into feat/85378080-chat-message-time-defaults 2026-08-12 20:44:37 +08:00
长真 30f3273a17 fix(chat): validate message list-all time range 2026-08-12 20:43:56 +08:00
xlb1130 3e362fb3d1 Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-12 20:23:20 +08:00
长真 d40a22aeb0 fix(chat): default start from explicit message end 2026-08-12 20:17:10 +08:00
xlb1130 516bd5d99c Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-12 20:09:09 +08:00
chichuan 9818f7779a Merge branch 'main' into feat/drive-sync-family 2026-08-12 20:08:13 +08:00
长真 65a00b497b fix(chat): migrate audit join validation id flag 2026-08-12 20:06:09 +08:00
github-actions[bot] 3388df1c63 Merge pull request #978 from xlb1130/feat/85387314-chat-image-guide
docs(chat): clarify image markdown guide
2026-08-12 19:54:03 +08:00
chichuan 0e856f5a6e test(drive): cover dry-run collisions on Linux 2026-08-12 19:25:14 +08:00
克谨 b29a12abbf test: harden parameter alias safety gates 2026-08-12 19:05:11 +08:00
chichuan e08fb484a8 fix(drive): make folder dry-run side-effect free 2026-08-12 19:02:56 +08:00
克谨 65bedd5f8c Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-12 18:35:18 +08:00
chichuan 2df3b99e26 Merge remote-tracking branch 'origin/main' into feat/drive-sync-family 2026-08-12 18:31:15 +08:00
chichuan 21c6581975 docs(drive): keep confirmation out of examples 2026-08-12 18:31:09 +08:00
xlb1130 d3584077d7 Merge branch 'main' into feat/85387314-chat-image-guide 2026-08-12 18:30:40 +08:00
github-actions[bot] e49ba1ae71 Merge pull request #972 from typefield/feat/zcode-skill-root
feat(skill): support ZCode skill root
2026-08-12 10:20:18 +00:00
长真 3e4a3fb9d9 Merge remote-tracking branch 'origin/fix/85200556-im-id-flags-v3' into fix/85200556-im-id-flags-v3 2026-08-12 18:06:56 +08:00
长真 1f1c27d68f fix(chat): restore audit join group flag 2026-08-12 18:06:10 +08:00
长真 2c46213257 docs(chat): to #85387314 clarify image markdown guide 2026-08-12 18:04:17 +08:00
克谨 388ae0d37b ci: shard parameter alias changes 2026-08-12 17:59:54 +08:00
john 77dc7d30a0 Merge branch 'main' into feat/zcode-skill-root 2026-08-12 17:56:45 +08:00
ruigong aa3c279313 chore(policy): align doc skill context budget with event/chat (10000) 2026-08-12 17:55:31 +08:00
chichuan f2a3025f41 test(drive): cover Windows sync branches 2026-08-12 17:52:38 +08:00
chichuan 5282a55a54 test(drive): make MD5 failure coverage portable 2026-08-12 17:24:54 +08:00
克谨 07c5d25d55 fix(cli): cover doc search time aliases 2026-08-12 17:14:23 +08:00
ruigong 51dc3df91b fix(skill): clarify document-space routing in doc/drive/wiki descriptions 2026-08-12 17:14:20 +08:00
xlb1130 1b8ca149cb Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-12 17:03:29 +08:00
克谨 e9bbfdd20c Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-12 17:00:18 +08:00
chichuan 59978d9c06 Merge remote-tracking branch 'origin/main' into feat/drive-sync-family 2026-08-12 16:49:05 +08:00
长真 1f7d8c16bd Merge branch 'main' into feat/85378080-chat-message-time-defaults 2026-08-12 16:42:38 +08:00
长真 9c14d9a6e1 fix(chat): repair message time defaults checks 2026-08-12 16:42:27 +08:00
github-actions[bot] 70e03887d4 Merge pull request #962 from xlb1130/chore/85200556-im-id-flag-migrations-pending
chore(interface): add IM ID flag migration pending approvals
2026-08-12 08:40:45 +00:00
chichuan 8c25736f39 Merge remote-tracking branch 'origin/main' into feat/drive-sync-family 2026-08-12 16:38:26 +08:00
chichuan dacf166935 fix(drive): require confirmation for folder sync writes 2026-08-12 16:34:10 +08:00
克谨 fd26152141 docs(release): note Doc and Drive parameter aliases 2026-08-12 16:24:03 +08:00
克谨 a53971b146 feat(cli): standardize Doc and Drive parameter aliases 2026-08-12 16:23:17 +08:00
xlb1130 6ac2bbb7cf Merge branch 'main' into chore/85200556-im-id-flag-migrations-pending 2026-08-12 16:23:15 +08:00
长真 56bb50913b feat(chat): default message query time ranges 2026-08-12 16:23:13 +08:00
github-actions[bot] 5812276f46 Merge pull request #958 from typefield/codex/upgrade-stream-client-v0.9.2-beta.1
chore(deps): upgrade DingTalk Stream SDK to v0.9.2-beta.1
2026-08-12 08:15:00 +00:00
john 74baac23a1 Merge branch 'main' into codex/upgrade-stream-client-v0.9.2-beta.1 2026-08-12 15:51:30 +08:00
玉澜 b31eaec78d docs: remove ZCode release fragment 2026-08-12 15:47:39 +08:00
xlb1130 34c5118e85 Merge branch 'main' into chore/85200556-im-id-flag-migrations-pending 2026-08-12 15:36:05 +08:00
玉澜 6e4ea0980f feat(skill): support ZCode skill root 2026-08-12 15:34:38 +08:00
chichuan 54aefaaf60 test(drive): use testseam for seam swaps and expose tests to platform coverage runners 2026-08-12 15:22:08 +08:00
github-actions[bot] 3ce0e001c1 Merge pull request #961 from yutongShe/feat/drive-file-comments
feat(drive): add file comment commands
2026-08-12 15:20:41 +08:00
xlb1130 077a5c3b30 Merge branch 'main' into chore/85200556-im-id-flag-migrations-pending 2026-08-12 14:57:37 +08:00
之桐 f3567fba71 Merge remote-tracking branch 'upstream/main' into feat/drive-file-comments 2026-08-12 14:54:18 +08:00
github-actions[bot] e7837cdc6b Merge pull request #964 from typefield/fix/upgrade-default-multi
fix(skill): avoid duplicate Agent skill roots
2026-08-12 14:50:57 +08:00
长真 88e2f8e9e2 chore(interface): address migration approval review feedback to #85200556 2026-08-12 14:40:52 +08:00
之桐 b131726497 docs: add drive file comment release fragment 2026-08-12 14:36:26 +08:00
之桐 86ec9733c0 Merge remote-tracking branch 'upstream/main' into feat/drive-file-comments 2026-08-12 14:35:22 +08:00
chichuan 0a90c0350d docs(changelog): move release note to a .changes fragment 2026-08-12 14:26:45 +08:00
chichuan 654b740532 Merge branch 'main' into feat/drive-sync-family 2026-08-12 14:25:49 +08:00
玉澜 8a60334978 Merge remote-tracking branch 'upstream/main' into fix/upgrade-default-multi 2026-08-12 14:24:52 +08:00
之桐 76a6980244 fix(drive): validate numeric file comment IDs 2026-08-12 14:24:50 +08:00
玉澜 fcbbc0bd9a fix(skill): require explicit nested layout migration 2026-08-12 14:21:47 +08:00
github-actions[bot] 31edcc3c5a Merge pull request #888 from DingTalk-Real-AI/codex/release-fragments
release: use isolated changelog fragments
2026-08-12 14:21:18 +08:00
chichuan 6910bda9c7 refactor(drive): drop unreachable fixed-point guard in symlink escape check 2026-08-12 14:09:35 +08:00
wxianfeng bfd836064d feat: support optional flag rename governance to #85349380 2026-08-12 13:59:07 +08:00
chichuan f256d7a43c refactor(drive): add case-detection seam, split Windows guards, extract walk callbacks 2026-08-12 13:57:48 +08:00
chichuan 305ccf0984 Merge remote-tracking branch 'origin/main' into pr888-nested-gate 2026-08-12 13:53:37 +08:00
chichuan c2c1131079 fix: match the release archive directory literally, not as a regex
release_version was interpolated into an awk regex, where '.' matches any
character. Version 1.0.1-beta.1 therefore also admitted
.changes/released/1x0x1-betaX1/, letting the archive drift from the
CHANGELOG version while every other seal assertion still passed and
breaking the documented audit trail.

Compare the archive prefix with index() and split the basename off with
substr(), matching the literal-comparison idiom already used throughout
check-changelog-pr.sh. Only the basename, whose character class is fixed,
stays a pattern.
2026-08-12 13:52:25 +08:00
玉澜 24ea2505a5 test(skill): cover upgrade migration branches 2026-08-12 13:44:14 +08:00
chichuan 488411615f test(drive): cover parent-folder cascades and keep-both rollback paths 2026-08-12 13:38:06 +08:00
chichuan 01c1428b66 test(drive): cover sync family end-to-end paths and error branches 2026-08-12 13:33:09 +08:00
玉澜 566e94a31e fix(skill): make generic cleanup deterministic 2026-08-12 13:19:52 +08:00
chichuan f6a699227e Merge branch 'main' into feat/drive-sync-family 2026-08-12 12:44:39 +08:00
chichuan 185fbb1544 chore(schema): record drive sync leaves as reviewed pending-review exclusions 2026-08-12 12:43:44 +08:00
玉澜 5c68e4d9cc fix(skill): avoid duplicate Agent skill roots 2026-08-12 12:32:53 +08:00
github-actions[bot] 38e387bcd6 Merge pull request #959 from DingTalk-Real-AI/codex/drive-shortcuts
feat(drive): harden and expand shortcut workflows
2026-08-12 12:18:58 +08:00
长真 276ab52aed chore(interface): add im id flag migration pending approvals to #85200556 2026-08-12 12:14:10 +08:00
chichuan 12435e6e54 refactor: stage the .changes diff once for both fragment triggers
Both trigger predicates ran the same git diff, which the script already
avoids elsewhere by staging --name-status into $tmp_root/status. Write the
path list once and let each awk predicate read it, matching that idiom.
2026-08-12 12:07:07 +08:00
chichuan 1d8182bcfb Merge remote-tracking branch 'origin/main' into pr888-nested-gate 2026-08-12 12:01:38 +08:00
chichuan 4243676739 fix: trigger release fragment tree validation on nested .changes paths
Git records no diff entry for a directory itself, so adding
.changes/foo/bar.md only surfaced the nested path, which the single-level
trigger regex skipped. The entry validation and the renderer were both
bypassed, letting a nested directory reach main and break every later
fragment render with 'unexpected directory'.

Trigger the top-level tree validation on any .changes change outside
.changes/released/ (which keeps its own immutability and release-seal
checks), and assert .changes itself is still a tree so replacing it with a
blob or symlink cannot empty the child listing unnoticed.

Re-rendering stays keyed on fragment changes so a README-only edit does
not fail on an empty fragment set.
2026-08-12 12:00:42 +08:00
Dennis 4324fa72f2 fix(drive): preserve copy schema properties 2026-08-12 11:56:10 +08:00
长真 b6c508acdf fix(chat): canonicalize send-card id flags 2026-08-12 11:49:14 +08:00
chichuan 1d4c51a4d3 feat(drive): add local/Drive folder status, pull, push and sync 2026-08-12 11:37:47 +08:00
Dennis ef5462a4dc fix(drive): scan paginated file versions 2026-08-12 11:36:33 +08:00
之桐 bdf3048773 feat(drive): add file comment commands 2026-08-12 11:29:21 +08:00
Dennis e1da6ba356 fix(drive): preserve download output shorthand 2026-08-12 11:21:03 +08:00
Dennis ae309b5846 feat(drive): harden and expand shortcut workflows 2026-08-12 11:11:46 +08:00
玉澜 57e23d661d chore(deps): upgrade DingTalk Stream SDK to v0.9.2-beta.1 2026-08-12 10:57:37 +08:00
xlb1130 9472f4a1d9 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-12 10:48:44 +08:00
github-actions[bot] 9ef26055fa chore: update beta formula for v1.0.58-beta.4 [skip ci] 2026-08-12 02:45:42 +00:00
xlb1130 90e27c4b86 Merge branch 'main' into fix/85200556-im-id-flags-v3 2026-08-12 10:41:36 +08:00
chichuan d1bd518043 Merge pull request #957 from DingTalk-Real-AI/codex/changelog-v1.0.58-beta.4
docs: seal v1.0.58-beta.4 changelog
2026-08-12 10:32:34 +08:00
chichuan bac4fded0d docs: seal v1.0.58-beta.4 changelog 2026-08-12 10:25:48 +08:00
github-actions[bot] 82bfddc1c2 Merge pull request #922 from typefield/feat/skill-mode-migration
feat(skill): default installs and upgrades to multi-skill layout
2026-08-12 09:04:30 +08:00
玉澜 8cf23ee7cb Merge remote-tracking branch 'upstream/main' into feat/skill-mode-migration 2026-08-12 08:47:01 +08:00
玉澜 5777ea36e9 fix(skill): roll back partial mono installs 2026-08-12 08:44:48 +08:00
github-actions[bot] 6eceebd701 Merge pull request #953 from Anonymity-0/feat/card-send-native-mentions
feat(chat): support mentions in native card creation
2026-08-12 02:41:26 +08:00
玉澜 4b898e9011 test(auth): remove PAT polling timing race 2026-08-12 02:41:14 +08:00
玉澜 cb14ae96b3 Merge remote-tracking branch 'upstream/main' into feat/skill-mode-migration 2026-08-12 02:07:42 +08:00
前津 aeb4b2dcaa fix(chat): reject conflicting card update responses 2026-08-12 02:01:13 +08:00
玉澜 09f9289deb fix(skill): match managed names literally 2026-08-12 01:56:17 +08:00
前津 bbb14c24dc Merge remote-tracking branch 'upstream/main' into feat/card-send-native-mentions 2026-08-12 01:28:00 +08:00
github-actions[bot] 79f4be31d5 Merge pull request #956 from DingTalk-Real-AI/codex/fix-text-input-bounds
fix(localio): bound all text input paths
2026-08-11 17:16:44 +00:00
玉澜 e2a1be5e93 fix(skill): roll back partial setup transactions 2026-08-12 01:09:19 +08:00
Dennis 69911543c3 Merge remote-tracking branch 'origin/main' into codex/fix-text-input-bounds 2026-08-12 00:58:25 +08:00
john d4eba7fa96 Merge branch 'main' into feat/skill-mode-migration 2026-08-12 00:54:52 +08:00
前津 bbc2eb111c Merge remote-tracking branch 'upstream/main' into feat/card-send-native-mentions 2026-08-12 00:54:37 +08:00
github-actions[bot] b58b8c51bf Merge pull request #955 from DingTalk-Real-AI/codex/fix-eval-dispatch-403
fix(ci): restore eval dispatch PR comments
2026-08-12 00:52:54 +08:00
Dennis a7678472ab test(localio): scope path replacement to unix 2026-08-12 00:40:36 +08:00
Dennis f413db06be fix(localio): reject special files before open 2026-08-12 00:32:56 +08:00
Dennis 3d67d83110 test(localio): isolate input boundary e2e 2026-08-12 00:28:43 +08:00
玉澜 9de722ab34 fix(skill): roll back failed installer transactions 2026-08-12 00:22:09 +08:00
Dennis df088573fb fix(localio): bound all text input paths 2026-08-11 23:58:11 +08:00
chichuan a0c64e5ef4 fix(ci): restore eval dispatch PR comments 2026-08-11 23:57:12 +08:00
前津 eebd6b2a1c fix(chat): accept card update acknowledgement 2026-08-11 23:44:53 +08:00
玉澜 181f030350 test(skill): normalize backup paths on Windows 2026-08-11 23:40:28 +08:00
john 6140e503ec Merge branch 'main' into feat/skill-mode-migration 2026-08-11 23:31:08 +08:00
玉澜 9539ae8e40 fix(skill): centralize managed skill metadata 2026-08-11 23:26:49 +08:00
github-actions[bot] 7a140e59c3 Merge pull request #946 from DingTalk-Real-AI/codex/minutes-shortcuts
feat(minutes): align and expand shortcut workflows
2026-08-11 23:20:21 +08:00
前津 b1bfe6002d Revert "docs(skill): route create-only cards to native command"
This reverts commit 8e8e3a3ce8.
2026-08-11 22:54:02 +08:00
Dennis 38832448d2 Merge remote-tracking branch 'origin/main' into codex/minutes-shortcuts 2026-08-11 22:53:51 +08:00
前津 8e8e3a3ce8 docs(skill): route create-only cards to native command 2026-08-11 22:52:24 +08:00
长真 132dea9aaa fix(chat): hide remaining im id aliases 2026-08-11 22:51:16 +08:00
Dennis 3d4e43f4fc fix(minutes): align search scope enums 2026-08-11 22:49:27 +08:00
长真 5034c332fe fix(chat): converge im id flags 2026-08-11 22:38:37 +08:00
github-actions[bot] 155ce984c9 Merge pull request #916 from gtezg30062/feat/pull_knowledge_base_dynamic_1
Feat/pull knowledge base dynamic 1
2026-08-11 14:21:30 +00:00
john 4b93a1cb28 Merge branch 'main' into feat/pull_knowledge_base_dynamic_1 2026-08-11 22:05:50 +08:00
github-actions[bot] 1d384b9189 Merge pull request #952 from DingTalk-Real-AI/feat/eval-devix-poll
feat(eval): 用可验证轮询中继替代受限网络直连
2026-08-11 21:51:30 +08:00
玉澜 9f4e748404 fix(skill): preserve installs during layout migration 2026-08-11 21:37:55 +08:00
chichuan 025287873d Merge remote-tracking branch 'origin/main' into feat/eval-devix-poll 2026-08-11 21:33:26 +08:00
chichuan 6ddda6f1bf fix(eval): bind dispatch markers to workflow artifacts
Bind each accepted marker to the exact workflow run attempt, immutable artifact, source comment, and current PR head so a historical successful run cannot authorize a different payload.
2026-08-11 21:33:10 +08:00
chichuan e0dd800378 docs: state the release fragment filename and file-kind contract 2026-08-11 21:24:43 +08:00
chichuan 309c39a8e0 Merge remote-tracking branch 'origin/main' into codex/release-fragments 2026-08-11 21:24:25 +08:00
chichuan 39d6caa24d fix: validate every top-level .changes entry in the fragment gate
The fragment gate only ran validation when the changed path matched the
legal fragment name pattern, so `.changes/Foo.md`, `.changes/notes.txt`
and a symlinked fragment slipped through untouched and then broke the
next PR that added a legal fragment. The trigger now fires on any
top-level `.changes/` change other than README.md and rejects every
entry that is not README.md, released/, or a 100644 blob named
^[a-z0-9][a-z0-9._-]*\.md$.

The renderer had the same hole from the other side: `find -type f`
is false for symlinks, so a symlinked fragment was silently dropped
from the rendered notes, and the `[a-z0-9]*.md` glob only constrained
the first character so `chat reply.md` passed. It now walks every
top-level entry and fails on symlinks, unexpected directories,
non-regular files and illegal names. Both scripts pin LC_ALL=C so the
ASCII ranges cannot match uppercase under a different collation.

Adds regression coverage for illegal names, non-markdown entries,
symlinks and executable modes on both the gate and the renderer.
2026-08-11 21:07:12 +08:00
玉澜 0d4bd28a08 Merge remote-tracking branch 'origin/main' into feat/skill-mode-migration 2026-08-11 20:46:15 +08:00
玉澜 9264323b29 revert(ci): keep existing pull request checkout policy 2026-08-11 20:45:32 +08:00
github-actions[bot] dde5049454 Merge pull request #911 from Anonymity-0/feat/t07-chat-response-envelope
feat(chat): 统一 typed 与 shortcut 消息响应契约
2026-08-11 12:42:30 +00:00
玉澜 1744880648 test(skill): cover managed marker failure 2026-08-11 20:34:00 +08:00
玉澜 246f4ebaf5 docs(skill): consolidate migration design into RFC 2026-08-11 20:24:07 +08:00
Dennis a3f5a83527 Merge remote-tracking branch 'origin/main' into codex/minutes-shortcuts 2026-08-11 20:22:35 +08:00
Anonymity-0 5d7a66d4a3 Merge branch 'main' into feat/t07-chat-response-envelope 2026-08-11 20:22:20 +08:00
玉澜 ec5f312fd6 Merge remote-tracking branch 'origin/main' into feat/skill-mode-migration 2026-08-11 20:19:30 +08:00
玉澜 3c81741e2e fix(skill): fail partial setup installs 2026-08-11 20:19:00 +08:00
chichuan aebb75371b Merge branch 'main' into feat/eval-devix-poll 2026-08-11 20:17:55 +08:00
chichuanandClaude Opus 4.6 0a0634cfc2 fix(eval): harden extract_payload against non-dict JSON and invalid field types
Address P1 finding: extract_payload now strictly requires the parsed JSON
to be a dict, and validates each field's type and format:
- pr_number: string of digits
- pr_head_sha: 40-char lowercase hex string
- products: alphanumeric with commas/dots/hyphens/underscores only
- run_id: string of digits
- cases_ref: string (may be empty)

validate_run_id also guards against non-string input.

Added tests for: integer/array/string/null JSON, numeric field types,
invalid SHA format, injection in products, missing required fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-11 20:17:27 +08:00
github-actions[bot] 46aa0fe16d Merge pull request #944 from xlb1130/fix/85313115-chat-catalog-tools
fix(chat): register missing typed catalog tools
2026-08-11 20:11:26 +08:00
Anonymity-0 78165393e0 Merge branch 'main' into feat/t07-chat-response-envelope 2026-08-11 20:10:06 +08:00
Dennis 931af6af59 chore(pr): keep evidence out of merge tree 2026-08-11 19:51:57 +08:00
chichuanandClaude Opus 4.6 f6a4e0d5ad fix(eval): replace sed with bash string concat to satisfy shellcheck SC2001
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-11 19:51:32 +08:00
Dennis 44311d0160 docs(pr): publish minutes agent e2e evidence 2026-08-11 19:51:08 +08:00
chichuan afb25ae0e9 Merge remote-tracking branch 'origin/main' into codex/release-fragments 2026-08-11 19:49:49 +08:00
长真 fb44601f21 fix(chat): restrict audit join typed enum 2026-08-11 19:48:24 +08:00
chichuanandClaude Opus 4.6 83c64d31dd security(eval): add anti-forgery validation for eval-dispatch comments
Address P1 lint finding: structured eval-dispatch comments could be
forged by unauthorized users. Add three-layer consumer-side validation:

1. comment.user.login == 'github-actions[bot]' (platform-enforced identity)
2. comment.performed_via_github_app.slug == 'github-actions' (App signature)
3. payload.run_id verified against actual successful workflow run via API

Also adds:
- eval_poll_validate.py: consumer validation module (in-repo, auditable)
- test_eval_poll_validate.py: unit tests proving forged comments are rejected
- Go security contract test updated to assert run_id and validate reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-11 19:47:52 +08:00
玉澜 293c085634 fix(skill): preserve same-prefix user skills 2026-08-11 19:47:30 +08:00
Dennis f81d09fb95 fix(localio): pin verified upload file across retries 2026-08-11 19:45:06 +08:00
前津 f8258576ef feat(chat): support mentions in native card creation 2026-08-11 19:42:06 +08:00
chichuanandClaude Opus 4.6 e437cf4bbb feat(eval): replace direct internal API call with structured comment for Devix polling
The GitHub Actions runner cannot reach internal Aone CI API (structural
network isolation). Replace the curl-to-internal step with a structured
HTML comment (<!-- eval-dispatch: {...} -->) that an internal Devix
polling service picks up every 3 minutes to trigger the Aone CI pipeline.

This eliminates the EVAL_TRIGGER_URL/EVAL_TRIGGER_TOKEN secrets dependency
from the GitHub side — those can be removed once verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-11 19:40:48 +08:00
Dennis cd1ba34d96 Merge remote-tracking branch 'origin/main' into codex/minutes-shortcuts 2026-08-11 19:30:15 +08:00
Dennis 2cc410db6c docs: remove shortcut analysis artifacts 2026-08-11 19:09:30 +08:00
玉澜 f57c002ae7 Merge remote-tracking branch 'origin/main' into feat/skill-mode-migration 2026-08-11 19:06:57 +08:00
长真 495a3b256f Merge remote-tracking branch 'origin/fix/85313115-chat-catalog-tools' into fix/85313115-chat-catalog-tools 2026-08-11 19:03:09 +08:00
长真 4210334f55 fix(chat): preserve yes shorthand on guarded writes 2026-08-11 19:00:45 +08:00
Dennis 06ec207d17 fix(minutes): harden end-to-end failure handling 2026-08-11 18:54:24 +08:00
xlb1130 30caba5dcb Merge branch 'main' into fix/85313115-chat-catalog-tools 2026-08-11 18:51:33 +08:00
玉澜 b17634ef7d fix(skill): fail incomplete bundled skill installs 2026-08-11 18:49:05 +08:00
长真 76316ef5f0 Merge remote-tracking branch 'origin/fix/85313115-chat-catalog-tools' into fix/85313115-chat-catalog-tools 2026-08-11 18:48:18 +08:00
长真 f5b1c2659f fix(chat): enforce confirmation for chat write tools 2026-08-11 18:47:30 +08:00
github-actions[bot] b4f0053bbe Merge pull request #924 from typefield/feat/unified-command-framework-core
feat: add unified result framework with dingtalk-dev pilot
2026-08-11 18:34:15 +08:00
玉澜 3593818a46 Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 18:15:58 +08:00
玉澜 e0fd344a26 fix(skill): always refresh bundled skills 2026-08-11 18:13:44 +08:00
github-actions[bot] 21bbf42ca7 chore: update beta formula for v1.0.58-beta.3 [skip ci] 2026-08-11 10:06:41 +00:00
玉澜 edf1e58141 Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 17:59:38 +08:00
xlb1130 bd94c63de8 Merge branch 'main' into fix/85313115-chat-catalog-tools 2026-08-11 17:50:56 +08:00
chichuan 43b1936b65 Merge pull request #950 from DingTalk-Real-AI/codex/changelog-v1.0.58-beta.3
docs: seal v1.0.58-beta.3 changelog
2026-08-11 17:50:03 +08:00
玉澜 9d8806927f Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 17:48:35 +08:00
chichuan dbe47d58fb docs: seal v1.0.58-beta.3 changelog 2026-08-11 17:45:44 +08:00
xlb1130 8c2c94e0f1 Merge branch 'main' into fix/85313115-chat-catalog-tools 2026-08-11 17:40:29 +08:00
玉澜 b7b78f0c16 fix(dev): keep recovery commands behind confirmation 2026-08-11 17:36:59 +08:00
john c38e988b14 Merge branch 'main' into feat/skill-mode-migration 2026-08-11 17:34:26 +08:00
github-actions[bot] ec7593dabb Merge pull request #936 from wxianfeng/feature/aone85277391-event-runtime-token-handoff
fix(event): securely hand off runtime token to detached bus
2026-08-11 09:32:24 +00:00
chichuan 1df4cc95a6 Merge branch 'main' into feature/aone85277391-event-runtime-token-handoff 2026-08-11 17:15:15 +08:00
Dennis 66468c703f fix(minutes): preserve upload recovery and schema compatibility 2026-08-11 17:06:22 +08:00
玉澜 773e76a1c6 Merge remote-tracking branch 'fork/feat/skill-mode-migration' into feat/skill-mode-migration 2026-08-11 17:01:44 +08:00
玉澜 f4e39a219b fix(skill): preserve state on partial setup 2026-08-11 17:01:32 +08:00
john c170a464e1 Merge branch 'main' into feat/skill-mode-migration 2026-08-11 17:00:13 +08:00
Dennis 74ef426064 Merge remote-tracking branch 'origin/main' into codex/minutes-shortcuts 2026-08-11 16:55:06 +08:00
玉澜 5ce391b49b Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 16:41:42 +08:00
xlb1130 4a14f4b1e3 Merge branch 'main' into fix/85313115-chat-catalog-tools 2026-08-11 16:40:45 +08:00
玉澜 451a6fffe7 Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core
# Conflicts:
#	internal/errors/errors.go
#	internal/errors/errors_test.go
2026-08-11 16:40:15 +08:00
github-actions[bot] d052c104d9 Merge pull request #948 from cywan1998/docs/sync-calendar-skill-mono-multi
docs(skills): sync calendar reference between mono and multi layouts
2026-08-11 08:39:55 +00:00
Anonymity-0 e4e653d3b3 Merge branch 'main' into feat/t07-chat-response-envelope 2026-08-11 16:26:43 +08:00
xlb1130 6b85867309 Merge branch 'main' into fix/85313115-chat-catalog-tools 2026-08-11 16:23:13 +08:00
fengbai fdf3e8cc3b docs(skills): sync calendar reference between mono and multi layouts 2026-08-11 16:21:20 +08:00
前津 a5902ca233 Merge upstream/main into chat response branch 2026-08-11 16:20:06 +08:00
玉澜 4665b42bbf fix(skill): preserve installer caches during refresh 2026-08-11 16:16:49 +08:00
github-actions[bot] 0fb332c3f3 Merge pull request #934 from DingTalk-Real-AI/feat/eval-dispatch
ci: add /eval PR comment dispatch for internal MCP evaluation
2026-08-11 16:15:19 +08:00
john 16273de554 Merge branch 'main' into feat/skill-mode-migration 2026-08-11 16:13:48 +08:00
xlb1130 28669ffeee Merge branch 'main' into fix/85313115-chat-catalog-tools 2026-08-11 16:04:49 +08:00
长真 fa5bc65d66 fix(chat): preserve conversation id alias contracts 2026-08-11 16:04:11 +08:00
chichuan 27b16b190f Merge branch 'main' into feat/eval-dispatch 2026-08-11 15:47:54 +08:00
github-actions[bot] de1e1aaf6c Merge pull request #913 from DingTalk-Real-AI/codex/fix-im-reliability
fix(chat): harden IM search, card updates, and message workflows
2026-08-11 15:47:27 +08:00
chichuan 20d1f7c614 feat(eval-dispatch): optional sha= for own-PR dispatch; structural cases ref validation
- /eval on one's own PR may omit sha=: the guard auto-pins the
  dispatch-time head (commenter == PR author leaves no third-party
  swap window); dispatching another author's PR still requires the
  explicit reviewed SHA (keeps the P1-2 TOCTOU remedy where the
  threat lives)
- cases= is now validated structurally per git check-ref-format
  semantics (leading/trailing//double slashes, '..', dot-leading
  components, .lock suffixes) and rejects '-'-leading values to
  prevent git fetch option injection (review P2)
2026-08-11 15:46:16 +08:00
chichuan 233e0359e4 chore(eval-dispatch): seed allowlist with 53 internal contributors 2026-08-11 15:45:33 +08:00
chichuan ad6837d694 feat(eval-dispatch): allowlist tier for self-service PR evaluation
Users listed in .github/eval-allowlist.txt (default branch, PR-reviewed)
may dispatch /eval for their own PRs only; write/maintain/admin retain
dispatch for any PR. Fail-closed on permission API 404/network errors.
2026-08-11 15:45:33 +08:00
前津 49afa82d27 chore: rerun ci 2026-08-11 15:37:41 +08:00
john aabee99e3f Merge branch 'main' into feat/skill-mode-migration 2026-08-11 15:35:23 +08:00
玉澜 3eda3b5ce6 docs: align unified framework scope with dev pilot 2026-08-11 15:32:06 +08:00
玉澜 49ab7a46f4 fix(devapp): preserve pagination contract during dry-run 2026-08-11 15:30:40 +08:00
长真 d500f2fe5f chore: rerun CI 2026-08-11 15:29:52 +08:00
克谨 7849116a69 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability 2026-08-11 15:17:59 +08:00
克谨 b082135e6e test(chat): cover scoped search branches 2026-08-11 15:17:48 +08:00
Anonymity-0 bcc9e27da0 Merge branch 'main' into feat/t07-chat-response-envelope 2026-08-11 15:15:51 +08:00
玉澜 cf64f2ad02 Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 15:14:39 +08:00
玉澜 5ab46921c5 fix: preserve legacy errors and devdoc pagination contract 2026-08-11 15:13:18 +08:00
xlb1130 f8a031564a Merge branch 'main' into fix/85313115-chat-catalog-tools 2026-08-11 15:08:50 +08:00
github-actions[bot] 9ae0191270 Merge pull request #932 from abucraft/codex/aitable-workflow-run-history
feat: add aitable workflow run and history commands
2026-08-11 07:08:34 +00:00
玉澜 2989c1db37 Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 14:56:53 +08:00
Anonymity-0 eaee7f1c6f Merge branch 'main' into feat/t07-chat-response-envelope 2026-08-11 14:52:23 +08:00
chichuan 211a5fa393 Merge branch 'main' into codex/aitable-workflow-run-history 2026-08-11 14:33:14 +08:00
xlb1130 103b188458 Merge branch 'main' into fix/85313115-chat-catalog-tools 2026-08-11 14:17:11 +08:00
chichuan 8619d90119 Merge remote-tracking branch 'origin/main' into feat/eval-dispatch 2026-08-11 14:16:33 +08:00
长真 156d95e6d1 fix(chat): complete catalog leaf contracts 2026-08-11 14:16:23 +08:00
玉澜 9e3a083c27 Merge remote-tracking branch 'origin/main' into feat/skill-mode-migration 2026-08-11 14:12:43 +08:00
克谨 af199e73e2 Merge origin/main into codex/fix-im-reliability 2026-08-11 14:10:05 +08:00
github-actions[bot] fd24619437 Merge pull request #935 from xiaoji121/fix/json-output-doc-export-drive-download
fix: return JSON receipts for exports and downloads
2026-08-11 14:08:11 +08:00
前津 b1f5c67e9c Merge upstream/main into chat response branch 2026-08-11 14:00:34 +08:00
玉澜 037deefe67 Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 14:00:07 +08:00
chichuan 42e764a7a8 fix(ci): harden eval dispatch authorization 2026-08-11 13:57:42 +08:00
玉澜 3a0d814276 docs(skill): remove confirmation bypass examples 2026-08-11 13:52:24 +08:00
Dongming Ji 6337058d15 Merge branch 'main' into fix/json-output-doc-export-drive-download 2026-08-11 13:51:46 +08:00
克谨 d38868c8aa Merge origin/main into codex/fix-im-reliability 2026-08-11 13:51:27 +08:00
玉澜 06ed3aeeb3 fix: harden unified result rollout contracts 2026-08-11 13:47:07 +08:00
github-actions[bot] de8040ecc2 Merge pull request #938 from xlb1130/feat/im-page-all-pagination
docs(chat): expose typed message pagination help
2026-08-11 13:36:43 +08:00
Dongming Ji 96f406be6b Merge branch 'main' into fix/json-output-doc-export-drive-download 2026-08-11 13:34:55 +08:00
Anonymity-0 b244df1634 Merge branch 'main' into feat/t07-chat-response-envelope 2026-08-11 13:31:37 +08:00
玉澜 f3ddbb2db0 fix(upgrade): preserve skill cache during refresh 2026-08-11 13:17:07 +08:00
克谨 0d99d18acc test(chat): align update-card selection copy 2026-08-11 12:44:13 +08:00
xlb1130 9377abc5f6 Merge branch 'main' into feat/im-page-all-pagination 2026-08-11 12:38:16 +08:00
长真 eb3f7328bb fix(chat): tighten catalog safety contracts 2026-08-11 12:17:31 +08:00
长真 3c445ce73a fix(chat): to #85313115 register missing catalog tools 2026-08-11 12:17:31 +08:00
玉澜 fbdb5e8d4d Merge remote-tracking branch 'origin/main' into feat/skill-mode-migration 2026-08-11 12:07:58 +08:00
克谨 e8ca78fe49 Merge origin/main into codex/fix-im-reliability 2026-08-11 12:07:27 +08:00
玉澜 cc7e7bf0e0 Merge remote-tracking branch 'origin/main' into feat/skill-mode-migration
# Conflicts:
#	internal/app/skill_setup.go
#	internal/app/skill_setup_test.go
2026-08-11 12:06:59 +08:00
github-actions[bot] b923f522d5 Merge pull request #912 from aqruan/fix/minutes-permission-apply-policy-int
fix(minutes): type permission apply --policy as int
2026-08-11 04:01:32 +00:00
玉澜 ef73257a69 Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 11:58:36 +08:00
Dennis 461b9b773a feat(minutes): align and expand shortcut workflows 2026-08-11 11:52:57 +08:00
克谨 28bc577e88 Merge origin/main into codex/fix-im-reliability 2026-08-11 11:50:22 +08:00
克谨 82dfee7291 fix(chat): preserve layered IM workflow contracts 2026-08-11 11:48:39 +08:00
wxianfeng bab7c8879b Merge remote-tracking branch 'upstream/main' into feature/aone85277391-event-runtime-token-handoff 2026-08-11 11:48:08 +08:00
Dongming Ji 1d2edbaa9f Merge branch 'main' into fix/json-output-doc-export-drive-download 2026-08-11 11:45:16 +08:00
xlb1130 25a5f5b7d2 Merge branch 'main' into feat/im-page-all-pagination 2026-08-11 11:44:24 +08:00
wxianfeng 82b17ced32 Merge upstream/main into feature/aone85277391-event-runtime-token-handoff 2026-08-11 11:37:37 +08:00
Anonymity-0 7945f44c9a Merge branch 'main' into feat/t07-chat-response-envelope 2026-08-11 11:29:59 +08:00
chichuan 0b43905697 Merge branch 'main' into fix/minutes-permission-apply-policy-int 2026-08-11 11:29:04 +08:00
李晟 28227b19c7 Merge branch 'main' into codex/aitable-workflow-run-history 2026-08-11 11:28:44 +08:00
github-actions[bot] 622632908e Merge pull request #943 from DingTalk-Real-AI/codex/fix-helper-ci-sharding
ci: shard helper changes through full suite
2026-08-11 11:27:09 +08:00
wxianfeng 63dbf98cdf test(event): cover runtime token rejection on Windows to #85277391 2026-08-11 11:22:48 +08:00
玉澜 8034f0c2dc fix: preserve nested error operation context 2026-08-11 11:15:36 +08:00
chichuan 69cef74e1d Merge branch 'main' into feat/eval-dispatch 2026-08-11 11:10:05 +08:00
chichuan 2ec25ebb98 Merge branch 'main' into fix/minutes-permission-apply-policy-int 2026-08-11 11:08:54 +08:00
Dongming Ji bccc9eb056 Merge branch 'main' into fix/json-output-doc-export-drive-download 2026-08-11 11:03:13 +08:00
玉澜 5b0e44290e Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 11:02:00 +08:00
liangxiaoqin.lxq 4bd9f75231 cr修复1 2026-08-11 10:57:44 +08:00
liangxiaoqin.lxq 8f8f64c391 cr修复,增加测试 2026-08-11 10:57:44 +08:00
liangxiaoqin.lxq ae9caa06af cr修复 2026-08-11 10:57:44 +08:00
liangxiaoqin.lxq ee0c3507a5 补充测试 2026-08-11 10:57:44 +08:00
liangxiaoqin.lxq 72a9902254 wiki feed list命令 2026-08-11 10:57:44 +08:00
liangxiaoqin.lxq 5f337e0ce5 wiki feed list命令:新增时间格式化/字段裁剪 2026-08-11 10:57:44 +08:00
xlb1130 2274fd96f0 Merge branch 'main' into feat/im-page-all-pagination 2026-08-11 10:55:10 +08:00
chichuan 10fe258e4b ci: shard helper changes through full suite 2026-08-11 10:54:42 +08:00
github-actions[bot] 22ab166c9b Merge pull request #905 from wxianfeng/feat/dws-event-oa
feat(event): support personal OA approval events
2026-08-11 02:46:51 +00:00
玉澜 01a7b20026 fix(skill): keep setup confirmation and Windows tests safe 2026-08-11 10:46:40 +08:00
阮知夏 d3e444cb56 docs(changelog): move the Minutes policy notes into Unreleased
The two Minutes notes (permission apply --policy int typing and the skill
reference updates) landed in the released 1.0.58-beta.2 section after the
branch merged main. That rewrites published release notes and would drop
both notes from the next release generated out of Unreleased. Move them
verbatim into a Changed subsection under Unreleased; the beta.2 section is
byte-identical to main again.
2026-08-11 10:43:51 +08:00
Anonymity-0 6fdd17d3b6 Merge branch 'main' into feat/t07-chat-response-envelope 2026-08-11 10:43:18 +08:00
玉澜 5c2181a31d test: require envelope-safe fields projection 2026-08-11 10:30:23 +08:00
克谨 0148ad1800 test(chat): cover scoped search error fallbacks 2026-08-11 10:29:53 +08:00
前津 956819663d chore: retrigger CI 2026-08-11 10:29:50 +08:00
玉澜 670ab1fd5e Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 10:24:36 +08:00
玉澜 b299400017 fix: preserve result envelope with fields 2026-08-11 10:23:59 +08:00
玉澜 3afcabc41d fix: report output publication failures 2026-08-11 10:20:05 +08:00
炳昱 4a4a1e0407 Merge branch 'main' of https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli into feat/dws-event-oa 2026-08-11 10:18:55 +08:00
玉澜 62541947e7 Merge remote-tracking branch 'origin/main' into feat/skill-mode-migration 2026-08-11 10:10:01 +08:00
aqruan e38fd9ab93 Merge branch 'main' into fix/minutes-permission-apply-policy-int 2026-08-11 10:09:53 +08:00
长真 fb33a0b9e0 Merge remote-tracking branch 'origin/feat/im-page-all-pagination' into feat/im-page-all-pagination 2026-08-11 09:59:50 +08:00
长真 e94c7063ed fix(helpers): sync paged aggregate cursors 2026-08-11 09:59:10 +08:00
克谨 d6b51a04f4 fix(chat): preserve scoped search preflight errors 2026-08-11 09:55:19 +08:00
xlb1130 cd3a09e153 Merge branch 'main' into feat/im-page-all-pagination 2026-08-11 09:32:01 +08:00
李晟 2f925d29fd Merge branch 'main' into codex/aitable-workflow-run-history 2026-08-11 09:26:07 +08:00
克谨 68483f05b2 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability 2026-08-11 09:17:02 +08:00
修雨 730d3fa27f Merge pull request #941 from DingTalk-Real-AI/codex/issue-940-stdio-idempotency-race-budget
test(transport): widen stdio idempotency race budget
2026-08-11 09:06:58 +08:00
长真 6eb3efa065 fix(helpers): stop paged commands at max items 2026-08-11 08:41:18 +08:00
玉澜 a43e75e8df Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-11 00:18:18 +08:00
chichuan 783e1eeef9 fix(ci): stabilize minutes coverage contracts 2026-08-11 00:13:48 +08:00
玉澜 596da1343e fix(skill): sync installed multi-skill set safely 2026-08-11 00:10:59 +08:00
xlb1130 9e0a67f728 Merge branch 'main' into feat/im-page-all-pagination 2026-08-11 00:00:33 +08:00
长真 19f9285f8c fix(helpers): propagate paged output errors 2026-08-10 23:50:55 +08:00
修雨 c295027e84 Merge main into test/transport race budget candidate 2026-08-10 23:47:12 +08:00
克谨 3817ac230d Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability 2026-08-10 23:46:54 +08:00
chichuan 75b54a9467 Merge branch 'main' into fix/minutes-permission-apply-policy-int 2026-08-10 23:44:46 +08:00
github-actions[bot] 24437fc1a5 Merge pull request #921 from DingTalk-Real-AI/codex/interface-migration-governance
ci: govern exact CLI flag migrations
2026-08-10 23:43:19 +08:00
玉澜 2aad96fa7b Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-10 23:42:21 +08:00
玉澜 3fe2a7f5c0 fix: preserve emitted result exit codes on signals 2026-08-10 23:42:11 +08:00
Dongming Ji 851d491d2a Merge branch 'main' into fix/json-output-doc-export-drive-download 2026-08-10 23:31:35 +08:00
chichuan b55f243780 ci(test): shard helper changes in full suite 2026-08-10 23:25:52 +08:00
chichuan e9850a2e49 fix(ci): enforce stable Schema compatibility 2026-08-10 23:06:13 +08:00
玉澜 12c7b6eb89 test(skill): cover mono cleanup failure on Windows 2026-08-10 22:50:52 +08:00
玉澜 24fd2d2573 fix: use default legacy status rollout 2026-08-10 22:49:53 +08:00
玉澜 90d99d9bbe fix: preserve connect status output compatibility 2026-08-10 22:47:55 +08:00
玉澜 bc3d92ccaf Merge remote-tracking branch 'origin/main' into pr-922 2026-08-10 22:36:07 +08:00
玉澜 61adc87987 fix(skill): fail safely during layout migration 2026-08-10 22:35:36 +08:00
克谨 257ac94fb1 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability 2026-08-10 22:31:57 +08:00
xlb1130 9834a84888 Merge branch 'main' into feat/im-page-all-pagination 2026-08-10 22:31:18 +08:00
chichuan 8097943e3a Merge branch 'main' into codex/interface-migration-governance 2026-08-10 22:31:16 +08:00
chichuan a68c06540c Merge remote-tracking branch 'origin/main' into fix-912-conflict
# Conflicts:
#	CHANGELOG.md
2026-08-10 22:26:01 +08:00
长真 44c5ef13b4 test(chat): cover conversation pagination edges 2026-08-10 22:24:27 +08:00
github-actions[bot] d5a9a72fa6 Merge pull request #931 from DingTalk-Real-AI/fix/schema-compat-policy-int
ci(schema): allow reviewed parameter type migrations
2026-08-10 22:22:35 +08:00
炳昱 6f73e5187a Merge official main into feat/dws-event-oa 2026-08-10 22:21:25 +08:00
chichuan b7918be6f3 fix(ci): require stable flag migration reference 2026-08-10 22:16:00 +08:00
玉澜 a37f614be4 test: cover unified schema validation edges 2026-08-10 22:15:04 +08:00
Dongming Ji b70e109e89 Merge branch 'main' into fix/json-output-doc-export-drive-download 2026-08-10 22:13:31 +08:00
chichuan b84b56d9f8 Merge origin/main into interface migration governance 2026-08-10 22:03:18 +08:00
chichuan e66cd95c51 Merge remote-tracking branch 'origin/main' into fix/schema-compat-policy-int 2026-08-10 21:59:55 +08:00
chichuan 7e8b216e07 ci(schema): compare the full parameter contract for reviewed type migrations
Auto-CR (P1) correctly flagged that the carve-out's "nothing else changed"
guard was keyed on len(otherFailures) == 0, which only observes changes the
gate already judges incompatible. Several parameter contract changes are
individually compatible and so produce no failure at all: relaxing required or
cli_required, clearing required_when, widening enum, clearing interface_type,
and clearing property through a reviewed mapping exclusion. Any of those could
have ridden along with a reviewed type migration, leaving the exemption wider
than both its documentation and what the entry actually reviewed.

Replace the failure-list heuristic with a real equality check over every
published field except Type. Comparing the struct also means a field added to
parameterSchema later is covered automatically, instead of silently widening
every existing entry. The type check moves back ahead of the field loop because
it no longer needs to observe the other findings.

Add a rejection case for each individually-compatible direction. Each case first
asserts that the drift alone really is compatible, so it keeps exercising the
equality guard instead of quietly duplicating one of the incompatible-bundle
cases.

Verified against the previous implementation: with the old guard all six new
cases fail while the nine incompatible-bundle cases still pass, which is exactly
the gap that was reported.
2026-08-10 21:52:58 +08:00
前津 08cf334cc1 Merge remote-tracking branch 'upstream/main' into feat/t07-chat-response-envelope 2026-08-10 21:41:03 +08:00
玉澜 c515f7c1e5 test(ci): cover aggregate changed-code edges 2026-08-10 21:28:58 +08:00
玉澜 12088f2d44 Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-10 21:24:58 +08:00
玉澜 d9c74fbe96 feat: add result schemas and devapp pagination 2026-08-10 21:23:23 +08:00
克谨 3fc144a699 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability 2026-08-10 21:19:53 +08:00
github-actions[bot] 5501c9f1a5 Merge pull request #933 from pengzhihan47-star/codex/doc-shortcut_and_skill_opt
feat(doc): harden dingtalk-doc shortcuts, contracts, and verification
2026-08-10 21:11:23 +08:00
玉澜 8eae408e28 fix(ci): pin synthetic merge to event SHA 2026-08-10 21:08:55 +08:00
玉澜 9f3df91584 fix(ci): pin merge checkout and cover Windows edges 2026-08-10 21:04:08 +08:00
玉澜 37cccdbc0e Merge remote-tracking branch 'origin/main' into pr-922 2026-08-10 20:51:58 +08:00
前津 1522653844 test(chat): cover existing operation context 2026-08-10 20:41:37 +08:00
长真 4d274c9da3 fix(chat): merge conversation message pagination 2026-08-10 20:34:43 +08:00
玉澜andCursor b6851e641e fix(skill): back up skill dirs before removal and satisfy coverage gate
Address the two P1 review findings and the coverage-gate CI failures:
- Every install/upgrade path that removes a skill dir (opposite-mode
  leftovers, stale dingtalk-* / dws-shared, and same-name refreshes) now
  moves the directory to ~/.dws/skill-backups/<stamp>/ first across
  install.sh, install-skills.sh, install.ps1, install.js, `dws skill
  setup`, and `dws upgrade`. A backup failure preserves the original
  directory and never removes it.
- Remove --yes from every copyable `dws skill setup` example and document
  what the command may remove; add regression tests that declining the
  confirmation performs no removal and that the confirmation previews
  every directory slated for backup+removal.
- Rename the skill-mode tests to the TestCrossPlatformCoverage* prefix so
  the platform coverage gate selects them, and add edge tests for the
  backup/prune/cleanup fallback branches, restoring changed-code coverage
  to 100%.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 20:23:35 +08:00
前津 357f31376d fix(chat): preserve operation on read failures 2026-08-10 20:15:04 +08:00
如椽 7ffb48c9ae test: cover JSON export and download receipts 2026-08-10 19:57:37 +08:00
前津 0f178f8382 ci: rerun interrupted tests 2026-08-10 19:37:49 +08:00
如椽 a24fd542c0 Merge remote-tracking branch 'upstream/main' into fix/json-output-doc-export-drive-download
# Conflicts:
#	CHANGELOG.md
2026-08-10 19:28:55 +08:00
前津 910fb4a9b1 fix(chat): preserve legacy message context 2026-08-10 19:06:59 +08:00
长真 b8418b6a5f test(chat): cover paged command edge cases 2026-08-10 18:59:24 +08:00
修雨 af71efd253 test(transport): widen stdio idempotency race budget
Refs #940

Authority: https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/940

Assignment: https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/issues/940#issuecomment-5239203628
2026-08-10 18:58:21 +08:00
xlb1130 8c19b0048b Merge branch 'main' into feat/im-page-all-pagination 2026-08-10 18:24:07 +08:00
长真 a9751fa74d docs(changelog): drop typed pagination entry from branch 2026-08-10 18:23:41 +08:00
镜玄 8a0bd34e13 Merge remote-tracking branch 'upstream/main' into codex/aitable-workflow-run-history
# Conflicts:
#	CHANGELOG.md
2026-08-10 18:17:15 +08:00
长真 5a160cefd8 docs(chat): expose typed message pagination help 2026-08-10 17:59:38 +08:00
炳昱 a9c0e0409c Merge remote-tracking branch 'official/main' into feat/dws-event-oa 2026-08-10 17:58:42 +08:00
炳昱 9616441e54 fix(skill): migrate retired shared skill 2026-08-10 17:58:33 +08:00
柏智 eefe6f05e1 fix(schema): review doc import constraint transition 2026-08-10 17:49:27 +08:00
前津 89feea7971 chore: rerun CI 2026-08-10 17:39:40 +08:00
前津 24b61b1c17 fix(chat): reject empty message read responses 2026-08-10 17:39:40 +08:00
前津 edbc8275b6 chore: rerun CI 2026-08-10 17:39:40 +08:00
前津 27afa806ca feat(chat): unify typed and shortcut message contracts 2026-08-10 17:39:40 +08:00
柏智 6598292b1b fix(doc): allow import to default root 2026-08-10 17:32:23 +08:00
柏智 dea637228d fix(doc): preserve schema compatibility after review 2026-08-10 17:32:23 +08:00
柏智 a09467f1eb fix(doc): address PR 906 review feedback 2026-08-10 17:32:23 +08:00
柏智 34feb348af feat(doc): harden shortcuts and skill routing 2026-08-10 17:32:23 +08:00
如椽 08ee5dc573 fix: emit JSON receipts for exports and downloads 2026-08-10 17:21:54 +08:00
chichuan 6b1a1a6201 Merge branch 'main' into fix/schema-compat-policy-int 2026-08-10 17:19:54 +08:00
镜玄 5c45bd57da test: cover aitable workflow validation branches 2026-08-10 17:13:58 +08:00
克谨 349537e336 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability 2026-08-10 17:13:12 +08:00
chichuan 3af7adaad6 Merge branch 'main' into codex/release-fragments 2026-08-10 17:06:47 +08:00
github-actions[bot] 9f60cdeef1 Merge pull request #920 from Anonymity-0/feat/card-reply-mentions
feat(chat): support mentions in streaming card creation
2026-08-10 17:06:24 +08:00
前津 258caa5906 fix(chat): fail safely when card mention tag is missing 2026-08-10 16:39:51 +08:00
前津 a0d59a79aa feat(chat): prepend card mention tag to content 2026-08-10 16:39:51 +08:00
前津 c7148f3ebb docs(chat): clarify streaming card mention content 2026-08-10 16:39:51 +08:00
前津 ef29b48a55 fix(chat): keep skill within context budget 2026-08-10 16:39:51 +08:00
前津 5c33ea526e feat(chat): support mentions in streaming card creation 2026-08-10 16:39:51 +08:00
chichuan 6d3b54b25f Merge branch 'main' into fix/schema-compat-policy-int 2026-08-10 16:37:45 +08:00
玉澜 b6101bdbc3 fix: preserve typed error fallback contract 2026-08-10 16:35:22 +08:00
克谨 b243b38d65 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability 2026-08-10 16:32:20 +08:00
github-actions[bot] 867bb44586 chore: update beta formula for v1.0.58-beta.2 [skip ci] 2026-08-10 08:22:55 +00:00
镜玄 819355b31f feat: add aitable workflow run and history commands 2026-08-10 16:20:22 +08:00
玉澜 538f2aba6f fix: complete unified output lifecycle coverage 2026-08-10 16:19:07 +08:00
克谨 c3d4de52a7 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability 2026-08-10 16:09:52 +08:00
chichuan 4bbd42cc25 Merge pull request #930 from DingTalk-Real-AI/codex/changelog-v1.0.58-beta.2
chore(release): seal v1.0.58-beta.2 changelog
2026-08-10 16:04:27 +08:00
wxianfeng 5004ed8ae6 fix(event): securely hand off runtime token to detached bus to #85277391 2026-08-10 15:59:11 +08:00
chichuan fd0c3350f3 ci(schema): allow reviewed parameter type migrations
schema-compatibility is the third check in the same Interface Integrity job,
after the two CLI interface gates. It also rejected every published parameter
type change outright. Because the earlier gates failed first and `set -e`
stopped the step from ever running, this one never surfaced in CI, so the
previous exemption only covered two thirds of the problem.

checkParameterCompatibility now consults a precise allowlist: the tool path,
parameter name and both type values must match exactly, making it
direction-sensitive by construction, and it applies only when nothing else the
gate checks about the parameter moved (default, interface_default, format,
property, interface_type, required, cli_required, required_when, enum). The type
check moved to the end of the function so the carve-out can see those findings;
ordering is unobservable because the result is sorted.

The only entry is "minutes/minutes.apply_minutes_permission" parameter "policy"
migrating from "string" to "integer" (for #912). That type is projected from the
Cobra flag type (provenance cobra_flag_type), so it describes how the CLI accepts
a value. Consumers build a command line from it, and "--policy 4" is the same
argv under either declaration — a quoted "--policy \"4\"" still reaches pflag as
4 — while RunE keeps enforcing the same [2,4] domain. The parameter maps to
property "policyId", which the command has always sent as a number, so "integer"
is closer to the actual request than "string" was.

Table values must be the canonical form schemaType emits: the JSON encoding of
the type keyword, so `"string"` with its quotes rather than a bare string. The
guard test recomputes both through schemaType and checks the decoded name
against the closed JSON Schema type set — reviewedInterfaceRefRedirect was
silently disabled twice by exactly this class of spelling mistake.
2026-08-10 15:57:15 +08:00
chichuan 2cc24de505 chore(release): seal v1.0.58-beta.2 changelog 2026-08-10 15:55:25 +08:00
炳昱 0e14f69aae Merge commit '6575301a3a7fef264f0550185a0bee13087be729' of https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli into feat/dws-event-oa 2026-08-10 15:43:37 +08:00
炳昱 9f14035483 test(event): cover subscription and migration failures 2026-08-10 15:42:44 +08:00
chichuan a5672152a7 ci: add /eval comment dispatch workflow for internal MCP evaluation (Aone JSON trigger contract) 2026-08-10 15:34:01 +08:00
chichuan 2d38abe681 feat(ci): PR 评论 /eval 触发内网 MCP 评测的 dispatch workflow
- issue_comment 触发,author_association ∈ OWNER/MEMBER/COLLABORATOR 门控
- 不 checkout、不执行 PR 代码;触发通道与凭证全部经 secrets 注入
- scripts/ci/eval_comment_parse.py 解析 /eval <products> [cases=<ref>](10 个单测)
2026-08-10 15:34:01 +08:00
阮知夏 f478b7d3e1 Merge remote-tracking branch 'origin/main' into fix/minutes-permission-apply-policy-int
# Conflicts:
#	CHANGELOG.md
2026-08-10 15:27:38 +08:00
克谨 d84c73e8b2 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability 2026-08-10 15:27:09 +08:00
github-actions[bot] 6575301a3a Merge pull request #926 from DingTalk-Real-AI/ci/reviewed-flag-type-exemption
ci(interface): allow reviewed flag type migrations
2026-08-10 15:22:40 +08:00
玉澜 2359de69fa fix: preserve unified failures with output files 2026-08-10 15:08:35 +08:00
炳昱 8daf5c71cd Merge commit '93a20718372f434f9eda84850df816a7c29c34fc' of https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli into feat/dws-event-oa 2026-08-10 15:03:39 +08:00
玉澜 b62f6c0c02 fix: reset unified results for each execution 2026-08-10 15:03:33 +08:00
chichuan 43121f1d8f test(interface): cover the merge-path bundled-regression branches
mergedFlagContractOtherwiseChanged was only ever exercised on the path where
every condition holds still, because `||` short-circuits: with no reviewed
entry the first operand already decides the outcome and the function is never
called at all. That left its five regression branches uncovered and put
changed-code coverage at 88.0952% against a 100% target.

Add the merge-path counterpart of the checkCompatibility bundled-regression
table, pairing each of shorthand / required / hidden / no-opt / scope with the
reviewed type change and requiring the type failure to reappear. Changed-code
coverage is now 100%.
2026-08-10 15:02:08 +08:00
玉澜 25b5e0b9fa Merge remote-tracking branch 'upstream/main' into feat/unified-command-framework-core 2026-08-10 14:42:24 +08:00
玉澜 03bda02e04 test: close unified framework contract coverage 2026-08-10 14:41:41 +08:00
chichuan cd02fe71e6 ci(interface): allow reviewed flag type migrations
The authoritative interface baseline and command-compatibility gates
rejected every flag type change on a historical command, with no review
channel — even when the new type only moves the same validation from RunE
to flag parsing. Both now consult a precise allowlist.

An entry must match command path, flag name and both type names exactly,
so it is direction-sensitive by construction, and it applies only when
nothing else about the flag moved (shorthand, required, hidden, no-opt,
scope). A bundled regression re-reports the type change.

The first and only entry is "dws minutes permission apply --policy" moving
from string to int (for #912): the old RunE parsed with
strconv.ParseInt(v, 10, 64) and enforced [2,4], the new one lets pflag
parse with base 0 and still enforces [2,4], so the historical set of
successful invocations is a subset of the new one. Base 0 additionally
accepts spellings like "0x3", which widens rather than narrows. Defaults
are excluded from the guard because the migration necessarily changes one.

In the snapshot gate the exemption resolves against the canonical
Command.Path, never the alias-expanded accepted path: an aliased command is
compared once per accepted spelling, so keying on that would let every
alias bypass the table.

The table is duplicated because check-authoritative-interface-baselines.sh
copies the whole scripts/policy/interface-baseline directory into a
worktree checked out at a historical revision and builds it there, so that
copy cannot import a package this branch adds. A guard test fails if the
two copies drift.
2026-08-10 14:26:32 +08:00
克谨 431f64be85 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability 2026-08-10 14:13:46 +08:00
github-actions[bot] 93a2071837 Merge pull request #914 from maoqxxmm/codex/align-sheet-skill-docs
docs(sheet): align mono and multi skill references
2026-08-10 14:11:48 +08:00
玉澜 4da1e52b08 fix(dev): make connect dry-run plans auditable 2026-08-10 13:47:56 +08:00
玉澜 409ee0cb84 refactor: keep signal escalation portable 2026-08-10 13:35:26 +08:00
玉澜 7cf7598ef2 fix(dev): preserve published connect safety metadata 2026-08-10 13:24:14 +08:00
玉澜 9b220d0ee6 test: keep signal coverage portable 2026-08-10 13:14:57 +08:00
玉澜 d7ae59753d fix: preserve legacy formatter bytes during rollout 2026-08-10 13:09:28 +08:00
玉澜 72b2af1d1d feat(dev): integrate unified command results 2026-08-10 13:00:14 +08:00
玉澜 bd41da8caf fix: make signal escalation portable 2026-08-10 12:48:11 +08:00
玉澜 cc0e179a8d refactor: remove protocol version naming 2026-08-10 12:28:28 +08:00
玉澜 e0f66384e2 fix: keep framework core lint-clean 2026-08-10 12:25:50 +08:00
玉澜 2dd067562e feat: add unified command result framework core 2026-08-10 12:22:46 +08:00
克谨 d979d86fa3 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability
# Conflicts:
#	internal/helpers/chat.go
2026-08-10 12:21:37 +08:00
xiatian 33730337f3 Merge remote-tracking branch 'upstream/main' into codex/align-sheet-skill-docs 2026-08-10 12:06:16 +08:00
xiatian 6be12655dc fix(skills): address sheet review feedback 2026-08-10 12:06:07 +08:00
github-actions[bot] cf3bcb380f Merge pull request #897 from Anonymity-0/feat/chat-message-help-id-chain
docs(chat): document post-send message ID chain
2026-08-10 04:01:36 +00:00
Anonymity-0 89e8bd7015 Merge branch 'main' into feat/chat-message-help-id-chain 2026-08-10 11:42:02 +08:00
chichuan 64e1dcc150 test: make temp failure portable on Windows 2026-08-10 11:36:53 +08:00
chichuan f3ecac1ad1 ci: satisfy workflow shell lint 2026-08-10 11:14:23 +08:00
阮知夏 b150911da9 docs(minutes): scope permission member-uids rule and add apply routing 2026-08-10 11:09:08 +08:00
玉澜andCursor e02e4a666d Merge latest main into feat/skill-mode-migration
Upstream reorganized the multi-skill layout (#887: long-tail skills folded
into dingtalk-misc, dws-shared renamed to dingtalk-shared). Conflict
resolution keeps this branch's multi-by-default semantics (install.sh /
install.ps1 / skill setup default to multi; interactive prompts list multi
first) and adapts the cleanup paths to the rename: cleanup predicates now
recognize both dingtalk-shared (new bundle name, covered by the dingtalk-
prefix) and the legacy dws-shared so full installs and mode switches remove
pre-rename leftovers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 11:03:33 +08:00
chichuan 46ae1c50fe ci: govern exact CLI flag migrations 2026-08-10 10:46:20 +08:00
阮知夏 37d6a4ea2e Merge remote-tracking branch 'origin/main' into fix/minutes-permission-apply-policy-int
# Conflicts:
#	CHANGELOG.md
2026-08-10 10:32:21 +08:00
克谨 20c8e0dfec Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability
# Conflicts:
#	scripts/policy/schema-compat/main.go
2026-08-10 10:22:06 +08:00
xiatian 5de36d783a Merge remote-tracking branch 'upstream/main' into codex/align-sheet-skill-docs
# Conflicts:
#	CHANGELOG.md
#	skills/mono/references/products/sheet/sheet-dimension-operations.md
#	skills/mono/references/products/sheet/sheet-export.md
#	skills/mono/references/products/sheet/sheet-style-format.md
#	skills/multi/dingtalk-misc/references/sheet/sheet-dimension-operations.md
#	skills/multi/dingtalk-misc/references/sheet/sheet-export.md
2026-08-10 10:21:37 +08:00
长真 cde050f146 test(chat): cover paged command delay sleep 2026-08-10 10:10:16 +08:00
github-actions[bot] 2bc4ded969 Merge pull request #883 from Huwenjiao/feat/sheet-sync-a1-a2
feat(sheet): CSV export, style extensions and create-with-data
2026-08-10 10:05:57 +08:00
xiatian 468f9200f6 Merge remote-tracking branch 'upstream/main' into codex/align-sheet-skill-docs
# Conflicts:
#	CHANGELOG.md
2026-08-10 09:52:53 +08:00
克谨 e02410dae6 fix(ci): review card confirmation hardening 2026-08-10 01:02:28 +08:00
克谨 74d31566ff fix(chat): align native card update confirmation 2026-08-10 00:42:27 +08:00
克谨 6765a74d83 Merge remote-tracking branch 'origin/main' into codex/fix-im-reliability
# Conflicts:
#	internal/shortcut/smart/compatibility_coverage_test.go
#	internal/shortcut/smart/search_msg.go
#	internal/shortcut/smart/search_msg_execution_test.go
#	skills/multi/dingtalk-chat/references/contracts.md
2026-08-10 00:30:24 +08:00
克谨 13e5914638 test(chat): close changed-code coverage gaps 2026-08-10 00:07:53 +08:00
阮知夏 8fcc6baee0 Merge remote-tracking branch 'origin/main' into fix/minutes-permission-apply-policy-int
# Conflicts:
#	CHANGELOG.md
2026-08-10 00:07:42 +08:00
阮知夏 6774d423b7 docs(minutes): drop hot-word delete references from skill docs 2026-08-09 23:43:51 +08:00
长真 8156528c05 fix(chat): harden IM pagination cursor mapping 2026-08-09 20:20:20 +08:00
huwenjiao.hwjandClaude Opus 5 6f61183732 fix(sheet): reject sheet prefixes that are blank after trimming
--ranges validated the position of "!" in the raw string and then returned the
trimmed halves, so " !A1:B2" was accepted and produced a set_cell_range /
clear_range operation carrying sheetId: "". Depending on how the server treats
an empty sheetId, the whole batch_update fails, or — worse — the operation lands
on the default worksheet instead of the one the user named, while the command
reports success.

Both halves must now be non-empty *after* trimming. batch-clear grew the same
hole independently (it duplicated the split inline); it now shares
splitSheetPrefixedRange, so the invariant holds by construction rather than by
being repeated correctly in two places.

batch-set-style --batch had the same gap at the JSON level: it only rejected
sheetId == "", so "   " passed. It now judges the trimmed value but still sends
the raw one — sheetId may be a worksheet *name*, and names may legitimately
carry leading or trailing spaces, so trimming on the user's behalf would target
a different sheet. The --ranges form cannot express such a name anyway, which is
what --batch is for.

TestBlankSheetIdentifierIsRejectedBeforeAnyRemoteCall covers all three entry
points with calls == 0; TestBatchStyleSheetIDIsSentVerbatimNotTrimmed pins the
no-normalisation half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 18:33:56 +08:00
huwenjiao.hwjandClaude Opus 5 332b74e8ce refactor(sheet): split create-with-data and export-csv onto their own leaves
Both capabilities were added as flags on an existing leaf, and in both cases
the leaf's published interface stopped describing what the command did:

- `sheet create --values/--sheets/--styles` orchestrates create → probe →
  resolve default worksheet → write → read back → optional styles, yet the
  leaf still published `interface_mode: mcp` + `create_workspace_sheet`.
- `sheet export --export-format csv` reads `get_range_as_csv` and never
  invokes `submit_export_job`, yet the leaf published `submit_export_job`.

Each moves to its own command, declaring the interface it actually uses:
`sheet create-with-data` is `composite` with a reviewed reason and no
`interface_ref`; `sheet export-csv` is `mcp` + `get_range_as_csv`. Both are
pinned in the interface-disposition contract test.

`sheet create` and `sheet export` are restored byte-for-byte to main, so the
compatibility gates see two `command_added` additions instead of four
locked-field changes. The split also removes a user-visible trap: `--range`
without `--export-format csv` used to be silently discarded and the whole
workbook exported; the cross-format flags no longer exist, pinned by
TestSheetExportAndExportCsvFlagsDoNotLeak.

Drops the 8 now-stale mapping-ledger exclusions that covered the flags on
`sheet.create_workspace_sheet` / `sheet.submit_export_job`, shrinking this
branch's exclusion surface. Skill references and CHANGELOG follow the split.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 17:47:50 +08:00
长真 e5a60386c6 feat(chat): add typed IM message pagination 2026-08-09 17:18:53 +08:00
huwenjiao.hwjandClaude Opus 5 10bb2ec205 docs(changelog): record the composite-orchestration contract gap on both leaves
sheet create 带 --values/--sheets/--styles 时会依次执行探活、重命名、写入、回读与
可选样式操作,已不是一次 create_workspace_sheet 直接调用;sheet export
--export-format csv 实际读 get_range_as_csv、完全不碰 submit_export_job。两个叶子
却都仍声明 interface_mode: mcp 加单一 interface_ref,Schema 消费者会误判为单次
RPC,并把编排步骤或另一分支的参数当成该 RPC 的入参。

此前只有 csv 分支记了一条含糊的已知限制、create 侧完全没记。现在两条都写清楚:
不准确的具体表现、影响面(仅审计元数据,不影响执行),以及诚实的声明方式——拆成
独立叶子,或把叶子改为 interface_mode: composite。同时记下后者为何留待后续迁移:
对既有叶子而言 interface_mode 变更在 schema-compatibility 中是无条件失败,
checkToolCompatibility 对该字段没有任何豁免通道,而 interface_ref 的 reviewed
redirect 豁免明确要求 mode 保持 mcp 不变。

注意:这是把契约不准确记录成已知限制,不是修复。评审要求的是结构性修改。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 15:30:32 +08:00
huwenjiao.hwjandClaude Opus 5 ac0125e468 docs(changelog): record the stricter --length parsing as a behaviour change
--length 的解析由 fmt.Sscanf("%d") 改为 strconv.Atoi,影响 sheet
insert-dimension / delete-dimension / update-dimension 三个既有命令。这是对既有
命令的用户可见行为变更,此前只作为实现细节修掉,CHANGELOG 与 PR 描述都没记:原先
Sscanf 只消费前缀数字,"2x" 被静默当成 2 并对错误的行列数执行操作(删除方向不可
回滚);现在整个值必须是合法正整数。原先依赖宽松解析、在传畸形 --length 的脚本
升级后会开始报错,用户需要能在发布说明里找到解释。

CHANGELOG 的 Unreleased ### Changed 补一条(含升级影响与 add-dimension 不受影响
的说明),PR 描述的 Summary 补一张 Behaviour change 表。

同时补一条回归测试钉住该行为:三个命令 × 六种畸形值(2x / 3foo / "1 2" / 0x10 /
abc / 空串)都必须报错且错误信息指明 --length。文档写了的行为需要有测试守着,否则
改回宽松解析不会被发现。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 15:01:14 +08:00
huwenjiao.hwjandClaude Opus 5 f06a24ec2e fix(sheet): write export CSV atomically and reject cross-format flags
CSV 落盘从 os.WriteFile 改为仓库已有的 AtomicWrite:os.WriteFile 会先把
已存在的目标文件截断,写入中途失败(磁盘满、配额、I/O 错误)就把用户的原
文件毁掉了。改为写同目录临时文件再 rename,失败时原文件保持不变、临时文件
被清理。父目录仍先 stat 一次,保持与 xlsx 分支一致的「父目录不存在即报错」
语义,不让 AtomicWrite 的 MkdirAll 把拼错的路径悄悄建成目录。

格式分派后拒绝不属于当前分支的显式参数:--sheet-id / --range /
--value-render-option / --allow-truncated 只有 csv 分支消费,此前落到默认的
xlsx 分支会被静默忽略。自动化漏写 --export-format csv 时,用户要的 --range
被丢掉、导出的却是整篇工作簿,命令仍报成功。现在按 Flags().Changed 检测并
在提交导出任务之前报错,同时列出所有误用的参数。反向不需要检查:xlsx 分支
没有专属参数,node / output / export-format 两条分支共用。

测试覆盖 rename 失败后原文件完好且目录无临时文件残留、四个 csv 专属 flag 在
默认与显式 xlsx 下各自被拒且零远程调用、多个参数同时误用时全部列出、csv 分支
照常接受它们;另加一条登记表与实际绑定 flag 的一致性测试,防止新增 flag 漏登记
(漏登记会重新引入静默忽略,误登记共享 flag 会拒掉合法的 xlsx 调用)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 18:26:40 +08:00
huwenjiao.hwjandClaude Opus 5 8f135ecde6 fix(sheet): reject unknown and mistyped fields in border edge configs
parseBorderStyles read only style and a string color, so every other key and
any non-string color was silently dropped:
{"top":{"style":"solid","colour":"#f00"}} succeeded and drew a border with no
colour, and color: 123 did the same. That contradicts the unknown-key
rejection this PR applies to --sheets and --styles — a partially applied
style reported as success is harder to notice than an error.

Each edge now accepts only style/color, rejects near-miss spellings with the
canonical key, and fails when style or color is present with the wrong type
or empty. All three entry points (set-style --border-styles-json,
batch-set-style --ranges/--batch, and create --styles border_styles) share
parseBorderStyles, so one fix covers them; tests assert the rejection happens
before any MCP call on every path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 15:05:18 +08:00
huwenjiao.hwj a2e51f2b86 Merge remote-tracking branch 'upstream/main' into feat/sheet-sync-a1-a2 2026-08-08 14:13:02 +08:00
huwenjiao.hwjandClaude Opus 5 3d6e62fc08 docs(sheet): document the batch style caps and --styles size rules
Sweeping the same class the last review round hit: limits and behaviour this
PR added that only reached the Go long help, not the skill references an
agent actually reads.

- batch-set-style: the 200000-cell cumulative cap across all ranges was
  missing (the 100-range cap and the atomic rollback were already there).
- sheet create --styles: size must be a positive integer (a fraction is
  rejected rather than silently truncated) and the row/column range forms
  reject trailing characters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 00:17:45 +08:00
huwenjiao.hwj c6094e291e Merge remote-tracking branch 'upstream/main' into feat/sheet-sync-a1-a2
# Conflicts:
#	CHANGELOG.md
2026-08-08 00:03:17 +08:00
huwenjiao.hwjandClaude Opus 5 e026c754e0 fix(sheet): require EOF after the --values / --sheets JSON value
json.Decoder.Decode returns after one value, so `--values '[[1]] trailing'`
was accepted as a valid matrix and the document got created anyway. A paste
that ran long, leftover shell concatenation, or two JSON values glued
together would silently drop the tail and still create a document the user
never asked for — and creation cannot be rolled back atomically. Require
EOF after the first value on both flags, following decodeOARequest.

docs(sheet): document the fail-closed CSV export and --allow-truncated

The skill references still claimed an oversized table is truncated with a
warning on stderr, and omitted the flag. The command now aborts before
writing anything when the server reports hasMore, so an agent relying on the
skill would misread the result and had no way to learn how to opt in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 00:01:47 +08:00
github-actions[bot] 18030f1018 chore: update beta formula for v1.0.58-beta.1 [skip ci] 2026-08-07 15:59:04 +00:00
chichuan 6297b6b0c8 Merge pull request #915 from DingTalk-Real-AI/codex/changelog-v1.0.58-beta.1
docs: seal v1.0.58-beta.1 changelog
2026-08-07 23:46:16 +08:00
chichuan e8905a1984 docs: seal v1.0.58-beta.1 changelog 2026-08-07 23:37:52 +08:00
huwenjiao.hwjandClaude Opus 5 a097d57510 fix(sheet): validate the full --sheets contract before creating the document
sheet create promises that every structural check happens before the first
MCP request, but each sheet spec was only checked for object type and name:
columns/data/dtypes/formats/startCell were left to table_put, so a bad type
created and renamed the remote document first and failed at write time,
leaving behind a document the user never successfully asked for. Validate
every provided field against table_put's input contract up front, and reject
the malformed {"sheets":"bad"} wrapper instead of treating it as one spec.

Also fixed while auditing the same flow:
- unknown/misspelled keys are now rejected in both --sheets and --styles.
  The server DTOs are fixed beans, so a stray "datas" was silently dropped
  and the read-back probe landed on the header row: full data loss reported
  as success. Near-miss spellings get the canonical key in the message.
- columns is required (the server requires it), non-blank and trim-unique;
  dtypes/formats keys must resolve to a column, since the server looks them
  up by trimmed name and silently ignores the rest.
- sheetId inside a spec is rejected: the document does not exist yet.
- the read-back probe now honours header:false, mode:append (a fresh sheet
  appends at row 1, the startCell row is ignored) and $-absolute/lowercase
  startCell refs, which the server accepts after uppercasing.
- --values cells must be scalars; a map used to be written as "map[a:1]".
- the 30000-cell and 2000000-char write limits are enforced locally.

Docs: the --styles top level only accepts snake_case (camelCase aliases are
inner-field only), and the read-back probe is not pinned to A1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 23:37:15 +08:00
huwenjiao.hwj b566afe3e2 docs(changelog): record the csv-branch interface_ref limitation on sheet export
The --export-format csv branch reads get_range_as_csv, but the sheet export leaf
still declares interface_ref: submit_export_job, so discovering the csv
capability through Schema yields the wrong backing interface. Audit metadata
only — interface_ref is not read at runtime and routing is unaffected. Recorded
here so the limitation reaches release notes rather than living only in a code
comment; accurate attribution is tracked as follow-up.
2026-08-07 22:01:49 +08:00
huwenjiao.hwj 7405825294 fix(policy): register the reviewed interface_ref redirect in its canonical form
The allowlist added in the previous commit keyed the reviewed
sheet.range_set_style migration by bare RPC name ("update_range"), but
interface_ref holds the canonicalized JSON that parseTool produces via
canonicalRawJSON. The lookup therefore never matched, the carve-out was
effectively disabled, and the real gate failed with
`schema tool "sheet/sheet.range_set_style" changed interface_ref`.

The existing redirect test did not catch this: it registered the fixture entry
using its own value and then asserted with the same value, so any format would
have passed. That is the same mistake as keying a probe on the author's field
spelling instead of the wire contract.

- The allowlist entry now uses the compact canonical JSON, taken from the gate's
  own output rather than from pretty-printed `dws schema`.
- TestCrossPlatformCoverageReviewedRedirectKeysAreCanonicalJSON recomputes every
  registered key through canonicalRawJSON, so a bare name or a pretty-printed
  variant fails locally instead of only in CI.

sheet export keeps its reviewed mcp + submit_export_job declaration. The comment
now records the known trade-off explicitly: --export-format csv is a mutually
exclusive branch that reads get_range_as_csv and never invokes the declared
submit_export_job, so this declaration does not cover the csv branch's backing
interface. Attributing that branch is left out of scope for this change.

Verified against the real gate, not just unit tests: all four Interface
Integrity checks pass (authoritative-interface-integrity,
check-command-compatibility, schema-compatibility, skill-command-integrity).
2026-08-07 21:37:23 +08:00
huwenjiao.hwj 7eb39ad5d6 fix(sheet): narrow the interface_ref carve-out, fail closed on truncated CSV
Two review findings, plus a same-class defect found by sweeping for it.

1. compatibleInterfaceRefRedirect accepted any mcp tool repointing from any
   non-empty interface_ref to any other, as long as no other check for that tool
   failed. Schema shape cannot prove two RPCs share business semantics,
   permissions, error behaviour, or side effects, so that would have let every
   future backend swap bypass the gate it exists to enforce. It is now keyed on
   an explicit reviewedInterfaceRefRedirect allowlist of exact tool + old→new
   pairs, currently holding only the reviewed
   sheet.range_set_style: update_range → set_cell_range migration. Every other
   ref change is reported again.

2. sheet export --export-format csv only printed a stderr warning when
   get_range_as_csv returned hasMore=true, then wrote --output and reported
   success with exit code 0. Automated callers, and anyone not watching stderr,
   would treat an incomplete file as a complete export, and an existing target
   file was overwritten with truncated data. Truncation now fails before the
   write (leaving any existing file untouched) unless --allow-truncated is
   passed; with the opt-in the success line states the data is incomplete.
   --allow-truncated is registered in the reviewed mapping ledger as a local
   policy input.

3. Swept for the same classes and found sheet_dimension.go repeating the
   fmt.Sscanf("%d") prefix-parse hole in three places: insert_dimension,
   delete_dimension, and update_dimension all accepted --length "3x" as 3, so a
   malformed value silently operated on the wrong row/column count — the delete
   direction is not rollbackable. All three now use strconv.Atoi. (Checked and
   cleared: sheet csv-get also surfaces hasMore, but it has no --output and only
   returns the flag in its JSON payload, so it is not the same fail-open shape.)

Tests: allowlist rejection cases (unreviewed target ref, and the same pair on a
tool absent from the allowlist, asserted outside the table so the registration
survives until checkCompatibility runs); truncation fail-closed with a
pre-existing output file asserted byte-for-byte unchanged; --allow-truncated
write-through; and an untruncated read needing no opt-in. Changed-code coverage
stays at 100% (939 statements).
2026-08-07 21:05:47 +08:00
huwenjiao.hwj eb73b944a1 fix(sheet): reject row/column ranges with trailing characters up front
parseRowColRange gates --styles row_sizes/col_sizes before the document is
created. The row branch parsed with fmt.Sscanf(a, "%d", &r1), which consumes
only the leading digits and does not require the whole token, so "1x:3" was
silently accepted as row 1 and "2foo" as row 2. Such input passed pre-flight,
then update_dimension was sent to the wrong row after the document and data had
already been created — an unrollbackable wrong edit, the opposite of the gate's
purpose. The row branch now parses with strconv.Atoi, which requires the entire
token to be a valid integer.

The column branch had the same class of hole via a different path: parseA1Cell
appends "1" to the column token, so "A5" became "A51" and was accepted as
column A. A new isAllLetters pre-check requires the column token to be non-empty
and letters-only before parsing; genuine multi-letter columns like "AX" still
pass. With that guarantee the subsequent parseA1Cell can no longer fail, so its
now-dead error branch is removed.

Adds trailing-character rejection cases to TestParseRowColRange: "1x:3",
"2foo", "1 2:3" (rows), "A5:C", "A1", ":C" (columns), plus "AX:C" to confirm
multi-letter columns remain valid. Changed-code coverage stays at 100%.
2026-08-07 20:26:12 +08:00
huwenjiao.hwj ecaa375be8 fix(sheet): validate merge range up front, fix startCell key, correct CHANGELOG
Three review P1s.

1. CHANGELOG no longer asserts a breaking Schema change this PR does not ship.
   sheet export / sheet create deliver main's mcp + interface_ref and
   schema-compatibility reports ok (0 changed fields), so the "declared as
   composite (breaking)" entry was false and is removed; the set-style entry
   drops the "breaking" framing (accepted as compatible by the reviewed
   mapping-exclusion carve-out); the duplicate ### Changed heading is merged.

2. firstNonEmptySheetSpecCell reads the start cell via
   pickStr(spec, "startCell", "start_cell"). Sheet specs are forwarded verbatim
   to table_put, whose wire fields are camelCase in this repo. The prior
   snake_case-only read meant a user passing the real startCell would have data
   written at the offset while the probe read A1 — a false "写入未生效" on a
   successful write. camelCase preferred, snake_case kept for tolerance.

3. planStyleOps now parses cell_merges range with parseA1Range, matching the
   cell_styles branch. planStyleOps is dry-run once before create_workspace_sheet
   as the up-front structural gate, so an invalid range like "not-a-range" is now
   rejected before any RPC instead of failing only at the final merge_cells call
   and leaving an unrollbackable partially-completed document. merge_cells' range
   contract is A1:B3-style, which parseA1Range covers (and it strips a Sheet1!
   prefix).

Tests: cell-merges-invalid-range added to TestSheetCreateValidatesBeforeCreating
Document (asserts calls == 0); TestFirstNonEmptySheetSpecCell covers both
startCell and start_cell. Changed-code coverage stays at 100%; targeted sheet
suites pass; CHANGELOG has a single Changed section with no false breaking claim.
2026-08-07 20:26:12 +08:00
huwenjiao.hwj dbecf23bc4 test(sheet): cover --sheets read-back error paths to reach 100% changed-code coverage
Adds coverage for the branches introduced by the per-sheet read-back:
resolveSheetIDsByName's RPC-error and unparseable-response paths, the create
--sheets path surfacing a list-fetch failure with the nodeId, and the
single-value data row in sheetSpecGrid. Changed-code coverage back to 100%.
2026-08-07 20:25:21 +08:00
huwenjiao.hwj d73e199d97 fix(sheet): read back each sheet after --sheets table_put to catch silent data loss
The --values branch already reads back its first non-empty cell after writing,
to defend against the new-document initialization race where a write returns
success but the data does not land. The --sheets branch called table_put and
reported success with no read-back, so the same race would let the command exit
successfully while one or more sheets silently lost their initial data.

After table_put, the --sheets branch now:
- re-fetches get_all_sheets to build a name -> sheetId map (table_put reuses the
  renamed default sheet and auto-creates the rest by name), and
- for every spec that actually has content, reads back its first expected
  non-empty cell and fails if the read-back is empty or the sheet is missing.

firstNonEmptySheetSpecCell mirrors firstNonEmptyValuesCell: it treats columns as
the header row followed by data rows, honours start_cell, and returns
hasContent=false for a name-only spec so a legitimately empty sheet is not
misreported as data loss. Failures carry the nodeId and point at
sheet table-put for recovery, matching the --values branch's error shape.

Tests:
- TestSheetCreateWithSheetsVerifiesEachSheetLanded covers an empty read-back
  (errors, naming the sheet + nodeId + table-put), a sheet missing from the
  post-write listing, and a name-only sheet that must not trigger a read-back.
- TestFirstNonEmptySheetSpecCell covers header/data origins, an empty first
  header cell, a start_cell offset, and content-less specs.
- Existing --sheets tests updated for the added get_all_sheets + per-sheet
  read-back calls.
2026-08-07 20:25:21 +08:00
huwenjiao.hwj 7c9687094f refactor(sheet): declare create and export as mcp with their primary interface_ref
Reverts the interface_mode of sheet create and sheet export from composite back
to mcp with a single interface_ref, matching upstream/main and the existing
convention for multi-tool leaves (doc.create_document declares mcp +
create_document even though it also calls update_document).

Rationale:
- interface_ref is audit / traceability metadata; nothing reads it at runtime
  (verified: rebuilding with a bogus interface_ref still routes to the correct
  tool). Declaring the primary tool and treating the orchestration as an
  implementation detail is the pattern main already uses.
- sheet export was mcp + submit_export_job on main; it already orchestrated
  submit_export_job + query_export_job without declaring the poll. Adding an
  --export-format csv branch does not change that shape, so it does not warrant
  flipping to composite.
- sheet create was a genuine single-RPC command on main (create_workspace_sheet
  only). The --values / --sheets / --styles orchestration I added runs after the
  document exists; per the doc.create_document precedent it stays mcp.

This takes the sheet schema-compatibility failures from 4 to 0 without a waiver
or an admin override: the declarations now equal main's.

Also updates the wording of the seven new mapping-exclusion reasons for these
two commands (submit_export_job CSV params, create_workspace_sheet
values/sheets/styles) from "Composite ... input" to "Wrapper ... input", so the
reason text no longer collides with the interface_mode value now that both
leaves are mcp. The reasons are otherwise unchanged and still describe where
each value actually goes. range_batch_set_style keeps its "Composite" wording
because it genuinely stays interface_mode=composite.

Removes the two composite entries for these leaves from the interface
disposition contract test.

No execution path changes; targeted sheet suites, the disposition contract test,
and the schema-compat policy tests all pass; check-schema-catalog is green;
sheet-scoped schema-compatibility reports 0 failures.
2026-08-07 20:25:21 +08:00
huwenjiao.hwj 05d8c86177 policy(schema-compat): accept reviewed property clearing and mcp ref redirects
Two compatibility carve-outs, written alongside the existing interface_type
retirement allowance. Both cover declarative provenance metadata that nothing
reads at runtime: the tool a leaf invokes is decided in the CLI source, so a
stale interface_ref or property misinforms a reader rather than misrouting a
call. Neither carve-out can mask a change to the surface callers depend on.

Cleared property through a reviewed mapping exclusion. A leaf whose backing RPC
moves to a nested payload has no honest flat property to publish. The two
alternatives are worse: keep naming a field the request no longer contains, or
let assembly fall back to flag_name_inference and publish a name that appears in
no request at all. Accepted only when the old value was non-empty, the new value
is empty, and the new value resolved through reviewed_mapping_exclusion. A
redirect to a different non-empty value, a clearing by inference or native
annotation, a clearing with no recorded source, and populating a previously
empty property all stay incompatible. The exclusion table cannot be abused to
wave arbitrary clearing through: internal/cli/schema_parameter_bindings.go
verifies every parameter claiming an exclusion really does deliver an empty
property, and every entry carries a non-empty reviewed reason.

Redirected interface_ref with an unchanged CLI contract. Accepted only when
interface_mode is unchanged and stays mcp, both refs are non-empty, and no other
compatibility failure was recorded for that tool. That last condition is the
operative definition of "the contract is unchanged" — it is measured, not
asserted, so it automatically covers a lost parameter, a newly required one, a
moved type / default / format / enum, a tightened constraint, a positional or
dry_run change, and any effect / risk / confirmation / idempotency move. Any one
of them re-reports the redirect, so a surface change cannot ride along behind a
backend move. Moving to or from composite is a change in kind rather than a
redirect and stays reported; so does removing a ref outright.

Deliberately still incompatible: mcp -> composite with the ref dropped. That is
a leaf declaring it now orchestrates several RPCs, which is a semantic upgrade
rather than a like-for-like substitution, and it belongs in review.

For this branch the two carve-outs take schema-compatibility from 17 changed
fields to 4 — the twelve sheet.range_set_style property clearings and its
update_range -> set_cell_range redirect are now accepted. The remaining four are
the interface_mode plus interface_ref pairs on sheet.create_workspace_sheet and
sheet.submit_export_job.

Tests: TestCrossPlatformCoverageSchemaCompatPropertyClearingExclusion and
TestCrossPlatformCoverageSchemaCompatInterfaceRefRedirect assert the accepted
shapes plus twelve neighbouring shapes that must stay incompatible; the drift
table gains cases for clearing without an exclusion and redirecting despite one.
2026-08-07 20:25:21 +08:00
huwenjiao.hwj 115dad3b82 fix(sheet): keep JSON integer literals intact through both data channels
P1 from automated review. --values decoded with plain json.Unmarshal, so every
number became a float64 and integers beyond 2^53 were rounded before anything
was written. The read-back only checks that the probe cell is non-empty, so the
corruption was reported as a successful write. Order numbers and snowflake IDs
are ordinary spreadsheet data.

Measured before the fix:

  1234567890123456789   -> 1234567890123456768    snowflake id, tail rewritten
  12345678901234567890  -> 12345678901234567000   20-digit order number
  9007199254740993      -> 9007199254740992       2^53+1

--sheets had the same defect, which the review did not mention: its records and
data are forwarded verbatim to table_put, and the float64 round trip rewrote
1234567890123456789 as 1234567890123456800 before the request left the CLI.

Both channels now decode with json.Decoder.UseNumber, and cellToString emits a
json.Number through its String method so no float conversion happens on the way
to CSV. --styles keeps plain Unmarshal on purpose: its numbers are font sizes and
pixel dimensions, already constrained to int32 by pickNum, with no large-integer
case.

Tests assert the payload the CLI actually sends, not a recomputed decode:
- TestSheetCreatePreservesLargeIntegerLiterals checks the csv argument of
  set_range_from_csv and the marshalled table_put arguments. Both halves also
  assert the rounded forms are absent, so removing UseNumber fails the test
  instead of passing on a lucky substring match.
- TestCellToStringKeepsJSONNumberVerbatim covers large, negative, fractional and
  exponent literals, and keeps the existing float64 behaviour for other callers.

The shared scriptedToolCaller keeps only the last call, and the write is the
fourth of five, so the assertion needs an intermediate call. Rather than extend
that shared helper, this adds a callRecorder local to this file: InitDeps takes
the edition.ToolCaller interface, so embedding *scriptedToolCaller and
overriding CallTool is enough.

Changed-code coverage stays at 100.0000% (850 statements) per
check-coverage-gate.sh --changed-only.
2026-08-07 20:25:21 +08:00
huwenjiao.hwj b8ac9810f4 fix(sheet): require unique --sheets names and cover the size error branches
Two things, both in the create-with-data path.

Unique worksheet names. parseCreateSheetSpecs accepted a --sheets payload with
repeated names, so table_put created several worksheets sharing one name. The
style tools locate a worksheet by "id or name", so --styles would then land on
whichever duplicate the server picked, and --styles runs after the document
already exists and cannot be rolled back. Duplicates are now refused before
anything is created, naming the first occurrence:

  --sheets[1].name="一月" 与 --sheets[0] 重复;工作表名必须唯一,...

Case-only differences are still accepted: the server distinguishes them and the
CLI should not tighten that. --styles needs no equivalent check because it
already requires name equality with the corresponding --sheets entry, and those
are now unique.

Coverage. The previous commit added a precise pickNum error path to the standard
and auto branches of planSizes, but no test reached it: the existing cases used
an integer size, which stops at "不能同时给 size" before the numeric check runs.
The CI coverage gate therefore reported 99.7619% on changed code
(sheet_create_with_data.go:512-514 and :521-523). Two cases now drive
type=standard and type=auto with size 28.5.

That pair is not only about the percentage. It pins the error precedence: a
fractional size must report "size=28.5 必须是整数", which points at the field
actually written wrong, rather than the generic "不能同时给 size". Swapping the
two checks would make the message misleading and now fails the tests.

Changed-code coverage measured locally at 100.0000% (845/845 statements), with
no uncovered blocks in the diff against upstream/main.

Tests:
- TestParseCreateSheetSpecsRejectsDuplicateNames covers adjacent, non-adjacent
  and {"sheets":[...]}-wrapped duplicates, plus three payloads that must pass.
- Three new cases in TestSheetCreateValidatesBeforeCreatingDocument
  (sheets-duplicate-name and the two fractional sizes), each asserting calls == 0.
2026-08-07 20:25:21 +08:00
huwenjiao.hwj c1a90c0194 fix(sheet): reject fractional and out-of-range --styles sizes instead of truncating
P2 from automated review. pickNum ran int(n) straight on the float64 that JSON
decoding always produces, so font_size: 12.9 and row_sizes.size: 28.5 were
silently rewritten to 12 and 28 and then executed as a valid configuration. Both
the help text and the error messages state these fields must be positive
integers, and --styles is a non-atomic sequence that cannot be rolled back, so
truncation left a sheet that did not match what the caller asked for.

Measured before the fix:

  font_size=12.9  -> emitted fontSize=12
  size=28.5       -> emitted pixelSize=28
  size=1e20       -> emitted pixelSize=9223372036854775807

The overflow case was worse than reported: int(1e20) saturates to MaxInt64 and
was still sent.

pickNum now returns an error and rejects a non-integral value, a NaN or Inf, a
magnitude outside int32, and a non-numeric type. A missing key and an explicit
null still report "not provided" without an error, so optional fields keep
working. Every call site propagates the error, which means the whole --styles
payload is refused before the document is created. Confirmed through the real
CLI: font_size=12.9, col_sizes size=120.5 and size=1e20 all fail with a specific
message while font_size=12 still passes.

Tests:
- TestPickNumRejectsNonIntegralAndOutOfRange covers eight rejected inputs plus
  five accepted ones, the missing key, an explicit null, and alias-key lookup.
- Four new cases in TestSheetCreateValidatesBeforeCreatingDocument for
  cell_styles font_size, row_sizes size, col_sizes size and the overflow, each
  asserting calls == 0.
- TestPickStrAndPickNum updated: a bool value now surfaces a type error with
  ok=true rather than being reported as absent.
2026-08-07 20:25:21 +08:00
huwenjiao.hwj 0bb2b6ce98 feat(sheet): CSV export, style extensions and create-with-data
Adds four capabilities and, for the style surface, moves to the interface that
can actually express them.

Added:
- sheet create --values / --sheets / --styles: create a workbook and populate it
  in one command. --values takes a 2D array into the default sheet, --sheets
  takes typed tables across several sheets, --styles carries cell_styles /
  row_sizes / col_sizes / cell_merges. Every structure and enum is validated
  before the document is created, so an invalid config never leaves an orphan
  empty document behind.
- sheet export --export-format csv: synchronous single-sheet RFC4180 export with
  --sheet-id, --range and --value-render-option. --output writes to a file (a
  directory gets sheet-export.csv), otherwise the CSV goes to stdout while the
  truncation warning goes to stderr, keeping stdout pipeable.
- sheet update-dimension --size-type: pixel / standard (restore the default row
  height or column width) / auto (fit row height to content, ROWS only).
- sheet replace --match-formula: search and replace inside formula text.
- sheet range set-style --font-style / --font-line / --font-family /
  --border-styles-json.
- sheet range batch-set-style --ranges: stamp one style across several
  sheet-qualified ranges.

Changed (breaking Schema change, no CLI break):
- sheet range set-style moves from update_range to set_cell_range. The
  update_range style channel exposes exactly eight properties
  (backgroundColors, fontSizes, horizontalAlignments, verticalAlignments,
  fontColors, fontWeights, wordWrap, numberFormat) and has no slot for italic,
  underline/line-through, font family or borders, so the four new dimensions are
  not expressible there. interface_ref becomes set_cell_range and the twelve
  style flags stop publishing a flat property, because the value now lands in
  cells[i][j].cellStyles.* with no single top-level field to name.
- sheet range batch-set-style submits one atomic batch_update instead of looping
  update_range, so a partial failure no longer leaves half the ranges stamped.
  --continue-on-error becomes a server passthrough. Caps the fan-out at 100
  ranges and 200000 cells in aggregate.
- sheet export and sheet create declare interface_mode=composite: both route
  across several tools depending on the flags, so a single mcp ref was wrong.

Schema hygiene:
- Nineteen parameters that previously resolved through flag_name_inference into
  property names present in no request (bgColor, exportFormat, values, ...) are
  now reviewed mapping exclusions with a stated reason, so Schema omits the
  property instead of inventing one.

schema-compatibility reports 17 changed fields: 13 on sheet.range_set_style
(interface_ref plus twelve property mappings) and 2 each on
sheet.submit_export_job and sheet.create_workspace_sheet (interface_mode plus
interface_ref). No CLI flag is removed and no command path changes; the same
invocation runs on both the old and the new binary. Landing this needs a
decision on the Schema contract break.

Tests: targeted sheet suites in internal/helpers and the interface disposition
contract tests in internal/app pass; make build and gofmt clean;
check-schema-catalog, check-generated-drift, check-command-surface,
check-skill-commands and check-runtime-confirmation-truth all pass.
2026-08-07 20:25:21 +08:00
github-actions[bot] 2662f87ad0 Merge pull request #908 from liyuan333/feat/html-import-hints
feat(doc): fall back to upload chain for non-importable import formats
2026-08-07 19:58:57 +08:00
liyuan 5a5b567eb0 Merge remote-tracking branch 'upstream/main' into feat/html-import-hints
# Conflicts:
#	CHANGELOG.md
2026-08-07 19:27:49 +08:00
liyuan c2db909bd2 Merge branch 'feat/html-import-hints' of github.com:liyuan333/dingtalk-workspace-cli into feat/html-import-hints 2026-08-07 19:17:33 +08:00
liyuan 871542ef0c 评审意见修改 2026-08-07 19:17:25 +08:00
wxianfeng 1ee37ec4c2 fix(event): harden subscription reuse and skill migration 2026-08-07 18:46:25 +08:00
github-actions[bot] 2c7d0f3ac4 Merge pull request #907 from dxb121/codex/multi-im-shortcuts-hardening
feat(chat): harden multi-IM shortcuts and pagination
2026-08-07 18:43:01 +08:00
栩朝 5a345228eb fix(chat): address pagination and audit review feedback 2026-08-07 18:26:39 +08:00
阮知夏 06b0a9eef3 docs(minutes): drop hot-word delete intent routing 2026-08-07 17:56:32 +08:00
xiatian ea7c66b190 docs(sheet): align mono and multi skill references
Replace the oversized mono Sheet reference with the progressive routing layout, mirror all Sheet topic references across both bundles, and add a paired-tree drift guard.
2026-08-07 17:43:21 +08:00
克谨 83f72377a7 fix(chat): satisfy IM contract and compatibility gates 2026-08-07 17:41:25 +08:00
栩朝 b2b6153424 fix(chat): preserve flag-list size schema default 2026-08-07 17:19:36 +08:00
栩朝 59efb4facb feat(chat): harden multi-IM shortcuts and pagination 2026-08-07 17:19:36 +08:00
liyuan333 3605d4f450 Merge branch 'main' into feat/html-import-hints 2026-08-07 17:17:16 +08:00
liyuan f34b7741d4 Merge branch 'feat/html-import-hints' of github.com:liyuan333/dingtalk-workspace-cli into feat/html-import-hints 2026-08-07 17:09:21 +08:00
liyuan 2c573ec892 评审意见修改 2026-08-07 17:07:28 +08:00
克谨 50712d3305 Merge remote-tracking branch 'origin/main' into codex/fix-im-search-conversation-scope 2026-08-07 17:02:16 +08:00
wxianfeng 7e27fa384a Merge remote-tracking branch 'upstream/main' into feat/dws-event-oa 2026-08-07 16:47:17 +08:00
github-actions[bot] 3027337a34 Merge pull request #901 from DingTalk-Real-AI/codex/ai-table-shortcut
feat(aitable): expose and verify complete AI Table shortcut surface
2026-08-07 16:41:11 +08:00
wxianfeng 8bf6c15fad feat(event): restore standalone event skill 2026-08-07 16:38:30 +08:00
liyuan333 d0ad33034e Merge branch 'main' into feat/html-import-hints 2026-08-07 16:36:33 +08:00
阮知夏 f79a6fc707 fix(minutes): type permission apply --policy as int 2026-08-07 16:25:38 +08:00
liyuan 5f13876e6e fix(doc): address import fallback review — shared prechecks, clean upload primitive, marked JSON envelope 2026-08-07 16:25:19 +08:00
克谨 5a09204bf5 fix(chat): complete resource reference downloads 2026-08-07 16:23:20 +08:00
克谨 0d11b2be45 fix(chat): preserve resource filenames in message refs 2026-08-07 16:07:56 +08:00
Dennis4477 4bb8c8b586 Merge branch 'main' into codex/ai-table-shortcut 2026-08-07 15:55:46 +08:00
Dennis 48e522ec00 fix(aitable): harden pagination and upload inputs 2026-08-07 15:50:35 +08:00
克谨 effe7c829e fix(chat): align message workflows and diagnostics 2026-08-07 15:48:29 +08:00
github-actions[bot] 0e0007d7b7 Merge pull request #903 from DingTalk-Real-AI/codex/update-reviewer-pool
chore: 更新 Reviewer Router 评审人池
2026-08-07 15:36:41 +08:00
Dennis 176b217139 fix(aitable): require bootstrap confirmation 2026-08-07 15:32:01 +08:00
克谨 7c76dfea4b fix(chat): fail closed for scoped search and card updates 2026-08-07 15:30:09 +08:00
炳昱 c803cf7eeb fix(event): validate reused OA subscriptions in dry-run 2026-08-07 15:30:03 +08:00
chichuan 5c8fd0a48c fix: preserve reviewer routing fallback 2026-08-07 15:20:41 +08:00
Dennis 7490bb95c5 fix(aitable): scope strict write response checks 2026-08-07 15:09:58 +08:00
炳昱 832d3ab886 test(event): cover OA validation branches 2026-08-07 14:58:52 +08:00
wxianfeng 47f303d3fc Merge remote-tracking branch 'origin/feat/dws-event-oa' into feat/dws-event-oa 2026-08-07 14:56:28 +08:00
wxianfeng 1199240a36 Merge remote-tracking branch 'upstream/main' into feat/dws-event-oa
# Conflicts:
#	skills/mono/references/products/event.md
#	skills/multi/dingtalk-misc/references/event-oa.md
#	skills/multi/dingtalk-misc/references/event.md
2026-08-07 14:48:37 +08:00
chichuan b186e59a01 feat: route reviewers by module ownership 2026-08-07 14:42:56 +08:00
Dennis a92f54df3d fix(paging): restore zero-value safety limit 2026-08-07 14:42:27 +08:00
炳昱 354d39a6f1 Merge branch 'main' of https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli into feat/dws-event-oa
# Conflicts:
#	skills/mono/references/products/event.md
#	skills/multi/dingtalk-misc/references/event-oa.md
#	skills/multi/dingtalk-misc/references/event.md
2026-08-07 14:23:12 +08:00
Dennis 307c9e797b fix(aitable): reject empty bulk patch selectors 2026-08-07 14:08:59 +08:00
liyuan 116117e987 feat(doc): fall back to upload chain for non-importable import formats 2026-08-07 14:07:35 +08:00
Dennis 07ce090eeb test(aitable): close shortcut coverage gaps 2026-08-07 12:01:36 +08:00
Dennis 201949aec1 feat(aitable): add verified shortcut workflows 2026-08-07 12:01:33 +08:00
chichuan 8eeabd1419 chore: update reviewer pool 2026-08-07 10:45:59 +08:00
github-actions[bot] 6035d43899 Merge pull request #877 from xlb1130/feat/chat-toolbar-commands
feat(chat): add toolbar commands for conversation shortcut bar
2026-08-07 10:45:32 +08:00
chichuan 1f127881c9 Merge branch 'main' into codex/release-fragments 2026-08-07 10:24:51 +08:00
炳昱 6704eda83a fix(event): switch personal event defaults to production 2026-08-07 10:22:32 +08:00
xlb1130 ba375b40fa Merge branch 'main' into feat/chat-toolbar-commands 2026-08-06 22:25:56 +08:00
长真 0a063662a0 test(chat): use testseam for toolbar deps 2026-08-06 22:24:27 +08:00
github-actions[bot] 4148a90bf5 Merge pull request #889 from DingTalk-Real-AI/codex/ci-pr-release-compatibility
ci: run release compatibility in PR admission
2026-08-06 13:53:32 +00:00
chichuan 262248d08d Merge branch 'main' into codex/ci-pr-release-compatibility 2026-08-06 21:38:02 +08:00
github-actions[bot] e7955b5891 Merge pull request #864 from DingTalk-Real-AI/fix/param-hallucination
fix(cli): harden command and parameter hallucination recovery
2026-08-06 21:30:44 +08:00
chichuan efb172dcd6 Merge branch 'main' into codex/ci-pr-release-compatibility 2026-08-06 21:18:39 +08:00
长真 6572010922 fix(chat): preserve chmod yes shorthand 2026-08-06 20:27:18 +08:00
克谨 0cbb1b8d30 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-06 20:16:11 +08:00
xlb1130 6738d0f29e Merge branch 'main' into feat/chat-toolbar-commands 2026-08-06 20:11:24 +08:00
github-actions[bot] db6addfc0e Merge pull request #873 from maoqxxmm/codex/csv-put-formula-pr
feat(sheet): expose csv-put formula semantics
2026-08-06 12:05:46 +00:00
xlb1130 384b067ead Merge branch 'main' into feat/chat-toolbar-commands 2026-08-06 20:00:42 +08:00
长真 c32d10bd43 fix(chat): gate chmod confirmation 2026-08-06 19:51:41 +08:00
chichuan 17f153a070 Merge branch 'main' into codex/csv-put-formula-pr 2026-08-06 19:50:54 +08:00
克谨 959bc4c1a8 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-06 19:46:24 +08:00
github-actions[bot] 9f80006b3b chore: update formula for v1.0.57 [skip ci] 2026-08-06 11:25:13 +00:00
克谨 e530aea14d Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-06 19:23:34 +08:00
chichuan 32515729af Merge pull request #898 from DingTalk-Real-AI/codex/recover-v1.0.57-formula-parent
chore: prepare safe v1.0.57 Formula recovery
2026-08-06 19:08:28 +08:00
xlb1130 2a7ab22e85 Merge branch 'main' into feat/chat-toolbar-commands 2026-08-06 18:52:26 +08:00
长真 757f45df31 fix(chat): align toolbar mcp contracts 2026-08-06 18:20:18 +08:00
chichuan ef71673c69 chore: prepare v1.0.57 Formula recovery 2026-08-06 18:13:16 +08:00
前津 a3773c4384 Merge remote-tracking branch 'upstream/main' into feat/chat-message-help-id-chain 2026-08-06 18:11:41 +08:00
前津 4110330575 fix(skills): keep chat route within context budget 2026-08-06 18:00:32 +08:00
克谨 af0086c9a8 test: cover command fallback platform gates 2026-08-06 17:53:57 +08:00
克谨 28f75dec0a Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-06 17:42:40 +08:00
github-actions[bot] 15d5be6000 chore: update formula for v1.0.57 [skip ci] 2026-08-06 09:38:41 +00:00
github-actions[bot] ebfba82ebc Merge pull request #867 from Anonymity-0/feat/robot-message-image-file
feat(chat): support robot image and file messages
2026-08-06 17:35:32 +08:00
克谨 ec9897678f Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-06 17:20:38 +08:00
克谨 af6e566abd fix(cli): preserve native doc export task alias 2026-08-06 17:19:21 +08:00
前津 4ea298ec61 Merge remote-tracking branch 'upstream/main' into feat/robot-message-image-file 2026-08-06 17:12:30 +08:00
前津 7aba24b690 fix(chat): preserve schema compatibility 2026-08-06 17:11:19 +08:00
克谨 60e278f32f Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-06 17:11:17 +08:00
克谨 7acbec2615 fix(cli): extend doc hallucination recovery 2026-08-06 17:11:09 +08:00
chichuan 6811a12330 Merge pull request #896 from DingTalk-Real-AI/codex/accept-successful-release-rerun
fix(release): accept successful sealed run reruns
2026-08-06 17:08:02 +08:00
前津 c54a9e1e5e Merge remote-tracking branch 'upstream/main' into feat/robot-message-image-file 2026-08-06 16:56:58 +08:00
前津 a15fb22671 Merge remote-tracking branch 'origin/feat/robot-message-image-file' into feat/robot-message-image-file 2026-08-06 16:48:39 +08:00
chichuan a1712337a8 fix(release): accept successful sealed run reruns 2026-08-06 16:48:29 +08:00
前津 d65ad9aa2e fix(chat): preserve robot markdown requirements 2026-08-06 16:48:27 +08:00
长真 5d25a10223 fix(chat): cover toolbar command mappings 2026-08-06 16:46:39 +08:00
前津 2bd6b297b0 docs(chat): document post-send ID chain 2026-08-06 16:43:56 +08:00
前津 7688f95d2b Merge remote-tracking branch 'upstream/main' into feat/robot-message-image-file 2026-08-06 16:37:08 +08:00
github-actions[bot] c2ee691db7 chore: update beta formula for v1.0.57-beta.4 [skip ci] 2026-08-06 08:36:13 +00:00
xiatian 368c879f45 Merge remote-tracking branch 'upstream/main' into codex/csv-put-formula-pr
# Conflicts:
#	CHANGELOG.md
2026-08-06 16:24:03 +08:00
john 1920552ab0 Merge pull request #895 from DingTalk-Real-AI/codex/changelog-v1.0.57-beta.4
docs: seal v1.0.57-beta.4 changelog
2026-08-06 16:21:44 +08:00
chichuan f1b5330a4e docs: seal v1.0.57-beta.4 changelog 2026-08-06 16:18:22 +08:00
长真 0d5c6d92e3 feat(chat): add toolbar command contracts to #85129657 2026-08-06 16:07:50 +08:00
github-actions[bot] f9ccff963f Merge pull request #887 from DingTalk-Real-AI/feat/multi-skill-framework-align
feat(skills+schema): multi-skill fold, ding ParamDecl, retire discovery cache
2026-08-06 16:06:54 +08:00
玉澜andCursor 5cbc11d58f Merge branch 'main' into feat/multi-skill-framework-align
Resolve CHANGELOG conflict: keep Unreleased recovery deprecation and
main's v1.0.57 stable release section.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 15:48:39 +08:00
xiatian 49a17b4375 Merge remote-tracking branch 'upstream/main' into codex/csv-put-formula-pr
# Conflicts:
#	CHANGELOG.md
2026-08-06 15:46:53 +08:00
玉澜andCursor a0a995cfee ci: retrigger full CI after missed pull_request run
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 15:45:28 +08:00
玉澜andCursor 354c4546d8 docs(mail): align share-to-chat help with --yes hard gate
Update Long and --yes flag text to match the upfront confirmation gate
and automatic sign retry after --yes, consistent with thread trash.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 15:39:25 +08:00
玉澜andCursor 3ab22071fb fix(mail): hard-gate share-to-chat with --yes before MCP
Add explicit confirmation_required gate before any share_message_to_chat
call so piped stdin or direct server success cannot bypass --yes. Keep
sign retry after confirmation and add regression tests for zero-call deny,
sign retry, and direct-success paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 15:37:09 +08:00
github-actions[bot] a34f46794e Merge pull request #893 from DingTalk-Real-AI/codex/changelog-v1.0.57
docs: seal v1.0.57 changelog
2026-08-06 15:35:16 +08:00
chichuan f0b0bdbe48 docs: seal v1.0.57 changelog 2026-08-06 15:33:29 +08:00
github-actions[bot] 1fe019994d chore: update beta formula for v1.0.57-beta.3 [skip ci] 2026-08-06 07:27:42 +00:00
玉澜andCursor 85cb41423b revert(schema-compat): drop residual property-remap assertion from ding backfill
Keep schema-compat tests aligned with main after removing the ding
property-correction allowlist; the hard-fail case is already covered elsewhere.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 15:26:45 +08:00
xiatian b9e7ff8c55 Merge remote-tracking branch 'upstream/main' into codex/csv-put-formula-pr
# Conflicts:
#	CHANGELOG.md
2026-08-06 15:12:29 +08:00
玉澜andCursor 02ccd9d134 Merge branch 'main' into feat/multi-skill-framework-align
Resolve PR #887 base drift so CI merge-revision check matches event.base.sha.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 15:10:10 +08:00
chichuan c280b19568 Merge pull request #892 from DingTalk-Real-AI/codex/changelog-v1.0.57-beta.3
docs: seal v1.0.57-beta.3 changelog
2026-08-06 15:06:55 +08:00
chichuan bb5065410e docs: seal v1.0.57-beta.3 changelog 2026-08-06 14:59:39 +08:00
玉澜 0353215b1d Revert "declare(ding): semantic ParamDecl backfill for schema inference residuals"
This reverts commit 969292a8d7.
2026-08-06 14:52:34 +08:00
克谨 b94e21331d chore(param): refresh aliases after doc shortcuts 2026-08-06 14:46:11 +08:00
xiatian 637a6f2f68 Merge remote-tracking branch 'upstream/main' into codex/csv-put-formula-pr 2026-08-06 14:45:05 +08:00
chichuan ae9ba333a0 Merge branch 'main' into codex/ci-pr-release-compatibility 2026-08-06 14:44:32 +08:00
克谨 6dbc8399df Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-06 14:42:33 +08:00
github-actions[bot] 3c7ed03bd6 Merge pull request #880 from DingTalk-Real-AI/codex/doc-shortcut-final
feat(doc): add reviewed document shortcuts
2026-08-06 14:36:21 +08:00
xiatian 24cdb85d71 docs(changelog): record formula verify fix 2026-08-06 14:22:42 +08:00
玉澜andCursor a1f8ecb7f0 test(coverage): cover empty allowlist path in filterBlocksByFiles
Hit the nil-allowlist early return so the platform changed-statement gate
reaches 100% after shared-file overall baseline filtering.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 13:33:36 +08:00
玉澜andCursor a34ca5a137 test(ci): allow 0.1pp overall coverage tolerance in workflow contract
Align the Code Admission workflow contract with the shared-file overall
non-regression tolerance needed after deleting fully covered packages.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 13:18:48 +08:00
玉澜andCursor 7887b9473f chore(cli): extend interface baseline for visible cache shim
Merge of the Deprecated cache refresh surface reintroduced a public root
command; refresh the CLI interface baseline so cli-smoke stays green.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 13:12:10 +08:00
玉澜andCursor fd3c82aa91 fix(coverage): compare overall on shared files and cover compact edges
Deleting 100%-covered packages such as recovery falsely regressed overall
percent against merge-base. Baseline overall now uses only files still
present in the candidate profile, with a 0.1pp CI tolerance. Also exercise
stripSchemaValueCompact nested map/slice branches.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 13:06:52 +08:00
玉澜andCursor 9266632694 Merge branch 'fix/skill-doc-quickwins' (#869)
Absorb skill-doc quickwins intent while keeping the multi-skill fold:
standalone hrbrain/markdown/pat/profile/skill packages stay in misc,
and markdown routing remains misc-targeted.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 13:04:58 +08:00
玉澜 b0cbcd7a04 Merge branch 'fix/cli-missing-surfaces-from-eval' (#868)
Bring missing CLI surfaces from wukong cli_to_mcp eval into multi-skill align.
2026-08-06 13:02:48 +08:00
玉澜andCursor 6251117bd0 fix(cli): keep visible Deprecated cache refresh shim
Restore dws cache {refresh,status,clean} as a successful no-op
compat surface so historical scripts/agents keep working after
static-endpoint delivery. Deprecated leaves stay out of Schema via
IsAvailableCommand without schema exclusions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 13:02:39 +08:00
玉澜andCursor 566803c431 fix(skills): drop invalid dws command prose in misc refs
Rewrite incomplete `dws devapp +` and non-product `dws finance` mentions
so skill-command-integrity no longer treats them as executable paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 12:40:17 +08:00
玉澜andCursor d6848da60b chore(cli): extend interface baseline for recovery root
Visible Deprecated recovery stubs are part of the public root command
tree; refresh the CLI interface baseline so cli-smoke stays green.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 12:33:47 +08:00
Dennis 5f5d7ee21e fix(localio): harden local download publication 2026-08-06 12:26:50 +08:00
玉澜andCursor 53169a41af fix(policy): split schema projection --jq=/--fields= cases
Cover each equals-form flag in its own switch case so the platform
changed-statement coverage gate reliably hits both branches.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 12:26:47 +08:00
玉澜andCursor e90d5bac68 test: close platform coverage gaps for recovery and schema edges
Hit Encode/Fprint failure paths, compact projection branches, authActions,
reviewed property corrections, and schemaProjectionIssue --jq=/--fields=
so the changed-statement coverage gate reaches 100%.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 12:22:04 +08:00
玉澜andCursor d725bdbaa3 fix(schema): drop stale recovery exclusions
Deprecated recovery leaves are not public schema leaves
(IsAvailableCommand=false), so listing them as exclusions fails
completeness with stale exclusions.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 12:00:33 +08:00
chichuan d75b744a35 ci: classify core command changes as interface-sensitive 2026-08-06 11:58:49 +08:00
玉澜 9aea8c0b5c Merge chore/retire-mcp-schema-candidates (#882).
Bring MCP pin candidate retirement into the multi-skill align PR.
2026-08-06 11:54:12 +08:00
xiatian 7134f33e1d fix(sheet): route formula verify to registered tool 2026-08-06 11:53:06 +08:00
玉澜andCursor f54b964d62 fix(cli): keep visible Deprecated recovery stub
Drop Hidden so authoritative interface integrity still passes, restore
the CI historical command gate, and keep the unsupported notice without
Skill guidance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 11:48:31 +08:00
长真 88f7ea5679 Merge remote-tracking branch 'upstream/main' into feat/chat-toolbar-commands 2026-08-06 11:43:56 +08:00
玉澜andCursor 11fbeb4851 fix(cli): hide dws recovery with unsupported notice
Keep a Hidden compatibility shim that returns 不再支持 for plan/
execute/finalize, so this release stops supporting the surface while a
later release can delete the shim entirely.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 11:41:56 +08:00
玉澜andCursor 2b1f38edae ci: drop historical interface command gate
Allow intentional removal of public commands (e.g. dws recovery)
without compatibility stubs. Keep Schema and skill-command checks
in the Interface Integrity job.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 11:36:29 +08:00
Dennis 2f3797c1f6 fix(localio): enforce secure download client 2026-08-06 11:31:01 +08:00
xlb1130 c0b562cc07 fix(chat): add package-level MCP call seam for toolbar remove-custom
- introduce removeChatToolbarCustomShortcutFn in toolbar_remove_custom.go
  as a package-level injection seam; default impl routes through
  callMCPToolOnServer against the im server so production behavior is
  unchanged
- update RunE to dispatch via the seam instead of calling
  callMCPToolOnServer inline
- add two TestCrossPlatformCoverage* tests that swap the seam via
  testseam.Swap and verify: (1) without --yes the seam is never called
  and a typed confirmation_required error is returned, (2) with --yes
  the seam is called exactly once with openCid and shortcutId. The
  stub forwards to deps.Caller.CallTool so the user_required contract
  gate (leaf.go) still sees the CallTool channel.
2026-08-06 11:20:52 +08:00
chichuan 37fbb110e3 ci: run release compatibility in PR admission 2026-08-06 11:16:19 +08:00
xiatian 7564496ea0 fix(sheet): clarify csv-put literal text semantics 2026-08-06 11:09:02 +08:00
玉澜andCursor a0b0d98180 drop recipes/shared intermediate dir under multi skills
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 11:01:07 +08:00
Dennis 990c85d36b fix(localio): strip headers on cross-origin redirects 2026-08-06 11:00:44 +08:00
玉澜andCursor b742343937 restore per-product conventions dirs; rename _common to recipes/shared only
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 10:56:10 +08:00
玉澜andCursor 657df05d40 Reshape multi shared recipe dirs for agent-friendly paths, content unchanged.
Move dingtalk-shared best_practices/_common into references/recipes/,
drop duplicated product _common/conventions copies, and retarget links.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 10:53:28 +08:00
chichuan c52f2b6e05 release: use isolated changelog fragments 2026-08-06 10:49:46 +08:00
玉澜andCursor 86f2b14449 docs: drop multi skill experimental banners
Remove EXPERIMENTAL/Preview banners from skill setup, install scripts, README, and misc references so multi mode is no longer framed as unstable preview.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 10:46:42 +08:00
玉澜andCursor 86014c97cf fix(makefile): keep skill content/command checks out of policy
Do not expand the default policy gate with mono-multi or skill-commands;
leave them as optional make targets only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 10:35:20 +08:00
长真 70d58648c8 fix(chat): address CR P1 for toolbar remove-custom
- Drop --yes and shell-comment example lines from the Cobra Example
  field in chat toolbar remove-custom; keep only the single
  non-bypassing command line. Aligns with AGENTS.md "no --yes in
  stored examples" and "No shell comments in examples" rules.
- Mirror the change in skills/mono/references/products/chat.md
  toolbar remove-custom block: remove the duplicated --yes and
  shell-comment lines; keep Flags block and prose note untouched.
- Add two end-to-end confirmation gate tests under
  internal/helpers/toolbar_helpers_test.go using the existing
  toolbarTestCaller seam (extended with a calls []toolbarCall
  slice so the new tests can assert call counts as well as the
  most recent call):
  * TestCrossPlatformCoverageToolbarRemoveCustomRejectsWithoutYes
    asserts confirmation_required and zero MCP calls when --yes
    is omitted.
  * TestCrossPlatformCoverageToolbarRemoveCustomCallsMCPWithExactArgsWhenYes
    asserts exactly one im/remove_chat_toolbar_custom_shortcut
    call with openCid=<cid> and shortcutId=<id> when --yes is
    set.
- No changes to Contract.Selection.Examples (already compliant),
  Long prose, or any other toolbar file. Helper field addition is
  additive: legacy single-call fields stay so all prior tests
  remain green.

Fixes: PR #877 CR P1 (remove-custom confirmation gate).
Risk tier: Standard.
Verification: see PR description.
2026-08-06 10:35:04 +08:00
玉澜andCursor 94f3bba504 Merge declare/semantic-param-backfill into multi-skill align.
Bring ding ParamDecl backfill, schema agent-view bounds, and discovery
cache CLI/doctor retirement onto the multi-skill framework branch.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 10:31:40 +08:00
玉澜andCursor 339eaa4b1b Drop discovery-cache skill guidance and doctor cache check.
Prefer schema --compact in agent docs, remove服务发现/cache teaching,
and stop doctor from reporting a no-op cache health item.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 10:28:20 +08:00
玉澜andCursor 96774e6e23 Remove retired discovery cache CLI and doctor cache check.
Drop the no-op dws cache stubs and the doctor cache health item, and
scrub skill/AGENTS guidance that still pointed agents at them.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 10:28:05 +08:00
Dennis 1f77ba31f3 fix(localio): disable proxies for secure downloads 2026-08-06 10:26:04 +08:00
dxy704330469 eb0bd69b82 feat(doc): add reviewed document shortcuts
- add 45 public document shortcuts and 2 reviewed expert-only paths
- preserve six historical command and Schema identities alongside canonical leaves
- add safe local download primitives and document access/share orchestration
- keep comment create/reply confirmation backward-compatible
- ensure grant-and-share upgrades insufficient roles before messaging
- return non-zero partial/failure message ledgers and structured partial-write recovery metadata
- enumerate every selection candidate and use rune-safe Unicode keyword contexts
- assert zero-call confirmation boundaries for destructive shortcuts

Validation:
- full Go test suite and repository policy
- real DingTalk E2E for 34 canonical shortcuts, all 8 compatibility-affected entries, READER-to-EDITOR grant-and-share upgrade, and same-block selection ambiguity with zero comment writes
- command compatibility across 1,221 historical nodes and complete Schema compatibility
- 1,152 Agent examples including 62 real Cobra dry-runs
- 100% changed-code coverage across 1,260 executable statements
2026-08-06 09:56:58 +08:00
Anonymity-0 665b79d8da Merge branch 'main' into feat/robot-message-image-file 2026-08-06 09:54:07 +08:00
克谨 32e7a80889 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-06 09:52:52 +08:00
xiatian 5e6b588b5e Merge upstream/main into codex/csv-put-formula-pr 2026-08-06 09:52:28 +08:00
玉澜 c1d90672a8 test(schema): drop compact leaf size ceiling 2026-08-06 00:56:13 +08:00
玉澜 3d2d287723 feat(schema): bound agent views and prevent instruction drift 2026-08-06 00:49:31 +08:00
玉澜andCursor 969292a8d7 declare(ding): semantic ParamDecl backfill for schema inference residuals
Complete ding leaf Property/Required from Execute CallMCP keys and live
help semantics, and allowlist the four inference→declare property remaps
so schema-compat does not freeze wrong camelCase flag names.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 23:19:19 +08:00
github-actions[bot] 4bcf71fb9e Merge pull request #881 from Anonymity-0/feat/chat-reply-mentions
feat(chat): support mentions in message replies
2026-08-05 23:10:42 +08:00
玉澜andCursor ac610f2d24 fix(skills): remove stale conference product routing
No conference skill exists; stop linking conference.md / routing to
conference, and teach CLI-unsupported + DingTalk client instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 23:07:32 +08:00
玉澜andCursor 98f45cfe23 Retire dead MCP pin candidates from Schema parameter assembly.
Production already ships an empty pin; remove the leftover mcp_metadata
candidate path so parameter resolution only uses declare/Cobra sources.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 22:45:33 +08:00
wxianfeng b581426488 Merge remote-tracking branch 'upstream/main' into feat/dws-event-oa
# Conflicts:
#	internal/app/event_personal_command.go
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog.json
#	internal/cli/schema_hints/index.json
#	internal/cli/schema_hints/reference-review.json
#	skills/mono/SKILL.md
#	skills/mono/references/products/event.md
#	skills/multi/dingtalk-event/SKILL.md
2026-08-05 22:43:36 +08:00
玉澜andCursor fbf97ce402 feat(skills): fold long-tail skills into misc; rename dws-shared
Host hrbrain, markdown, pat, and profile under dingtalk-misc, retire
their standalone packages, and rename dws-shared to dingtalk-shared
across live paths, coverage, installers, and policy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 22:30:08 +08:00
玉澜andCursor f5b029b1a5 feat(skills): wire misc routing and coverage for folded event
Point coverage, installers, IM skill-chain, and shared routing at
dingtalk-misc references after retiring the standalone event package.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 22:18:21 +08:00
玉澜andCursor 75f92cf546 feat(skills): fold dingtalk-event into dingtalk-misc
Host personal IM event docs under misc like other long-tail products, and
retire the standalone multi skill package.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 22:18:18 +08:00
玉澜andCursor 6d485f47eb feat(skills): wire misc routing and coverage for folded dev/skill
Point coverage, shortcut generation, installers, and shared routing at
dingtalk-misc references after retiring the standalone packages.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 22:02:11 +08:00
玉澜andCursor 6651a162c5 feat(skills): fold dingtalk-dev and dingtalk-skill into dingtalk-misc
Host open-platform app docs and skill-market commands under misc like
other long-tail products, and retire the standalone multi skill packages.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 22:01:57 +08:00
玉澜 a9aa39c3e2 Revert "fix(skills): correct deprecated and nonexistent command teaching"
This reverts commit b0cd419f28.
2026-08-05 21:37:37 +08:00
前津 545ee17316 fix(chat): add missing reply mention placeholders 2026-08-05 21:23:12 +08:00
前津 0c62938f74 feat(chat): support mentions in message replies 2026-08-05 21:23:12 +08:00
玉澜andCursor b0cd419f28 fix(skills): correct deprecated and nonexistent command teaching
Align mono/multi minutes/doc/conference skill facts with live CLI: prefer
--limit/--cursor, drop false participants claims, replace deprecated doc
search, and mark conference as unsupported without dead conference.md links.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 21:13:39 +08:00
玉澜andCursor aa487002b7 fix(i18n/docs): drop recovery locale strings and close audit nits.
Sync transport post-recovery hints into en/zh locales, refresh skill QA docs for Phase 1–3/4B, and remove the dead internal/recovery CI high-risk path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 20:26:41 +08:00
玉澜andCursor 941bf01e30 Remove unused dws recovery CLI and continue multi-skill content framework.
Drop the recovery package/commands and related Schema/skill teaching so agents
stop being steered at a dead surface, while keeping mono↔multi content QA work.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 20:23:37 +08:00
玉澜andCursor b439c5fa09 docs(skill): add mono↔multi content QA track to align plan
Specify coverage/structure/drift gates against mono, inventory existing
skill policy tests, and extend the §7 checklist for the QA track.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 20:23:01 +08:00
玉澜andCursor 67250a9da5 docs(skill): limit align plan to content framework only
Defer install/upgrade behavior and cherry-picks to a follow-up branch;
keep this branch on multi/mono content layout and content contracts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 20:22:57 +08:00
玉澜andCursor 70243acb95 docs(skill): narrow wukong align plan to skill framework
Limit scope to skill trees and skill install/setup/upgrade framework;
defer non-skill CLI, client pipelines, and full installer rewrites.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 20:22:53 +08:00
玉澜andCursor 6cce7fdbd8 docs(skill): add multi-skill vs wukong align plan
Capture inventory, port/adapt/reject decisions, and phased work before any
framework implementation on a main-based branch.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 20:22:48 +08:00
克谨 fae21ec9f2 fix(chat): extend parameter and shortcut fallbacks 2026-08-05 20:17:49 +08:00
克谨 779fd82a88 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-05 20:14:10 +08:00
前津 3e60b83881 Merge remote-tracking branch 'upstream/main' into feat/robot-message-image-file 2026-08-05 20:04:29 +08:00
Anonymity-0 66a676b6aa Merge branch 'main' into feat/robot-message-image-file 2026-08-05 20:02:09 +08:00
前津 23bbbccb7e fix: use DX markdown type for robot direct messages 2026-08-05 19:58:17 +08:00
长真 868d9ff2b0 Merge remote-tracking branch 'upstream/main' into feat/chat-toolbar-commands 2026-08-05 19:56:11 +08:00
长真 cb2f240c0c chore(ci): refresh pr merge ref 2026-08-05 19:53:16 +08:00
github-actions[bot] 45b43e52bb chore: update beta formula for v1.0.57-beta.2 [skip ci] 2026-08-05 11:49:09 +00:00
长真 d800060e06 test(chat): cover toolbar command edges 2026-08-05 19:47:02 +08:00
chichuan 95a5cc42ce Merge pull request #879 from DingTalk-Real-AI/codex/changelog-v1.0.57-beta.2
docs: seal v1.0.57-beta.2 changelog
2026-08-05 19:36:25 +08:00
chichuan fec750b09e docs: remove duplicate beta.2 changelog entry 2026-08-05 19:26:27 +08:00
长真 8d8206f791 Merge remote-tracking branch 'upstream/main' into feat/chat-toolbar-commands 2026-08-05 19:20:34 +08:00
chichuan ddd5f15b91 docs: seal v1.0.57-beta.2 changelog 2026-08-05 19:19:49 +08:00
长真 15c2bd50b8 test(schema): derive chat shortcut counts 2026-08-05 19:16:25 +08:00
github-actions[bot] db50be868b Merge pull request #876 from DingTalk-Real-AI/codex/restore-chat-im-compat
fix(chat): restore stable send and history compatibility
2026-08-05 19:13:50 +08:00
长真 711d557b93 fix(chat): resolve schema policy BLOCK in toolbar remove-custom/create-custom
- Remove --yes from Selection.Examples in toolbar_remove_custom.go
  (schema_agent_examples.go forbids --yes in stored examples)
- Fix Confirmation "required" -> "user_required" in toolbar_remove_custom.go
  (schema catalog requires enum value from {not_required, user_required})
- Fix Idempotency "not_idempotent" -> "non_idempotent" in toolbar_create_custom.go
  (schema catalog requires enum value from {idempotent, non_idempotent, unknown})

Fixes: F1 BLOCK from stability-release-engineer round 5 review
2026-08-05 18:43:00 +08:00
Dennis a6220d7d8b fix(chat): preserve migration hints with legacy flags 2026-08-05 18:19:16 +08:00
长真 6102e9fc68 fix(chat): resolve code review BLOCK and WARNINGs for toolbar commands
- B1: Fix --sort-index 0 silent drop by using cmd.Flags().Changed()
  instead of value comparison in create-custom and update-custom
- W1: Add MarkFlagRequired("shortcut-id") in remove-custom and
  update-custom for consistent error messages
- W2: Extend SYSTEM_BUSY error handling to all write commands
  (add/hide/create-custom/remove-custom/update-custom)
- W3: Add duplicate key detection in parseExtension to prevent
  silent data loss on repeated --extension keys
2026-08-05 18:09:35 +08:00
Dennis 81bf0d2a6b test(coverage): stabilize drive worker cancellation branch 2026-08-05 18:05:54 +08:00
xiatian fd61868393 test(sheet): use managed seams in csv-put test 2026-08-05 18:02:01 +08:00
长真 ae426f37de feat(chat): add toolbar custom CRUD commands
Add 3 custom shortcut bar CRUD subcommands and update skill docs.

- toolbar_create_custom.go: create custom entry with extension parsing
  and org-id-list support (write/medium, not_idempotent)
- toolbar_remove_custom.go: delete custom entry with --yes confirmation
  gate (write/medium, confirmation required)
- toolbar_update_custom.go: update custom entry with same parameter set
  as create-custom plus shortcut-id (write/medium)
- chat.md: add toolbar command group documentation with all 7 subcommands
2026-08-05 17:58:23 +08:00
长真 4dbfaa4cef feat(chat): add toolbar commands (list/add/hide/sort)
Add `dws chat toolbar` command group with shared helpers and 4 basic
subcommands for managing conversation shortcut bar visibility and order.

- toolbar_helpers.go: shared utilities (hasIntersection, isSystemBusy,
  parseExtension, toolbarConversationID, toolbarNewSystemBusyError)
- toolbar_helpers_test.go: unit tests for shared helpers
- toolbar.go: command group entry assembling 7 subcommands
- toolbar_list.go: list shortcut entries (read/low)
- toolbar_add.go: add entries to visible area (write/low)
- toolbar_hide.go: hide entries from visible area (write/low)
- toolbar_sort.go: sort entries with intersection validation and
  SYSTEM_BUSY error handling (write/low)
- chat.go: mount newChatToolbarCommand() to chat root
2026-08-05 17:58:09 +08:00
玉澜andCursor d5c8982c00 feat(upgrade): always refresh to multi-skill layout (no sticky)
When a release zip contains multi/, upgrade one-shot refreshes to the
multi-skill layout and migrates existing mono installs. Docs drop the
cancelled runtime switch / sticky design.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 17:33:37 +08:00
Dennis f3a95d34a3 fix(chat): restore stable send and history compatibility 2026-08-05 17:32:12 +08:00
玉澜andCursor 2ea5acc2a3 fix(helpers): align PR868 whiteboard coverage with main API
After merging #861, use prepareWhiteboardCard and --yes so coverage
tests compile and match fail-closed confirmation + soft pending verify.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 17:07:19 +08:00
玉澜andCursor 5e1bac51e5 Merge origin/main into fix/cli-missing-surfaces-from-eval
Keep main/#861 fail-closed whiteboard; retain #868 missing surfaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 17:02:27 +08:00
Anonymity-0 f1e95d763d Merge branch 'main' into feat/robot-message-image-file 2026-08-05 16:59:20 +08:00
chichuan a37e6e6847 Merge pull request #875 from DingTalk-Real-AI/codex/changelog-v1.0.57-beta.1
docs: seal v1.0.57-beta.1 changelog
2026-08-05 16:51:42 +08:00
chichuan 0ceb96c745 docs: seal v1.0.57-beta.1 changelog 2026-08-05 16:47:10 +08:00
前津 9140015c42 test(chat): cover exclusive robot targets 2026-08-05 16:43:51 +08:00
前津 de418c5696 test: cover robot rich media branches 2026-08-05 16:43:51 +08:00
前津 5ec5b9811c chore(chat): omit rich media tests 2026-08-05 16:43:51 +08:00
前津 378eaa377e test(chat): use managed rich media seams 2026-08-05 16:43:50 +08:00
前津 d4368be1c3 fix(chat): preserve text schema compatibility 2026-08-05 16:43:50 +08:00
前津 9733acfb5a feat(chat): support robot image and file messages 2026-08-05 16:43:50 +08:00
github-actions[bot] 114503d52f Merge pull request #872 from lifeihong/feat/addUpdateUserOwnessV2A84934011
feat(contact): add update-ownness command for user personal status
2026-08-05 08:36:50 +00:00
chichuan 9de1c9c304 Merge branch 'main' into feat/addUpdateUserOwnessV2A84934011 2026-08-05 16:25:02 +08:00
克谨 b1d422dcfd Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-05 16:23:12 +08:00
克谨 1231e500a3 Merge remote-tracking branch 'origin/main' into fix/param-hallucination
# Conflicts:
#	internal/cli/param_concepts.json
2026-08-05 16:23:01 +08:00
github-actions[bot] 840e1d665f Merge pull request #861 from DingTalk-Real-AI/codex/sync-wukong-whiteboard
feat: add document whiteboard workflows
2026-08-05 16:17:14 +08:00
昕卉 f362c8c2a4 Merge remote-tracking branch 'upstream/main' into feat/addUpdateUserOwnessV2A84934011 2026-08-05 16:06:52 +08:00
chichuan e73a1556ce Merge latest main into codex/sync-wukong-whiteboard
冲突仅在 skills/mono/SKILL.md 的意图路由表,两侧改动正交,均保留:
- 本分支新增的 whiteboard 路由行
- main 把 event 行拆成 `event +listen-im` / `event consume` 的新表述
(下文「优先由一个 dws event +listen-im 进程表达目标」已是 main 版本,
保留旧 event 行会自相矛盾)
2026-08-05 15:59:17 +08:00
xiatian c508968814 feat(sheet): expose csv-put formula semantics 2026-08-05 15:51:43 +08:00
昕卉 186f2fa474 test(contact): add update-ownness tests and align confirmation with framework gate 2026-08-05 15:39:22 +08:00
github-actions[bot] d91a93c43b Merge pull request #860 from DingTalk-Real-AI/codex/multi-im-optimization
feat(im): harden Multi IM and publish complete Chat Schema
2026-08-05 15:28:29 +08:00
chichuan 08254e2a36 fix(whiteboard): fail closed when insert verification query fails
回查循环原先吞掉全部 queryErr,鉴权失败、MCP 错误与 JSONML 解析失败都
退化成 soft success 返回 whiteboardId: null,Agent 会把硬失败误判成最终
一致性并带着空 partId 继续调用 whiteboard query/update。

- 引入 errWhiteboardBlockPending sentinel,只有「块暂不可见」允许重试;
  其余错误立即返回,并把已插入的 blockId 带进错误消息供复原
- queryWhiteboardCardNode 严格校验 blocks 字段(缺失 / 非数组均为协议
  错误),避免畸形响应伪装成「块暂不可见」
- --ref-block 与 --parent-block、--where 与 --parent-block 显式互斥,
  锚点组装改用 else if 让单一定位分支在代码上自证
- 补全 mono / multi 两份 recipes.md 被截断的开篇句
- doc 根命令的命令结构清单补上 whiteboard insert 与 media upload/download
- 新增 3 个回归测试覆盖 fail-closed、soft success 与锚点互斥
2026-08-05 15:14:27 +08:00
Dennis e2c15fe9c8 fix(schema): reject breaking constraint expansion 2026-08-05 14:56:18 +08:00
Dennis 9fdf0d2cb3 fix(im): preserve incomplete read failures 2026-08-05 14:27:53 +08:00
玉澜andCursor 402429ac2a feat(skill): default installs and upgrades to multi-skill layout
Flip the agent-skill default from mono (single dws/ dir) to multi
(per-product dingtalk-* + dws-shared) across all distribution faces,
and fix the upgrade path so it no longer re-installs mono alongside
multi (mono+multi co-existence bug).

- upgrade: LocateSkillsRoot prefers the zip multi/ tree; multi refresh
  removes mono leftovers and stale skills, refreshes the multi cache
- install.sh/ps1/install-skills.sh/npm install.js: multi real-install
  (was print-only), default flipped, mono stays opt-in via DWS_SKILL_MODE
- skill setup: non-interactive default multi; full installs now clean
  stale dingtalk-*/dws-shared with confirm-preview disclosure, filtered
  (-s/-x) installs stay additive
- mutual exclusion is symmetric and includes dws-shared (previously
  leaked through the dingtalk- prefix) on all faces
- install.js: guard empty/corrupt multi trees (fall back to mono),
  validate SKILL.md on the mono branch, guard cache refreshes
- docs: roadmap (8/30 back-schedule), migration plan, distribution
  mechanism, rollout capability, capability completion, architecture
  optimization, wukong comparison (archived; line retired)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 14:13:45 +08:00
Dennis b6325a4f8a fix(im): harden user resolution and broadcast lookup 2026-08-05 13:57:27 +08:00
昕卉 fdd9e189d6 add update ownness 2026-08-05 13:32:23 +08:00
玉澜andCursor 65ce71b8d4 fix(helpers): avoid race on markdown diff compute seam
Capture runMarkdownUnifiedDiff/diffJSONMarshalIndent before spawning the
compute goroutine so testseam restores cannot race a late timeout path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 13:28:43 +08:00
玉澜andCursor 6bfcac4d54 test(helpers): use testseam for PR868 coverage seam swaps
Policy bans manual prev/t.Cleanup restores for package-var injection seams.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 13:08:24 +08:00
玉澜andCursor f819566c63 fix(schema): treat drive download --version as mapping exclusion
Keep the add-only Wukong compat flag, but publish it as a CLI-local
polymorphic dispatch without a download_file property binding.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 12:58:47 +08:00
玉澜andCursor 10d9aa3058 test(helpers): raise PR868 changed-line coverage to 100%
Exercise markdown diff, mail export/share, drive latest/depth, whiteboard,
and diff-engine edges via TestCrossPlatformCoverage*; add small injectable
seams only where defensive branches are otherwise unreachable.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 12:50:00 +08:00
chichuan eebdf52da9 fix: classify local whiteboard example precondition 2026-08-05 12:28:46 +08:00
玉澜andCursor a0ca1fdb28 feat(helpers): add minutes missing surfaces and add-only flag aliases
Expose minutes hot-word delete, permission apply, and audio-memo list from
live MCP gaps; add hidden/cross-product flag aliases only (never remove),
with TestCrossPlatformCoverage coverage for the new surfaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 12:04:27 +08:00
克谨 10943629d6 Merge remote-tracking branch 'origin/main' into fix/param-hallucination
# Conflicts:
#	Makefile
#	internal/cli/gen.go
#	internal/helpers/dev.go
#	scripts/policy/check-generated-drift.sh
2026-08-05 12:03:39 +08:00
chichuan 9eaee76a51 Merge origin/main into codex/sync-wukong-whiteboard 2026-08-05 11:51:35 +08:00
Dennis 2588c711a7 Merge origin/main into codex/multi-im-optimization 2026-08-05 11:24:58 +08:00
玉澜andCursor 5a93f80daa fix(ci): align new-surface dry-run preview kinds with Schema
Whiteboard dry-run now stamps preview_kind=plan; mail export/share
drop [DRY-RUN] tags so plan evidence matches declared DryRunSpec.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 11:21:38 +08:00
玉澜andCursor 8260cf7f53 fix(ci): drop --yes from mail share-to-chat examples
Schema Manual examples forbid confirmation bypass via --yes; also stub
whiteboardSleep in product example coverage so race CI does not hang.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 11:05:54 +08:00
玉澜andCursor 9fd38d9b9d fix(skills): drop stale EXPERIMENTAL banners and dead skill links
Align leftover multi skill banners with the non-experimental wording,
retarget markdown routing off dingtalk-misc, and remove the retired
SAFETY_PREAMBLE_INJECT marker plus the missing extract_media_id.py refs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 10:44:01 +08:00
玉澜andCursor 58dfbc5b6e feat: sync missing open CLI surfaces from wukong cli_to_mcp gaps
Port calendar event instances, markdown diff, drive list --latest,
mail calendar/calendar-event/shared-with-me/export/share-to-chat, and
doc whiteboard insert so open edition matches documented Wukong test
command surfaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 10:39:56 +08:00
john 000bc13450 Merge pull request #830 from typefield/agent/cmdcore-phase2
feat(corecmd): unify leaf/shortcut command framework onto a shared typed base
2026-08-05 10:03:21 +08:00
Dennis 01782cd9d7 test(profilectx): cover profile selector 2026-08-05 09:35:57 +08:00
玉澜andCursor ba56b7ff78 fix(test): use testseam.Protect in InitDepsForTest coverage
Policy bans manual t.Cleanup deps restores; Protect is the required seam form.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 02:03:51 +08:00
玉澜andCursor c5c97e60f3 test(coverage): cover InitDepsForTest and StaticServers visibility
Platform coverage only selects TestCrossPlatformCoverage*; name the new
declaration-only visibility regression accordingly and exercise the ForTest
deps restore helper.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 01:49:37 +08:00
玉澜andCursor 6780177356 fix(test): assert non-dry-run version preflight via CallTool
Outside --dry-run, list_doc_versions uses the normal CallTool channel;
CallReadTool is reserved for the dry-run read path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 01:30:17 +08:00
玉澜andCursor 5fa40b8ada fix: keep Schema source visibility without clobbering deps
Declaration-only roots skip injectStaticServers; derive product visibility
from StaticServers directly so reverse completeness is not weakened, and
restore helpers deps via pointer snapshot instead of InitDeps(nil).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 01:22:45 +08:00
玉澜andCursor 8a7d4a7027 fix: nil-safe doc deprecation wrappers for declaration-only Schema probes
Homology Execute probes use NewSchemaSourceRootCommand (no InitDeps); skip
deps.Out when unset and InitDeps a throwaway caller on the probe path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 00:18:46 +08:00
玉澜andCursor 894e550950 fix: keep schema source root from clobbering runtime deps/endpoints
Schema assembly must mount the reviewed command tree without InitDeps or
SetDynamicServers, so a live process keeps its ToolCaller and plugin
endpoints. Also restore doc version revert dry-run short-circuit so
--dry-run skips remote version preflight.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 23:42:05 +08:00
Dennis ad4ed41559 Merge origin/main into codex/multi-im-optimization 2026-08-04 22:23:31 +08:00
玉澜andCursor bb205c0f5c ci: align macOS auth filter with main
Drop Windows-only DPAPI export/import names from the darwin -run filter;
those tests skip on macOS and already run under the Windows job.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 22:14:48 +08:00
Dennis 86f9054d5b test(chat): cover batch output failures 2026-08-04 22:13:20 +08:00
玉澜andCursor 82c6b631bf Merge origin/main into agent/cmdcore-phase2
Resolve macOS CI auth/keychain job conflicts by keeping main's focused
6m keychain/auth split and unsigned-darwin self-heal filter, while retaining
this branch's portable DPAPI export/import diagnostics coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 22:11:33 +08:00
github-actions[bot] 306c30ecad Merge pull request #857 from DingTalk-Real-AI/codex/fix-macos-auth-test-scope
ci: avoid duplicate internal/app race suite on macOS
2026-08-04 22:07:20 +08:00
玉澜andCursor f793d980b5 fix(test): align leaf BuildArgs expectations with required-after-transform
TestLeafArgsOmitsEmptyAndNonPositive called BuildArgs with unset required
CSV flags; after rejecting empty required transforms that path correctly
errors. Satisfy required flags in the omit-empty case and cover the new
RequiredError / scalar-transform branches for the macOS coverage gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 21:54:02 +08:00
玉澜andCursor 706dbb349c fix(corecmd): reject required flags that transform to empty lists
Separator-only inputs like --event-codes ',' passed pre-transform Required
checks, then BuildArgs omitted the key and ConfirmFirst write paths could
still call MCP. Enforce non-empty transform results for Required flags and
add CrossPlatformCoverage regression coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 21:38:40 +08:00
chichuan d7c28bcfef fix: complete whiteboard skill examples 2026-08-04 21:19:45 +08:00
玉澜andCursor 45d0d1cd72 test(ci): reject restoring combined macOS app race shard
Keep the darwin workflow contract from re-accepting the merged
keychain+auth+app invocation that main briefly required.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 21:06:33 +08:00
玉澜andCursor e9a2360d36 fix(ci): align darwin workflow contract with auth/keychain split
Keep the changelog gate asserting the focused macOS auth/keychain job
instead of main's combined keychain/auth/app race command.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 21:05:17 +08:00
玉澜andCursor 9531dad9c6 Merge origin/main into agent/cmdcore-phase2
Port OA approval form-schema / forecast-process / create-instance from
main via DeclareLeafMetadata and mapping-ledger exclusions; keep retired
schema pin paths deleted and preserve the CI auth/keychain split.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 20:52:53 +08:00
chichuan 20d27f6db9 Merge latest main into codex/fix-macos-auth-test-scope
Resolve the macOS race budget conflict in favour of the focused scope.

c1f96241 on main extended the whole-package macOS race step from 10m to
12m and pinned that budget in the workflow contract. This branch removes
the whole-package run instead: ./internal/app is already covered by the
Ubuntu "race: app" shard, and macOS only needs the natively-gated tests.
With the focused scope the job drops from 10m47s to ~3m, so the 12m
budget is no longer needed and the two step timeouts (6m + 5m) fit inside
the 15m job budget with headroom.

The contract assertion c1f96241 added is superseded rather than dropped:
pinning both focused commands locks the per-step timeouts, and the
existing checks still block a whole-package regression and require
./internal/app to appear exactly once.
2026-08-04 20:14:58 +08:00
克谨 9fd9ae7a95 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-04 20:14:15 +08:00
chichuan 287b079c18 Merge origin/main into codex/sync-wukong-whiteboard 2026-08-04 20:12:37 +08:00
github-actions[bot] ca2b8adcb2 Merge pull request #853 from DingTalk-Real-AI/codex/sync-wukong-oa-approval
feat(oa): add approval form workflow commands
2026-08-04 20:05:58 +08:00
Dennis c4d5139a50 fix(chat): fail batch sends on delivery errors 2026-08-04 19:43:22 +08:00
Dennis 867f20abec test(chat): cover unsafe group files on Windows 2026-08-04 19:22:58 +08:00
Dennis 7c07b29de5 ci: retain failed Windows coverage profiles 2026-08-04 19:06:50 +08:00
Dennis 6ee0df8a9c test(chat): cover Windows drive-relative paths 2026-08-04 18:53:24 +08:00
Dennis 33ceab6000 test(chat): keep platform replace alias coverage-neutral 2026-08-04 18:36:39 +08:00
Dennis 26b06fe0ff fix(chat): replace exports atomically on Windows 2026-08-04 18:18:08 +08:00
chichuan a3c7009f9b Merge latest main into codex/fix-macos-auth-test-scope 2026-08-04 18:17:33 +08:00
chichuan 2d3f820f91 ci: keep the darwin-gated upgrade self-heal test reachable on macOS
Narrowing the macOS internal/app -run pattern orphaned
TestValidateNewBinary_RecoversFromUnsignedDarwin: it is the only
runtime.GOOS != "darwin" gated test in the package, the Ubuntu race shard
skips it on Linux, and the platform coverage gate only runs
^(TestAllShortcuts|TestCrossPlatformCoverage). No CI job selected it any
more, so it could never run or fail again.

- Add the self-heal test back to the macOS -run pattern.
- Add the (CrossPlatformCoverage)? group the Windows pattern already has,
  which also recovers TestCrossPlatformCoverageAuthMigrateKeychainRemainingBranches.
- Attribute vacuous runs: the two skip paths now name the branch that went
  unverified, and DWS_REQUIRE_AMFI_SELF_HEAL=1 escalates such a run to a
  hard failure on a host that does enforce amfid. GitHub's hosted macOS
  runners do not reproduce the amfid kill, so this test has been silently
  skipping there all along.
- Restore step-timeout headroom: 10m + 5m exactly equalled the 15m job
  budget, leaving none for setup. The keychain/auth step drops to 6m
  (measured 2m43s).
- Add a contract test that couples the macOS -run pattern to the set of
  darwin-gated tests in internal/app, so the next narrowing fails loudly
  instead of silently orphaning one.
2026-08-04 17:59:59 +08:00
chichuan 50f8ade1d7 Merge origin/main into codex/sync-wukong-whiteboard 2026-08-04 17:36:59 +08:00
chichuan f5a1b64d7a test(oa): include approval cases in native coverage 2026-08-04 17:36:22 +08:00
chichuan c1f96241af ci: extend macOS race test budget 2026-08-04 17:23:04 +08:00
玉澜andCursor eb63b3933e test(schema): restore interface metadata fallback coverage for overall gate
Cover applyInterfaceMetadataFallback success/audit paths and summary edges
that were lost when interface_metadata_test.go was retired, closing the
aggregate overall non-regression gap (~91.22% → above merge-base).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 17:19:51 +08:00
chichuan b87cad1eb5 docs: fix whiteboard protocol table 2026-08-04 17:14:11 +08:00
克谨 5b0198b2ec Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-04 17:12:53 +08:00
chichuan 0f2eec145e docs: add OpenNodes V1 whiteboard protocol 2026-08-04 17:10:15 +08:00
Dennis 7a5582f4f9 fix(ci): make multi IM coverage platform-safe 2026-08-04 17:05:59 +08:00
chichuan eb571e6e73 fix(oa): remove unreachable mode checks 2026-08-04 16:42:57 +08:00
玉澜andCursor 54358e1195 fix(schema): restore source_hash content validation on decode path
Re-enable snapshot.SourceHash vs schemaCatalogSnapshotHash compare in
loadSchemaCatalogSnapshot so tampered serialized catalogs fail closed.
Runtime assembly via assembleSchemaCatalogFromRoot still bypasses decode.
Adjust coverage tests to stamp valid hashes and avoid mutating the cached
delivery snapshot.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 16:41:40 +08:00
玉澜andCursor 4c5f8849d0 test(homology): replace Schema tool-count tripwire with non-empty check
The exact 848-tool assertion forced manual bumps on every identity change
without adding semantic value; require at least one ContractFinal leaf check.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 16:29:48 +08:00
玉澜andCursor 25a849d679 test(homology): bump Schema tool tripwire for wiki feed list
Merge of main added wiki.list_workspace_feeds (wiki feed list); update
the per-command consistency count from 847 to 848 so CI homology gates pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 16:25:29 +08:00
Dennis f050fbdebc Merge latest main into codex/multi-im-optimization 2026-08-04 16:17:01 +08:00
chichuan 4d5a47ac93 Merge latest main into codex/sync-wukong-oa-approval 2026-08-04 16:16:21 +08:00
Dennis e27dc9fe53 fix(schema): close chat runtime contract review 2026-08-04 16:09:54 +08:00
chichuan 6108f51c9d fix(oa): align approval mode schema contracts 2026-08-04 16:08:51 +08:00
玉澜andCursor ae77915507 Merge origin/main into agent/cmdcore-phase2
Incorporate wiki feed list command from main (#862) with
DeclareLeafMetadata declarations; keep retired schema pin paths deleted.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 16:07:16 +08:00
玉澜andCursor c494026305 test(coverage): restore aggregate coverage gate to 100% changed / non-regressing overall
Cover SetCommandAnnotation nil-map initialization and residual schema delivery
invariant error branches so aggregate Coverage passes alongside platform gates.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 15:53:19 +08:00
github-actions[bot] 6376f294da Merge pull request #862 from DingTalk-Real-AI/codex/sync-wukong-wiki-feed
feat(wiki): add knowledge base feed query command
2026-08-04 07:48:31 +00:00
Dennis 85587b9b62 fix(schema): preserve published chat constraints 2026-08-04 15:31:10 +08:00
chichuan f48a707e04 Merge latest main into codex/sync-wukong-wiki-feed 2026-08-04 15:30:25 +08:00
chichuan 7cb0de1f29 test(wiki): register feed command in the interface baseline
Add wiki.feed and wiki.feed.list to the CLI interface baseline so the new
command enters the backwards-compatibility contract and a later change
cannot silently drop it. The wiki root entry gains feed in its command
list; the leaf records the reviewed flags including the hidden
cross-product aliases.

Only the wiki nodes are merged. `make update-interface-baseline` would
also fold in 90 unrelated nodes that main has accumulated for chat,
aitable, doc, drive, and sheet; catching those up belongs in a separate
maintenance change, not in this feature PR.
2026-08-04 15:28:50 +08:00
Dennis 10d93f310e fix(schema): restore chat compatibility gates 2026-08-04 15:18:04 +08:00
玉澜andCursor 010d100e66 test(coverage): use testseam.Swap for overview render seam
Replace manual t.Cleanup assignment restore in the schema overview
render-failure coverage case so the schema-catalog policy seam gate passes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 15:09:03 +08:00
玉澜andCursor 32598fb38d test(coverage): close macOS changed-code coverage gaps to 100%
Add TestCrossPlatformCoverage cases for schema overview render failure,
marshalSchemaRaw error path, MCP metadata lookup in contract assembly,
and RegisterFlags MarkRequired+Aliases panic so platform coverage gate passes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 14:56:13 +08:00
chichuan f8d1fb84c0 Merge branch 'main' into codex/sync-wukong-wiki-feed 2026-08-04 14:42:10 +08:00
玉澜andCursor 22a20355e7 fix(drive): declare multipart download CLI flags in Schema
Main brought --part-size/--parallel/--no-resume onto drive download leaves
without ParamDecl or mapping exclusions, so Catalog fell back to
flag_name_inference and failed the unpinned-adapter mapping audit.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 14:24:54 +08:00
Dennis 99478c0060 feat(schema): publish complete chat shortcut catalog 2026-08-04 14:24:49 +08:00
克谨 dc854acb9b test: cover command fallback edge cases 2026-08-04 14:23:53 +08:00
玉澜andCursor ee286abb05 Merge origin/main into agent/cmdcore-phase2
Bring in v1.0.56 release line (multipart Drive downloads, chat download-media JSON fix, event-bus socket fix) while keeping PR #830's runtime Schema assembly and retired schema pin/catalog/bindings paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 14:14:50 +08:00
chichuan 64c2e8544c test: complete whiteboard branch coverage 2026-08-04 14:11:29 +08:00
克谨 d054e3dc01 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-08-04 13:52:34 +08:00
克谨 95536c3e97 fix(cli): complete shortcut hallucination fallbacks 2026-08-04 13:52:26 +08:00
chichuan fc31fddd73 test: cover whiteboard error paths 2026-08-04 13:51:37 +08:00
克谨 1bfedbd4d2 fix(cli): expand hallucination recovery coverage 2026-08-04 11:59:32 +08:00
chichuan 4298d0833b test: refresh CLI interface baseline 2026-08-04 11:45:05 +08:00
chichuan d3692e7b6e docs(wiki): document knowledge base feed query
Mirror the dws-wukong Skill updates for `dws wiki feed list` across both
Skill layouts: command reference, intent routing, the feed workflow, and
the nextToken context-passing row. Also refresh the wiki capability
summaries so the feed query is discoverable from the product tables.
2026-08-04 11:42:27 +08:00
chichuan e2e855d12a feat(wiki): add knowledge base feed query command
Port the knowledge base activity feed capability from the internal
dws-wukong branch feat/pull_knowledge_base_dynamic-wiki (merged there as
58fd118c) to the open-source CLI as `dws wiki feed list`.

The command forwards to the native wiki MCP tool list_workspace_feeds,
mapping --workspace/--limit/--cursor/--exclude-file onto
workspaceId/maxResults/nextToken/excludeFile. Cross-product hidden
aliases come from RegisterCrossProductAliases rather than hand-written
flags, so --workspace-id/--page-token/--next-token/--page-size all
resolve.

Register the reviewed Schema inputs (MCP contract pinned from the live
wiki tools/list payload, CommandRegistry entry, safety and selection
hints, parameter bindings, surface completeness count) and regenerate the
Catalog and Agent metadata projections.
2026-08-04 11:42:15 +08:00
chichuan 31e3f6bcbc Merge remote-tracking branch 'origin/main' into codex/sync-wukong-oa-approval
# Conflicts:
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog/catalog.json
2026-08-04 11:41:38 +08:00
chichuan 678f108adf docs(oa): complete approval workflow references 2026-08-04 11:39:55 +08:00
玉澜 bcb3b99faf refactor(cli): move test-only setFlagAnnotation helpers to test file, drop pflag import from seam 2026-08-04 11:34:58 +08:00
玉澜 9278a467b8 docs(cli): refresh runtimeCommandParameterSpecs required-floor comment 2026-08-04 11:26:21 +08:00
Dennis ee943d9b3f Merge remote-tracking branch 'origin/main' into codex/multi-im-optimization
# Conflicts:
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog/catalog.json
#	internal/cli/schema_hints/runtime-surface-completeness.json
#	test/mock_mcp/mock_mcp_smoke_test.go
2026-08-04 11:25:36 +08:00
chichuan 7e0957d9e8 feat: add document whiteboard workflows 2026-08-04 11:25:31 +08:00
玉澜 65ad8e0562 style(cli): gofmt schema_parameters_runtime.go 2026-08-04 11:23:13 +08:00
玉澜 1e14ed4a87 refactor(cli): relocate remaining test-only schema helpers and drop unused imports 2026-08-04 11:22:39 +08:00
Dennis a3c85a01a8 chore(im): sanitize pull request fixtures 2026-08-04 11:19:38 +08:00
玉澜 082a9bb93a refactor(cli): relocate test-only decodeSchemaMetaIndexLookup to test helpers 2026-08-04 11:16:21 +08:00
玉澜 772bf462ee refactor(cli): relocate test-only walkLeafCommands to test helpers 2026-08-04 11:15:02 +08:00
Dennis 81f67c8d7b fix(im): classify failures and normalize chat targets 2026-08-04 11:06:35 +08:00
github-actions[bot] e40f5bc537 Merge pull request #854 from DingTalk-Real-AI/codex/fix-chat-download-url
fix(chat): restore download-media JSON contract
2026-08-04 10:50:25 +08:00
玉澜 469d509cfb refactor(cli): drop three unused seam aliases 2026-08-04 10:50:03 +08:00
修雨 3210232876 Merge latest main into codex/fix-chat-download-url 2026-08-04 10:22:26 +08:00
玉澜 ad5909b8a6 docs(corecmd): describe risk/gate homology branches as residual bridges 2026-08-04 10:18:16 +08:00
github-actions[bot] 162a2eb0a7 chore: update formula for v1.0.56 [skip ci] 2026-08-04 02:16:23 +00:00
玉澜 3cce23e07d fix(corecmd): normalize AttachContract identity and selection pass-through
Trim Identity.Path and each alias in the declared identity copy (whitespace
could previously reach the wire), and route the declared Selection through
SelectionSpec.Normalized() so leaf and product pass-throughs share one
normalization. Wire output verified unchanged by the catalog gate.
2026-08-04 10:08:21 +08:00
chichuan d3f62193e7 Merge pull request #859 from DingTalk-Real-AI/codex/changelog-v1.0.56
docs: seal v1.0.56 changelog
2026-08-04 10:05:35 +08:00
修雨 1a11c687ed Merge remote-tracking branch 'origin/main' into codex/fix-chat-download-url 2026-08-04 10:04:55 +08:00
修雨 dfed4ba37d Merge main into codex/fix-chat-download-url 2026-08-04 10:04:07 +08:00
chichuan f26df04679 docs: seal v1.0.56 changelog 2026-08-04 10:00:32 +08:00
玉澜 fd6d3624c3 fix(corecmd): do not enum-validate env values on slice flags, which never transmit env 2026-08-04 09:58:08 +08:00
github-actions[bot] d02b03436d chore: update beta formula for v1.0.56-beta.4 [skip ci] 2026-08-04 01:55:53 +00:00
玉澜 b877172d7d refactor(corecmd): close alias/env validation gaps and prune dead symbols
Honor KindBool aliases in BuildArgs/hasEffectiveValue/constraintProvided;
reject MarkRequired+Aliases combinations at registration; validate
env-sourced values against declared enums; drop dead
ValidationEffective/ProjectDeclaredParameters constants and the unused
AnnotateRuntimeFlag parameter; route confirmationBypass through BoolFlag;
refresh retired-flow comments.
2026-08-04 09:46:55 +08:00
chichuan bc7b96ba5f Merge pull request #858 from DingTalk-Real-AI/codex/changelog-v1.0.56-beta.4
docs: seal v1.0.56-beta.4 changelog
2026-08-04 09:46:33 +08:00
玉澜 28df903801 refactor(cli): drop duplicate stringSetsEqual in favor of stringSlicesEqualAsSet 2026-08-04 09:37:14 +08:00
克谨 cfaf161fc7 fix(cli): add reviewed command path fallbacks 2026-08-04 09:35:48 +08:00
chichuan 9539c887f6 docs: seal v1.0.56-beta.4 changelog 2026-08-04 09:35:44 +08:00
玉澜 b4b4a31979 refactor(cli): tighten schema pipeline hygiene
Move the test-only runtimeCommandParameters adapter to the test helper
file; route snapshot-decoded optional slices through cloneOptionalStrings
so wire structs are never aliased into typed specs; drop three dead
annotation aliases; collapse the triplicated catalog/surface hash
stamping into stampSnapshotHashes; make registry-vs-command validation
iterate in sorted order; guard the enum mcp candidate with hasPinned
like its siblings; refresh stale discovery-era comments.
2026-08-04 09:34:51 +08:00
github-actions[bot] 6607f44724 Merge pull request #852 from AlwaysLee/feat/center-protocol-transfer
feat: multipart download engine with checkpoint resume and credential refresh
2026-08-04 09:29:17 +08:00
半圭 837a96fe3d fix: show friendly message on Ctrl+C instead of internal error JSON
When user interrupts multipart download with Ctrl+C, display a helpful
message indicating checkpoint is saved and download can be resumed,
instead of returning an internal error with context.Canceled.
2026-08-04 08:58:17 +08:00
玉澜 276837caae refactor(cli): move test-only schema helpers out of production files
Relocate seven functions with zero production callers
(schemaProductToolCount, normalizeRuntimeSchemaGroups,
runtimeFlagRequiredState, deliverySchemaCatalogAvailable,
exactSchemaCommand, schemaMap, schemaToolSpecFromPayload) into a
_test.go helper file, shrinking the shipped binary surface.
2026-08-04 08:56:42 +08:00
玉澜 ec7f4deaf4 refactor(corecmd): retire zombie annotation paths and fix alias validation
Make enum and required-flag validation alias-aware via a shared
flagNameProvided/EffectiveValue path so values passed through flag
aliases are no longer skipped; drop the fully-migrated runtime risk/gate
annotation bridge and the orphaned tool-metadata/title annotation
writers, trimming the cli seam re-exports accordingly.
2026-08-04 08:36:26 +08:00
玉澜 e135440b98 refactor(cli): retire legacy-overlay schema stack and gate test fixtures
Drop the legacy-metadata overlay path (runtimeToolSpecAllowingLegacy,
assembleSchemaRegistryFromBoundAllowingLegacy, metadata-based agent
selection, dry-run seams) so assembly flows exclusively through
ContractFinal; gate MCP fixture machinery behind a build flag so it can
never enter production assembly. Wire output verified identical before
and after; policy tripwire reports 26 products, 847 tools.
2026-08-04 08:36:22 +08:00
玉澜 93d7e5a4c6 refactor(app): consolidate command-framework seams and unify recovery errors
Centralize dynamic-server command-key protection, profile runtime
selection, and endpoint-resolution error construction; drop the dead
dry-run catalog-miss branch; document host_compat stubs as edition-sync
anchors; keep test fixtures out of the package dir via t.TempDir.
2026-08-04 08:36:12 +08:00
玉澜 fe969dad51 refactor(app): unify dynamic server registration and drop dead command surfaces
- Collapse SetDynamicServers/registerDynamicServer/AppendDynamicServer
  into one registration core; the ServerOverride-skip now applies to all
  paths and AppendDynamicServer keeps its cmd-key no-overwrite guard.
- Delete executor.NewWorkflowInvocation (never constructed, no gate
  accepts the kind) and newLegacyHiddenCommands (always returned nil).
- Route the single-profile branch through the runnerResolveProfile seam,
  matching the multi-profile branch.
- Refresh stale discovery-era comments (no wire strings changed).
2026-08-03 23:30:54 +08:00
半圭 fdcd44f9e3 fix: handle Ctrl+C (SIGINT) gracefully during drive download
- Replace context.Background() with cmd.Context() in download and
  download-version commands so SIGINT propagates to download goroutines
- Enables graceful interruption of multipart downloads via Ctrl+C
2026-08-03 23:21:59 +08:00
半圭 34c0c86a59 feat: center protocol upload/download refactoring with multipart download
- Add multipart download engine (drive_transfer.go) with Range probe,
  resume support, and credential auto-refresh on 401/403
- Add --part-size, --parallel, --no-resume flags to drive download and
  download-version commands
- Replace httpGetFile with driveTransferDownload for chunked parallel
  downloads in download and download-version commands
- Replace uploadToDrive credential parsing with driveUploadPut
  (transparent header pass-through, retry on 401/403)
- Add typed httpStatusError for non-2xx HTTP responses in doc.go
- Add comprehensive unit tests (32 cases) for drive_transfer
- Update drive reference documentation with multipart download behavior
- Add E2E test for multipart download (auto-test/, gitignored)

CR: 28984991
2026-08-03 23:21:59 +08:00
Raph a240ad2b81 ci: focus macOS auth race tests 2026-08-03 23:09:58 +08:00
玉澜 affc8715be refactor(cli): drop the always-empty interface_metadata wire projection
embeddedMCPMetadata only feeds interface validation now; its summary was
projected onto every schema payload as a constant-empty blob. Remove the
SchemaRegistry field, all three payload projections, the snapshot wire
field, the overview copy, the compact strip entry, and the jq policy gate.
Also delete the redundant io.Discard dead-code suppressor in
event_command.go (io has five genuine uses there).
2026-08-03 22:21:30 +08:00
玉澜 5c7407532a refactor(app): inline mcp command surface and retire CatalogFixtureEnv
The mcp command now delivers its final surface in NewMCPCommand instead
of root.go overriding Hidden/Short/Long after construction.
CatalogFixtureEnv no longer gates anything once discovery is gone:
endpoint resolution is the dynamic server registry only, so a miss is
terminal by design.
2026-08-03 22:04:02 +08:00
修雨 b1f4a5d62a test(chat): add download-media CLI integration coverage 2026-08-03 21:50:17 +08:00
修雨 bf33ab622f fix(chat): restore download media JSON result 2026-08-03 21:50:17 +08:00
玉澜 843ba7d81b refactor(app): remove the retired discovery layer; endpoints resolve via the dynamic server registry only
EnvironmentLoader.Load has returned a constant empty catalog since live
discovery was retired, leaving a zombie chain: loader interface, catalog
types, degraded-error semantics kept alive only by a `var _ =` suppressor,
and runner/recovery fallback branches that could never succeed.

- loader.go shrinks to the env constants and CLIFlagHint; the
  DiscoveryCatalog / DiscoveryCatalogLoader / DiscoveryDegraded families
  are deleted.
- runtimeRunner and recoveryRuntime drop the loader field; a direct-runtime
  miss is now terminal through handleCatalogMiss, and recovery endpoint
  resolution is directRuntimeEndpoint only. directRuntimeToolEndpoint loses
  its sole caller and is removed.
- Tests: loader-injection branches are deleted; live-behavior coverage
  (mock mode, direct-runtime hit/miss, recovery resolution, catalog-miss
  error path) is rewritten against the new flow.
2026-08-03 21:45:58 +08:00
chichuan f39a3f5417 Merge branch 'main' into codex/sync-wukong-oa-approval 2026-08-03 21:18:39 +08:00
玉澜 7afd4139fc refactor(cli): drop dead discovery loader params and retired MCP pin machinery
- NewSchemaCommand / NewMCPCommand / newCatalogCommand no longer accept a
  DiscoveryCatalogLoader they always discarded; schema's no-discovery
  property is now structural, retiring the panic-loader test guard along
  with the trivial root.go wrappers and the unused buildMCPCommandFn seam.
- Delete the lazy sync.Once / atomic counter around the retired MCP pin:
  runtimeMCPMetadata only existed so a diagnostic counter could observe a
  loader that always returns the constant empty pin. Assembly now calls
  emptyPinnedMCPMetadata directly and SchemaMetadataLoadCounts loses the
  dead MCPMetadata field (the policy bans keep the retired names from
  reappearing).
2026-08-03 21:15:20 +08:00
github-actions[bot] b2cbca2762 chore: update beta formula for v1.0.56-beta.3 [skip ci] 2026-08-03 12:55:19 +00:00
chichuan 9ce95db08e Merge pull request #855 from DingTalk-Real-AI/codex/changelog-v1.0.56-beta.3
docs: seal v1.0.56-beta.3 changelog
2026-08-03 20:39:44 +08:00
Dennis 30314311e4 fix(im): harden live audit contracts 2026-08-03 20:39:27 +08:00
玉澜 5b7bea8b11 refactor(cli): rename CatalogLoader to DiscoveryCatalogLoader and drop internal/ir catalog
Complete the in-flight rename: Catalog / CatalogLoader / CatalogDegraded*
become DiscoveryCatalog / DiscoveryCatalogLoader / DiscoveryDegraded* in
internal/cli/loader.go, the minimal stubs no longer live in internal/ir,
and schema_static_test.go's panic loader is migrated so the cli test
package compiles again.
2026-08-03 20:36:41 +08:00
chichuan e99c20a0a1 docs: seal v1.0.56-beta.3 changelog 2026-08-03 20:34:17 +08:00
玉澜 0fbdfe0130 test(seam): make testseam the only seam-swap form and legislate it
- Add testseam.Protect for save-and-restore seams with no up-front stub
  value (e.g. os.Args mutated by the code under test).
- Migrate all 35 remaining manual prev/assign/t.Cleanup-restore trios to
  testseam.Swap/Protect across app, auth, cli, event, helpers, output,
  pipeline, and shortcut tests; restores can no longer be forgotten.
- check-schema-catalog.sh: fail closed when a manual seam restore
  reappears in any *_test.go (internal/testseam exempt).

Seam injection is now a mechanism, not a convention.
2026-08-03 20:00:47 +08:00
Dennis 2a8c6c87cb feat(im): harden multi IM golden routes 2026-08-03 19:50:10 +08:00
chichuan e806e761ad test(oa): cover approval request branches 2026-08-03 19:48:26 +08:00
玉澜 e5a47a2d18 docs(schema): scrub stale phase/generated-file/hints wording in comments
- aitable_schema: declarations live in aitable.go, not a (nonexistent)
  aitable_schema_decls_generated.go.
- schema_parameter_bindings / mapping_ledger: drop retired 'Track 1 Phase 2'
  completion-gate framing; describe present state (ParamDecl.Property owns
  delivery, no committed bindings JSON).
- schema_contract_model: the Catalog is runtime-assembled/delivered, not
  embedded.
- schema_catalog: BuildSchemaCatalogSnapshot takes no Cobra root because
  identity must not be re-derived at the render boundary (no 'reapplying
  manual hints').

Comment-only; reviewed audit Reason strings left untouched.
2026-08-03 19:32:01 +08:00
chichuan a6696fe9e9 fix(schema): map OA request wrappers 2026-08-03 19:26:22 +08:00
玉澜 2e51dcf35d docs(schema): tell the truth about single-source identity and wire policy
- schema_cobra_binding: the Identity-vs-spec check is now a defensive
  self-consistency assertion (the spec is collected from the same
  ContractFinal.Identity), not a cross-source pin; name the real drift
  anchors (native annotation cross-check, collector uniqueness
  self-validation, homology tool-count tripwire, surface/catalog hash
  baselines) and relabel the mismatch diagnostic as collected vs declared.
- schema-compat: state explicitly that accepting interface_type clearing
  is a deliberate wire-visible policy decision taken with the MCP pin
  retirement (missing = unknown; re-population requires ParamDecl).
- schema_command_registry: drop retired bindings audit / MCP pin from the
  peer reviewed-inputs comment; note they must not reappear.
- canonical: schema help no longer claims commands must enter a reviewed
  registry; identity is collected from ContractFinal.Identity.
- homology: the tool-count tripwire error now says where to bump it after
  review.
- Sweep stale 'reviewed registry' wording in corecmd and the help-flag
  completeness gate comment; AGENTS.md interface-facts section matches.
2026-08-03 19:22:53 +08:00
玉澜andCursor e54927107f test(cli): cover changed-code gaps for coverage gate
Exercise BuildEffectiveCommandRegistry nil-root, loadSchemaSourceRootFn
before first store, and map-key JSON diff branches so aggregate changed-code
coverage reaches 100%.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 19:10:13 +08:00
chichuan 06af21c7a1 fix(oa): complete approval instance options 2026-08-03 19:09:51 +08:00
chichuan eba2b692ec feat(oa): add approval form workflow commands 2026-08-03 19:02:39 +08:00
玉澜andCursor a5fd64a908 fix(ci): close stdin handles on swap and align identity/policy checks
Close each owned os.Stdin file when replacing it in the stdin coverage
matrix so Windows TempDir cleanup does not fail on leaked handles. Update
the command registry coverage test for contract_identity source and drop
the retired loadPinnedMCPMetadata loader reference gate from policy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 18:41:03 +08:00
玉澜andCursor 4262a50c16 fix(ci): close stdin before TempDir cleanup on Windows coverage
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 18:18:30 +08:00
玉澜 9bc6a15232 Merge phase3-followups: catalog side-guards, CommandSpec.Source=contract_identity, docs 2026-08-03 18:14:13 +08:00
玉澜andCursor 9d1c3c5c95 fix(ci): restore schema-compat and coverage after MCP pin retirement
Allow clearing interface_type and expanding constraint group members so
MCP-pin retirement and declare≡execute alias groups stay backward-compatible.
Close platform coverage gaps with TestCrossPlatformCoverage* and bump the
ContractFinal consistency count to 847.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 17:58:38 +08:00
玉澜andCursor ba72358f78 retire pinned schema_mcp_metadata.json from Schema assembly
Schema Catalog now assembles from Contract/ParamDecl/Interface and Cobra only.
Keep fetch-mcp-metadata as an optional diagnostic dump and ban the retired pin path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 17:24:32 +08:00
玉澜 07fea09561 chore(schema): Phase 3 follow-ups after registry retirement
- Relocate the registry-agnostic side-guards from the deleted
  check-schema-command-registry.sh into check-schema-catalog.sh:
  legacy hint/visibility source ban, go:generate single-track checks,
  agent-metadata embed/loader bans, lazy-loader reference-count checks,
  package-scope eager-initializer ban, internal/app loader ban, and the
  two fresh-process laziness tests (TestRuntimeSchemaMetadataLoadsOnlyOnDemand,
  TestOrdinaryRootCommandsDoNotLoadSchemaMetadata). Drop the guards that only
  protected the retired reviewed registry (JSON Schema/product shard presence,
  registry-overwrite go:generate ban, registry-count test runs); the native
  materialization ban already lived in check-schema-catalog.sh.

- Rename the wire-visible CommandSpec.Source label from
  "reviewed_command_registry" to "contract_identity" (new exported
  constant CommandSourceContractIdentity): identity is collected from
  ContractFinal.Identity declarations, the registry is gone. Source is a
  provenance label excluded from the identity SourceHash (surface hash is
  unchanged); the catalog content hash shifts with the delivered bytes as
  expected. Updated every assignment and every test pin consistently.

- Update docs/schema-dynamic-endpoint-design.md,
  docs/rfc-command-framework-convergence.md and
  docs/flag-help-schema-homology.md: collector is the single identity
  source, reviewed registry retired; keep genuine historical context.
2026-08-03 17:07:36 +08:00
玉澜andCursor 057a860dcc retire MCP service review without a replacement ledger
Drop schema_mcp_service_review disposition gates from policy, outputguard,
and docs. Keep schema_mcp_metadata.json as the only pinned MCP baseline.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 17:04:42 +08:00
玉澜andCursor aeec39115f retire schema_mcp_service_review.json into Go ledger
Keep notify→out_of_surface disposition and snapshot hash alignment as
reviewed Go constants so policy/tests no longer depend on a committed JSON.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 17:01:15 +08:00
玉澜andCursor 562c29905f Merge origin/main into agent/cmdcore-phase2
Resolve CONFLICTING with Phase 3 identity collection: keep retired
schema_hints/catalog/registry/agent_metadata deleted, port aitable
workflow edit-example via DeclareLeafMetadata, and retain main's
event-bus socket fix plus CR #7 constraint/count gates.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 16:47:44 +08:00
玉澜andCursor 7a945318e0 fix(schema): align CR #7 declare≡execute constraints with gates
Update shortcut/app expectations, catalog jq, and schema-compat to accept
full hidden-sibling constraint groups, and bump delivered shortcut count to 216.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 16:37:48 +08:00
玉澜 b1cefba808 refactor(schema): retire reviewed registry; collector is the single identity source
Phase 3 of identity-deregistry. BuildEffectiveCommandRegistry now builds the
EffectiveCommandRegistry from CollectIdentitySpecs(root) instead of the
embedded reviewed registry, and the registry source is removed atomically:

- delete internal/cli/schema_command_registry/ (registry.json + products/),
  schema_command_registry.schema.json, the three //go:embed directives, and
  the registry-only loaders/validators (loadReviewedCommandRegistry,
  decodeCommandRegistry, ValidateCommandRegistrySource, shard assemble/merge
  helpers, ReviewedCommandRegistryMergedJSON/SourceHash, ReviewedCommandSpecs)
- keep CommandSpec/CommandRegistry/EffectiveCommandRegistry,
  newEffectiveCommandRegistry/indexCommandSpecs, and SourceHash; the
  collector-built effective hash is byte-identical to the reviewed one, so
  catalog surface_hash/source_hash are unchanged
- convert the Phase 2 dual-run gate into TestCollectedIdentityIsValidSingleSource:
  collected specs non-empty, no missing primaries, effective build succeeds,
  SourceHash stable across repeated collection walks
- generators: catalog -surface and agent-metadata -registry/-surface become
  fail-closed retired valves; outputguard no longer protects the registry
  paths; fetch_mcp_metadata derives interface refs from collected identity
  instead of the merged registry JSON
- retire scripts/policy/check-schema-command-registry.sh and its Makefile
  invocation; generate-schema/check-generated-drift now fail closed if
  schema_command_registry/ reappears
- update AGENTS.md, docs/reference.md, and in-code reviewed-input notes
2026-08-03 16:18:20 +08:00
github-actions[bot] 96bfae079a Merge pull request #846 from wxianfeng/fix/event-unix-socket-tmpdir
fix(event): use secure Unix bus runtime directory
2026-08-03 16:04:41 +08:00
wxianfeng bb18cdba3b Merge upstream/main into fix/event-unix-socket-tmpdir 2026-08-03 15:45:38 +08:00
wxianfeng 015a1f85ca fix(event): satisfy platform coverage gate 2026-08-03 15:42:17 +08:00
玉澜 124ddd85f2 docs(schema): drop stale Phase 2 label from assembly switchover note 2026-08-03 15:14:43 +08:00
github-actions[bot] 8854e0d1d4 Merge pull request #851 from abucraft/codex/aitable-workflow-docs
feat: add aitable workflow edit example command
2026-08-03 07:01:27 +00:00
玉澜 99ca88597e docs(schema): note assembly switchover must be atomic with registry removal
Flipping BuildEffectiveCommandRegistry to the identity collector before
removing the reviewed registry is not a clean incremental step: the collector
only finds leaves present in the tree, so the 'reviewed entry without a Cobra
leaf' bind-failure path disappears and synthetic-root tests that exercise it
break. The switchover therefore ships together with the registry removal
(Phase 3) as one atomic change. The standing dual-run gate
(TestCollectedIdentityMatchesReviewedRegistry) keeps collected identity
byte-equivalent with the registry until then.
2026-08-03 15:01:08 +08:00
Dennis 017258e5b4 feat(im): optimize Multi IM golden routes
Unify natural target resolution and message contracts, add deterministic IM event listening, streamline cold-start skills, and cover the flows with schema gates and end-to-end tests.
2026-08-03 14:54:19 +08:00
镜玄 22862508b8 feat: add aitable workflow edit example command 2026-08-03 14:47:59 +08:00
玉澜andCursor 22d3dd1096 fix(corecmd): close CR follow-ups for source-root sync, Default, constraints
Synchronize schemaSourceRootFn via atomic.Value, fail closed on malformed
Int/Bool FlagSpec Default, and stop projecting a sole visible flag as
required when a hidden sibling still satisfies ValidateConstraints.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 14:44:06 +08:00
玉澜 4ad8cce5f3 feat(schema): make identity dual-run a standing gate and self-validate collector
Phase 2 of identity-deregistry:
- Promote the opt-in probe to a standing regression gate
  (TestCollectedIdentityMatchesReviewedRegistry, no env var): collected
  Contract.Identity must stay byte-equivalent to the reviewed registry.
  This is the insurance that lets Phase 3 retire the registry; its
  MISSING_PRIMARY/DIAG/DIFF logs pinpoint any drifted command.
- CollectIdentitySpecs now self-validates (fail closed): duplicate canonical
  paths, duplicate primary CLI paths, and alias collisions with a primary
  path or another alias all error at collection time.
2026-08-03 14:42:57 +08:00
玉澜 c53ed8d383 feat(schema): add identity-deregistry probe proving byte-equivalence with reviewed registry
Phase 1 of identity-deregistry: demonstrate command identity can be collected
from live Cobra leaves carrying ContractFinal.Identity, byte-equivalent to the
reviewed schema_command_registry.

- schema_identity_collect.go: CollectIdentitySpecs walks ALL runnable leaves
  (hidden included, mirroring bindCommandRegistryPath reachability) and builds
  CommandSpec from ContractFinal.Identity; CompareCommandSpecEquivalence and
  DiagnoseMissingPrimaries produce a deterministic diff/diagnostic report.
- opt-in probe test (DWS_IDENTITY_PROBE=1): collected SourceHash equals
  reviewed SourceHash (846 commands), zero missing primaries, zero field diffs.
  Skips without the env var so normal test runs are unaffected.
- registry: add minutes.shortcut_minutes_search (a declared read-only smart
  shortcut with full Identity, consistent with 215 registered sibling smart
  shortcuts); homology reviewed-tool count 845 -> 846.

Registry SourceHash advances 60eee8e2 -> 2214177084; no pinned baseline
references the old value.
2026-08-03 14:33:38 +08:00
玉澜andCursor d10738d0db fix(schema): restore ForTest boundary and document at_least_one empty-string change
Extract production resetSchemaDeliveryState for RegisterSchemaSourceRoot,
gate production *ForTest call sites, and record the H0 constraint "provided"
semantics in CHANGELOG.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 14:29:13 +08:00
玉澜andCursor f26968264d test(schema): cover Identity/AttachContract edges for platform gate
Fill the remaining ~12 changed-code stmts blocking Coverage at 99.85%,
and point RFC reviewed-input wording at the Go mapping ledger.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 13:38:43 +08:00
玉澜 517eedb412 refactor(schema): drop dead cli.RegisterRuntimeContractFinal wrapper and legacy catalog label
- 删除 cli.RegisterRuntimeContractFinal 包装器:生产侧零调用(全部走
  corecmd.New 内部注册或 contractfinal 直调),9 处测试调用迁移到
  contractfinal.RegisterRuntimeContractFinal
- 删除死常量 ProvenanceEmbeddedCommandCatalog(全库零引用的 legacy
  wire label;运行时装配统一打 SchemaSourceRuntimeAssembled)
- 同步 6 处文档/注释:AGENTS.md、RFC §277、contract/final.go、
  contractfinal/store.go、contractfinal/doc.go、corecmd.go、
  contract/doc.go、contract_register_seam_test.go 的死符号钉扎改为
  import 前缀分层检查兜底
2026-08-03 13:26:34 +08:00
玉澜andCursor 6210840b54 retire empty schema_parameter_bindings.json audit table
Move mapping_exclusions/removals into a reviewed Go ledger so ParamDecl.Property
stays the sole property authority without a committed empty bindings{} Phase 2 gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 13:09:14 +08:00
玉澜andCursor 2d76433be0 docs(schema): group reviewed inputs beside command registry
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 12:27:26 +08:00
玉澜andCursor b3adfa8d26 feat(schema): require Contract.Identity aligned with reviewed registry
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 12:10:54 +08:00
玉澜 633862d07a docs(schema): add command-registry guide and correct AGENTS.md facts
- docs/schema/schema-command-registry.md:身份册论证(为什么不能删/
  Cobra 不够当身份源/Contract 不能顶替)、文件结构、三层校验、
  写入格式与验证步骤、防漂移表与「精确版」边界
- AGENTS.md:修复 go build ./cmd 报错命令为 make build;registry 指南
  指向 docs/schema/schema-command-registry.md;Tier1 精确为
  helpers.NewLeafCommand;区分 ResolveSchemaBuild 与 deliverySchemaCatalog
  的 lazy 包装;runtime-confirmation 脚本描述补运行时门禁探测
2026-08-03 11:59:05 +08:00
wxianfeng 5459bcc524 fix(event): secure Unix bus runtime directory 2026-08-03 11:40:01 +08:00
玉澜 2f31f48da0 docs(agents): pin testseam swap, fortest.go, and platform-gate test naming conventions 2026-08-03 09:22:35 +08:00
玉澜 3fb8631e5f docs(testseam): warn Swap is unsafe for t.Parallel tests 2026-08-03 08:40:33 +08:00
玉澜 a57e6bbfeb test(app): cover pure token/profile/retry helper branches
主覆盖门禁在 Linux CI 以 -0.0008pp 惜败 overall 非回归(changed-code
已 100%)。补 25 句纯函数分支覆盖抬高 overall:
tokenResolutionError 四分支、profileSwitchProfileCells sameCorp 消歧、
authRefreshFailureError.Unwrap / withAuthRetrying / managesRuntimeOAuth、
ForceRefreshAccessToken 与 forceRefreshRejectedAccessToken 守卫、
getCachedRuntimeToken prefetch 缝。
2026-08-03 02:42:39 +08:00
玉澜 20a4eb77a4 test(smoke): share one root command across --help subtests
TestCLISmoke_AllPublicCommandsSupportHelp 之前对 845+ 条命令路径每条
重建一次 NewRootCommand;Linux TSan 影子内存随树构建次数持续累积,
CI ubuntu runner 上 ~3 分钟即被 OOM SIGTERM(三次同形态失败)。
--help 不 mutate 命令状态,共享单个 root 即可:本地 race 峰值内存
4.1GB -> 844MB,无 race 全套耗时同时从分钟级降到 ~1.5s。
2026-08-03 01:58:12 +08:00
玉澜 e0bd2a88c0 test(schema): name new coverage tests for the platform gate selection
平台覆盖门禁仅运行 TestAllShortcuts|TestCrossPlatformCoverage 前缀的测试。
testseam 与 coverage-gate 的新测试原名不在选择集内,导致 seam.go 与
physicalPath 在 darwin profile 中未覆盖(CI Coverage(macOS) 99.8737%)。
按仓库既定命名约定改前缀。
2026-08-03 01:30:31 +08:00
玉澜 059bdfd03e style(runtimeannotate): gofmt annotation assertion map 2026-08-03 01:20:35 +08:00
玉澜 260d8ddddd test(schema): replace coverage line-touches with real assertions
review 遗留的 coverage theater 清理(M3 已由 7257919a 先行修复):
- agentmetadata:selection precedence 双向 round-trip 断言、cloneInterfaceRef
  深拷贝断言、record/merge candidate 去重与合并结果断言
- cli:schemaOverviewPayloadFromCatalog 产物内容断言、walkLeafCommands
  hidden 叶子排除断言、agentMetadataSummaryFrom 汇总字段断言、
  RenderSafetyAnnotation 未注册时静默断言
- corecmd:stdinIsTerminal 以临时普通文件断言非终端路径
- runtimeannotate:AnnotateRuntimeFlag* 写入断言(type/description/format/
  example/required/required_when/enum 注解值)
2026-08-03 01:13:31 +08:00
玉澜 c0808ab5e6 fix(policy): normalize symlinked paths in coverage-gate buildable scope
goListBuildableFiles 之前用 git rev-parse 的物理路径与 go list 由逻辑
CWD 派生的 Dir 做 filepath.Rel;macOS 上 /tmp -> /private/tmp 分叉时所有
buildable 文件都落到根外,--scope-buildable 静黙放空 changed-code 门禁
(本地 /tmp worktree 必中,Linux CI 不触发)。新增 physicalPath 对两侧
统一 EvalSymlinks 归一,并补直测与端到端用例。
2026-08-03 01:01:52 +08:00
玉澜 ea43db1d64 refactor(schema): drop cli shim packages, add testseam swap, consolidate ForTest helpers
- import 统一:删除 cli/contractfinal 与 cli/runtimeannotate 垫片包,
  26 个消费文件一律直引 corecmd/*;cli 根仅保留 runtime_schema_seam.go
  包内别名,依赖图保持单向无环
- 新增 internal/testseam.Swap[T]:包级 var 注入缝置换由 t.Cleanup
  结构性恢复;迁移 pipeline*/stdinIsTerminalFn/loadReviewedCommandRegistry/
  schemaCommandCatalogError/schemaParameterBindingData/finalSchemaAgentMetadata
  六组核心缝(26+ 处)
- ForTest 辅助归拢到 per-package fortest.go(corecmd/contract、
  corecmd/contractfinal、shortcut、cli),生产文件只留真逻辑
- 文档同步:runtime_schema_seam.go / runtimeannotate/doc.go 注释、
  CHANGELOG、RFC §278-279、AGENTS.md
2026-08-03 00:50:16 +08:00
玉澜andCursor 7257919a49 test(schema): harden coverage-gap assertion teeth
Fail closed on uniqueStringsInOrder, empty-bound assemble, and delivery
completeness report branches instead of silently accepting weak paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 23:40:05 +08:00
玉澜andCursor 27f0fd4337 fix(ci): isolate test/smoke into its own race shard
race:remaining was SIGTERM'd (exit 143) mid test/smoke after mock_mcp with
no FAIL/DATA RACE; NewRootCommand public-tree smoke under -race is too heavy
to share that shard. Mirror the cli split and give smoke a 15m budget.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 22:28:19 +08:00
玉澜andCursor e4fde3917d test(schema): close overall coverage non-regression gap
Cover remaining cli/agentmetadata/pat edge paths so aggregate coverage
stays at or above the merge-base overall percentage.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 22:06:56 +08:00
玉澜andCursor 812ec4f87e fix(schema): route assemble injection tests through production path
AllowingLegacy bypasses assembleRuntimeToolSpec, so the coverage injection
stubs never ran and CI failed on a false provenance error before the gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 21:35:07 +08:00
玉澜andCursor 8d799979d4 test(schema): close remaining assembly and metadata marshal coverage gaps
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 20:02:45 +08:00
玉澜andCursor 2839d36631 fix(schema): close platform coverage gap and pin MCP loader policy
Rename policy loader assertions to loadPinnedMCPMetadata and add
minimal CrossPlatformCoverage tests for the remaining changed-code
statements that kept macOS/Windows gates below 100%.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 18:06:49 +08:00
玉澜andCursor f1eb1a44d1 fix(ci): restore coverage gates and dynamic race timeout contract
Pin admission race shards to timeout_budget (12m/cli 15m), cover
runtimeannotate and schema_source_root success paths for platform/main
gates, and make Windows absolute catalog path checks platform-safe.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 17:21:02 +08:00
玉澜andCursor 6c15b591a3 chore(schema): converge Embedded/Hints/provenance naming debt
Rename misleading public Embedded* loaders to Reviewed/Load APIs, keep
fail-closed HintsDir/-hints valves, and centralize wire provenance
string literals behind named consts without changing Catalog values.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 14:11:23 +08:00
玉澜andCursor d4f6aab04d chore(schema): rename remaining embeddedAgentMetadata fixtures
Finish Catalog/Agent-metadata naming debt so delivery and fixture symbols no
longer imply a retired go:embed Catalog or Hint overlay path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 13:59:37 +08:00
玉澜andCursor 9bf772ea06 chore(schema): rename Embedded/Hint leftovers to delivery/selection
Drop misleading Catalog-embed and HintFile naming now that assembly is
declare→delivery and selection comes from ContractFinal.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 13:47:26 +08:00
玉澜andCursor a086be422a chore(schema): remove throwaway catalogcodegen probe
Drop the compiled-literal feasibility probe and its generator; runtime
Catalog delivery is already single-track ResolveSchemaBuild only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 13:30:22 +08:00
玉澜andCursor 53816b3d33 fix(schema): drop retired Hint dead code and harden CI coverage shards
Remove manual_hints/Hint* leftovers after declare-or-annotate delivery, and fix macOS auth scoping plus Windows/.exe TestMain and race shard packaging so platform coverage gates stay reliable.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 13:21:15 +08:00
玉澜andCursor 0a2e49e7e4 fix(schema): separate catalog content hash from surface registry hash
Runtime assemble was stamping Snapshot.SourceHash with the registry
surface hash, so schema --all catalog_hash diverged from the CI dump
content source_hash and failed Policy. Also remap ContractFinal
Interface.Ref onto pinned MCP metadata so interface_type stays aligned.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 12:26:25 +08:00
玉澜andCursor ef39a1b8db fix(cli): drop go vet self-assignment in schema delivery cleanup
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 10:56:59 +08:00
玉澜andCursor 9eccc0c9e0 fix(schema): align registry policy with single-track Catalog assembly
Require param_aliases generate plus assembly determinism instead of a
committed cmd_schema_catalog go:generate path; gofmt and temp cleanup.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 10:49:41 +08:00
玉澜andCursor dc5c9fe110 docs(schema): align architecture with runtime ResolveSchemaBuild delivery
Drop residual go:embed catalog / committed-fixture wording so architecture
and the dynamic-endpoint design match declare→runtime assembly + Meta cache.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 10:41:09 +08:00
玉澜andCursor 3d163242f5 fix(schema): cache ResolveMeta from runtime assembly Once
Keep declare→ResolveSchemaBuild as the ToolSpec authority, but materialize
map[cli_path]CommandMeta during deliverySchemaCatalog sync.Once so leaf
--help / ResolveMeta are O(1) after the first Schema touch. Defer wire
Catalog/Tools maps, stamp Source as runtime-assembled, drop committed
catalog/gob fixtures, and cover steady-state reuse with app/cli tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 10:07:19 +08:00
玉澜andCursor 2a1fe47c50 refactor(schema): retire JSON Catalog delivery for runtime assembly
Move schema authority to declare-time ParamDecl/ContractFinal and
ResolveSchemaBuild so CI/runtime assemble instead of shipping JSON
exclusions/meta-index as delivery sources.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 09:08:17 +08:00
玉澜andCursor 410fb05498 fix(cmdcore): gofmt import order in tip contract tests
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 01:03:11 +08:00
玉澜andCursor 286a84edcd refactor(cmdcore): move annotate/ContractFinal store under corecmd
Break the remaining corecmd→cli reverse dependency by owning
runtimeannotate and contractfinal on the framework side, with cli
keeping thin re-exports. Document the three authoring tiers and that
Shortcut may use DeclareLeafMetadata.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 00:10:03 +08:00
玉澜andCursor a26957c425 docs(schema): align architecture homology with Catalog and declare-vs-delivery
Round-6 Medium docs only: drop retired agent-metadata JSON authority, document
seam packages, and pin Title/Description delivery rules. Also clarify
AttachContract godoc that description compares Long only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 22:59:41 +08:00
玉澜andCursor b8b2d9475e fix(schema): address #830 round-5 review docs and gofmt
Align ContractFinal godoc and AttachContract comments with the
contractfinal/runtimeannotate seams, clarify CHANGELOG that corecmd
still may import cli subpackages, and gofmt shortcut_test imports.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 22:30:08 +08:00
玉澜andCursor b7f04fd2a0 refactor(schema): break corecmd→cli cycle and split contract seams
Move AnnotateRuntime* into cli/runtimeannotate and the Cobra-keyed
ContractFinal store into cli/contractfinal so corecmd depends on thin
subpackages instead of the cli delivery root. Keep contract as DTO-only,
document Description declare-vs-delivery, and house homology gates under
cli/homology.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 22:16:17 +08:00
玉澜andCursor 7fedbea806 fix(schema): document dual delivery and lock Short out of description
Homology docs still said Catalog was the sole embed artifact; align with
meta-index ResolveMeta/help Safety, and add an assemble-path regression
so Short-only leaves keep declared description as contract_final.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 21:18:16 +08:00
玉澜andCursor 6cdd781ae7 fix(schema): gofmt contract_decl_test and align meta-index docs
Unblock CI Lint/gofmt on contract_decl_test, refresh design/CHANGELOG for
ContractDecl + schema_meta_index ResolveMeta delivery, and correct SafetyForCLIPath comments.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 21:07:52 +08:00
玉澜andCursor bb54761e50 perf(schema): add CommandMeta index so ResolveMeta skips full catalog decode
Publish a compact schema_meta_index.json beside the catalog so help/selection
lookups avoid decoding the full ToolSpec wire on the hot path.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 21:01:45 +08:00
玉澜andCursor 3e83ac18d1 docs(schema): align description provenance with Long-first assembly
Review Medium fixes: document Cobra Long → cobra_help over ContractDecl
description (title stays declared-first), add assembly regression tests,
and retire LeafSchema/Decl naming leftovers to ContractDecl + contract.*.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 20:51:33 +08:00
玉澜andCursor 75bd1f1447 refactor(cmdcore): rename SchemaDecl to ContractDecl and unify register seam
SchemaDecl confused authoring with Catalog/ToolSpec delivery. Authors now
declare ContractDecl (nested contract.* types) on Spec/LeafSpec/Shortcut;
AttachContract registers only through cli.RegisterRuntimeContractFinal, and
description provenance stamps cobra_help when Long wins.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 20:33:45 +08:00
玉澜andCursor a26d219b27 refactor(schema): remove cli contract aliases; single entry at corecmd/contract
Drop the dual-entry thin alias layer so helpers/shortcut/framework author
contract.* types directly; keep only AnnotateRuntime* delivery helpers in cli
and document the corecmd→cli seam.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 19:51:54 +08:00
玉澜andCursor 46af37669c refactor(schema): consolidate Schema contract assembly under corecmd
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 19:07:39 +08:00
玉澜andCursor c2e4a85ba9 feat(schema): remove Manual/Schema hint overlays; Catalog from ContractFinal only
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 18:54:09 +08:00
玉澜andCursor e29354ca70 feat(schema): retire schema_hints and gate Catalog on ContractFinal only
Remove schema_hints as a generation input so Catalog delivery depends solely on leaf ContractFinal and ProductDecl; migrate policy and contract tests to embedded catalog introspection and fix publicShortcutCount for chat-list.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 18:18:07 +08:00
玉澜andCursor 0119f6e2a8 feat(schema): declare product selection and remove selection JSON hints
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 17:16:11 +08:00
玉澜andCursor e98586ebb0 feat(schema): retire schema_agent_metadata JSON in favor of catalog-only delivery
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 14:51:13 +08:00
玉澜andCursor bd45de95ab feat(schema): finish declaration-framework migration and remove metadata hints
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 13:46:54 +08:00
玉澜andCursor 066094a68c fix(schema): restore ParamDecl mappings and drop messages-send RequiredWhen
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 12:00:47 +08:00
玉澜andCursor 31cade34ff feat(schema): finish ParamDecl migration for remaining overlays
Move the last hint parameter overlays into in-code ParamDecls (helpers +
shortcuts), regenerate catalog, and harden the migrate script for factory
and Use/RPC matching so all 74 overlay tools are declaration-backed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-01 09:43:21 +08:00
玉澜 e705012011 feat(schema): migrate parameter overlays into code declarations
Move 121/210 parameter-level Schema field overlays from
schema_hints/metadata JSON into in-code ParamDecl declarations on
DeclareLeafMetadata commands. The declared values are emitted as
dws.schema.* annotations at assembly time via ApplyParamDecls,
outranking tool_schema_hint (rank 620 > 500) so the hint overlay
becomes redundant once the declaration is in place.

Key mechanism changes:
- Add SchemaDecl.Parameters []ParamDecl with property/required/
  interface_type/description/required_when/enum fields
- Add cli.ParamDecl type carried inside ContractFinalPayload
- ApplyParamDecls emits dws.schema.* annotations from the payload
  at assembly time (no sync.Map, no tree-rebuild key issue)
- Add cli.AnnotateRuntimeFlagInterfaceType (41 overlays needed it)
- Add cli.AnnotateRuntimeFlagRequiredValue for explicit true/false
- Compatibility alias check tolerates dws.schema.* annotation
  differences between primary and alias commands
- Remove runtime source_hash recomputation (87ms/997k allocs saved;
  enforced by check-generated-drift.sh at build time instead)
- Add shortcut.Flag.RequiredWhen and wire through FromShortcut
- Add boolFlag OR semantics to fix confirmationBypass disagreement
- Add bindKey default kebab-to-camel for forgotten Bind
- Add schema consumption benchmarks (catalog decode 1.5s/817MB,
  shortcut load 1.9ms/3MB — three orders of magnitude apart)
- Add catalog codegen feasibility probe (34 tools: 0.06s compile,
  31ns access, 87KB linked — extrapolates to 2.1MB for 845 tools)

Migrated products (29 tools, 121 fields):
  aisearch(1), chat(12), contact(4), doc(3), drive(1),
  hrbrain(10), mail(2), report(1), sheet(3), todo(6)

Remaining 89 fields across 11 products blocked by:
  - RPCName not found as string literal (19 tools, shortcut/variable)
  - No matching DeclareLeafMetadata near callMCPTool (11 tools)
  - No single RPCName for multi-step commands (drive.upload etc.)

All tests green: corecmd, cli, helpers. Generation and drift clean.
2026-08-01 09:25:40 +08:00
玉澜 8791088027 test(app): pin dev safety expectations to the merge-base contract values
The fixture codified the migration's risk downgrade (high→medium) and the
publish re-classification (write→destructive); both were reverted to keep
the published Schema byte-stable, so the expectations follow the shipped
values (write tools stay high, publish stays write/high).
2026-07-31 23:18:11 +08:00
玉澜 746b7e403c fix(schema): restore merge-base contract parity for the corecmd migration
The ContractFinal assembly path dropped every non-declared parameter fact,
breaking the published Schema against the reviewed merge-base contract:

- merge pinned MCP parameter metadata and the reviewed in-code runtime hints
  back into contract_final parameter resolution (318 interface_type losses,
  calendar recurrence required/required_when regressions)
- restore devapp write risk to high and publish back to write/high; the
  migration silently downgraded 14 dev write tools and re-classified publish
  as destructive
- keep dev at-least-one checks as Validate hooks with the shipped wording
  instead of publishing new typed constraints; constraint publication is a
  contract change that belongs to its own reviewed PR (aitable annotations
  reverted for the same reason)
- align RunE escape hatch, BoolFlag shadowing, guard-first ConfirmFirst
  declaration, and Sheet target preflight with behavioral tests; drop the
  retired gen_schema_decls.py helper and fix corecmd naming in docs/CHANGELOG

check-authoritative-schema-compatibility vs origin/main: ok.
2026-07-31 22:15:45 +08:00
玉澜andCursor 86e34b5489 fix: gofmt aitable_schema_test.go so CI lint can proceed
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 19:28:57 +08:00
wxianfeng 029c665029 fix(event): place Unix bus sockets in temp dir 2026-07-31 18:01:34 +08:00
玉澜andCursor b64438d01c fix(corecmd): keep Validate and ConfirmSafety on the same RunE layer
PreRunE Validate was skipped by direct RunE / proxy calls. Run both hooks
in one wrapper (Validate first), add pat chmod Validate, let Sheet outer
guards call ContractValidate first, and assert declare user_required
leaves expose Validate, required flags, or CallTool-defer confirm.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 16:58:11 +08:00
玉澜andCursor 4943c6ff49 fix(corecmd): defer metadata ConfirmSafety until CallTool
Without Validate, DeclareLeafMetadata no longer confirms before RunE-local
required checks. Wrap deps.Caller so the first MCP CallTool runs
ConfirmSafety; Validate-backed leaves keep confirm-after-PreRunE.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 16:12:24 +08:00
玉澜andCursor f0fef85905 fix(corecmd): run metadata Validate before ConfirmSafety
DeclareLeafMetadata user_required wraps were confirming before RunE-local
checks, so illegal calls got confirmation_required instead of real errors.
Allow Validate on PreRunE, migrate event stop and drive publish checks, and
lock the Sheet dual-gate transitional state.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 15:46:48 +08:00
玉澜andCursor 7773eb27f1 refactor(corecmd): rename package and finish ContractFinal leaf migration
Move cmdcore to corecmd, declare attendance ContractFinal in helpers, keep
Sheet destructive commands --yes-only, and align catalog/policy provenance.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 14:13:46 +08:00
玉澜 18e32ad09d fix(cmdcore): unify runtime and schema safety 2026-07-31 11:56:57 +08:00
玉澜andCursor a9857d94d7 refactor(schema): migrate selection/metadata into ContractFinal decls
Compile reviewed Agent Schema into bind-time Go declarations so catalog
tools stamp contract_final without changing execution bodies.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 08:30:53 +08:00
玉澜 461455b4fa fix: harden destructive dry-run validation 2026-07-31 01:19:51 +08:00
玉澜 f0d558a0d1 refactor(shortcut): route live commands through cmdcore 2026-07-31 00:21:33 +08:00
玉澜 3cf690e779 Merge remote-tracking branch 'origin/main' into agent/cmdcore-phase2
# Conflicts:
#	CHANGELOG.md
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog/catalog.json
2026-07-30 23:43:21 +08:00
玉澜 340f01e95c refactor(cmdcore): align leaf safety with schema contract 2026-07-30 23:03:32 +08:00
github-actions[bot] 187787040b chore: update beta formula for v1.0.56-beta.2 [skip ci] 2026-07-30 15:00:34 +00:00
chichuan cd6e854bf1 Merge pull request #843 from DingTalk-Real-AI/codex/changelog-v1.0.56-beta.2-admission-final
docs(release): clarify v1.0.56-beta.2 skill routing
2026-07-30 22:48:59 +08:00
chichuan 61124f8768 docs(release): clarify v1.0.56-beta.2 skill routing 2026-07-30 22:44:52 +08:00
github-actions[bot] 6cfeac3179 Merge pull request #842 from DingTalk-Real-AI/codex/changelog-v1.0.56-beta.2-admission
docs(release): complete v1.0.56-beta.2 notes
2026-07-30 22:43:08 +08:00
chichuan acd293cc83 docs(release): complete v1.0.56-beta.2 notes 2026-07-30 22:40:59 +08:00
github-actions[bot] d2045c3441 Merge pull request #841 from DingTalk-Real-AI/codex/changelog-v1.0.56-beta.2
docs(release): seal v1.0.56-beta.2 changelog
2026-07-30 22:38:42 +08:00
chichuan 4de41c27c7 docs(release): add v1.0.56-beta.2 notes 2026-07-30 22:36:34 +08:00
github-actions[bot] 5df2860e66 Merge pull request #831 from wxianfeng/fix/agent-product-header-separation
fix: separate Agent Product from claw-type
2026-07-30 14:35:55 +00:00
chichuan f3390b6875 Merge branch 'main' into fix/agent-product-header-separation 2026-07-30 22:16:13 +08:00
玉澜andCursor 621229fca2 test(app): align example dry-run classifier and devapp safety gate with declared contracts
- manualAgentExampleDryRunEvidence now recognizes executor invocation
  envelopes ("kind": "*_invocation" / connect_preview with dry_run) as
  invocation previews before the generic request branch, so the 32 devapp
  declared tools match their declared preview_kind; pinned with a unit test
  covering all invocation kinds plus request/plan precedence.
- TestDevAppWriteGuardRequiresFinalSchemaConfirmation updates devapp wants
  to the declared risk grading (reversible writes medium; create/version
  create/robot submit high-write; delete/publish destructive) and accepts
  contract_final provenance for declared tools while hints-fed tools keep
  reviewed_explicit.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 22:08:48 +08:00
github-actions[bot] 55f25d8d48 Merge pull request #835 from DingTalk-Real-AI/codex/skill-token-shallow-water
perf(skills): reduce common-path context loading
2026-07-30 14:04:51 +00:00
johnandClaude Opus 4.6 acfbd35aa2 docs: add command framework comparison (DWS vs lark-cli vs GWS)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-30 21:56:52 +08:00
chichuan 67fbf65916 Merge branch 'main' into fix/agent-product-header-separation 2026-07-30 21:54:11 +08:00
chichuan 7f82e46adf Merge branch 'main' into codex/skill-token-shallow-water 2026-07-30 21:52:17 +08:00
chichuan 1fb1ae3e23 Merge remote-tracking branch 'origin/main' into codex/pr-831-conflict-fix
# Conflicts:
#	CHANGELOG.md
2026-07-30 21:47:14 +08:00
github-actions[bot] fd0ab16c7f chore: update beta formula for v1.0.56-beta.1 [skip ci] 2026-07-30 13:46:57 +00:00
johnandClaude Opus 4.6 95a5fd069a docs: add command framework architecture and domain model
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-30 21:42:52 +08:00
d6292d92d3 feat(cmdcore): suppress interactive prompt off-terminal, document agent protocol
Align the write-confirmation UX with lark-cli: ConfirmRisk (and the
shortcut confirmRisk) now print the yes/no prompt only when stdin is a
real terminal (ioctl-level check via go-isatty; a char-device stat would
misclassify `< /dev/null`). Non-interactive callers get a clean
structured confirmation_required error on stderr. Piped answers are
still honored for humans/scripts; --yes/--dry-run remain the sanctioned
non-interactive paths.

skills/mono: add the recognition + retry protocol for agents —
identify confirmation_required via error.reason, show action and params,
retry the original command with --yes only after explicit user consent,
never silently append --yes or treat it as a transient error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 21:42:19 +08:00
chichuan daaad35f5b Merge pull request #840 from DingTalk-Real-AI/codex/changelog-v1.0.56-beta.1-followup
docs(release): complete v1.0.56-beta.1 notes
2026-07-30 21:33:31 +08:00
chichuan 953a36f4c9 docs(release): complete v1.0.56-beta.1 notes 2026-07-30 21:30:31 +08:00
github-actions[bot] e015f40ae2 Merge pull request #836 from DingTalk-Real-AI/codex/changelog-v1.0.56-beta.1
docs(release): add v1.0.56-beta.1 notes
2026-07-30 21:27:07 +08:00
afb9c27560 style: gofmt the three PR files failing the lint format gate
Alignment-only changes in runtime_schema.go, schema_contract_model.go,
and devapp_safety_homology_test.go. Remaining make lint findings are in
upstream-owned keychain/transport files untouched by this PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 21:22:48 +08:00
chichuan 84226b963c Merge branch 'main' into codex/changelog-v1.0.56-beta.1 2026-07-30 21:22:16 +08:00
chichuan 1d1c06aaae Merge branch 'main' into fix/agent-product-header-separation 2026-07-30 21:17:23 +08:00
github-actions[bot] f34f9e8223 Merge pull request #839 from DingTalk-Real-AI/codex/fix-multi-profile-e2e-timeout
ci: increase integration test timeouts
2026-07-30 21:14:44 +08:00
08c9c8a923 fix(cmdcore): close review findings on safety tier inference
- schemaSafetyFromDecl: drop the now-unreachable nil return; the tier
  fill always produces a complete block for a declared Schema
- validateDispatchDecl: panic when ConfirmFirst is set without Risk —
  it orders a confirmation that does not exist, and for declared-Schema
  writes an empty Risk would silently publish the read safety tier
- RFC: document the boundary that write commands must declare Risk

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 21:13:29 +08:00
johnandClaude Opus 4.6 f805c966d6 docs: add command framework architecture overview
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-30 21:02:55 +08:00
chichuan 3519965285 ci: increase integration test timeouts 2026-07-30 20:52:18 +08:00
eae7955448 chore(schema): regenerate artifacts after rebase onto latest main
Upstream added five reviewed tools (845 total); hashes and counts
refresh. Content of existing tools is unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:38:53 +08:00
ce0501b93e refactor(cmdcore): split Safety into its own enum tier with Risk default
Risk (runtime confirmation) and Safety (schema metadata) are two
independent enums composed at embed time: explicit SafetyDecl fields >
CommandSpec.Safety tier > Risk.SafetyDefault(). The tier fill now also
covers idempotency, so an enum-only declaration is self-sufficient and
validateSchemaDecl no longer needs safety completeness checks.

devapp reclassifies its write leaves by reversibility: reversible
mutations declare LeafSafetyWrite (risk high->medium), create/robot
submit/version create declare LeafSafetyHighWrite, and delete/version
publish declare LeafSafetyDestructive (publish effect
write->destructive). Shared hand-written safety constants are deleted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:37:40 +08:00
玉澜andCursor da93fbcc47 fix(cmdcore): close review findings on decl completeness and dry-run indexing
- validateSchemaDecl now also requires Safety (effect/risk/confirmation
  or the Risk shorthand; Idempotency is declaration-only) and Interface
  (mode/availability, plus reason for composite/unavailable), so every
  unconditional catalog required key is guaranteed at construction time
- declared dry_run capabilities are indexed by BindEffectiveCommandRegistry
  instead of Schema assembly: every process resolving the command tree
  gets the reviewed set, removing the hidden "must assemble in-process
  first" precondition of the delivery gate
- agent-metadata contract merge now errors when a declared tool has no
  canonical CLI projection instead of silently dropping the declaration

Artifacts are byte-identical; full cli/cmdcore/helpers/generator suites pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:37:38 +08:00
玉澜andCursor 92f1daa95f feat(cmdcore): single-source dry-run review and authoring-time decl checks
- dry_run capabilities declared via cmdcore.SchemaDecl are reviewed by
  construction: the Schema pass-through indexes them into the reviewed
  capability set, so declared tools no longer need manual entries in
  reviewedDryRunCapabilityGroups (31 devapp paths deleted). A conflicting
  manual entry for the same canonical is a hard error.
- NewCommand now enforces authoring-time homology for declared commands:
  a non-empty Schema without Description/AgentSummary/UseWhen/AvoidWhen/
  Examples panics at construction instead of failing later in generated
  artifacts or silently drifting from cobra prose.
- --help Example inherits Schema.Selection.Examples when not authored
  separately, keeping one authored source for examples.

Catalog and agent metadata artifacts are byte-identical.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:37:36 +08:00
玉澜andCursor 9911d8f9c9 feat(schema): consume Contract declarations in agent-metadata, drop devapp hints
The agent-metadata generator now merges each registered Contract final
overlay (cmdcore.SchemaDecl) as the top-precedence contract_final
candidate, so declared tools no longer need hint-file rows for
agent_summary/use_when/avoid_when/examples/safety/interface. Selection
eval fixtures and example execution plans synthesize the same assertions
from the declaration, keeping semantic-eval and example coverage intact.

- devapp hint rows deleted from schema_hints/{metadata,selection}/dev.json
  (connect_status/connect_stop/search_open_platform_docs_rag kept);
  artifact content for all 31 declared leaves is byte-identical, only
  provenance now reads contract_final / cmdcore.SchemaDecl
- exact-coverage gates exempt declared tools (hints remain required for
  every non-declared command); reviewed-delivery gate accepts
  contract_final as the stronger reviewed source
- cmdcore derives effect_source=cmdcore.contract for SchemaDecl-only
  safety (read leaves), matching the Risk-shorthand path

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:37:34 +08:00
玉澜andCursor 52324e9bc2 feat(devapp): declare complete Schema for all 31 published leaves
Every devapp leaf now declares its full final Schema in LeafSpec
(description/safety/interface/selection/dry_run plus Risk, Required
flags and at-least-one Constraints); declaration is the sole final
source, hints no longer shape the published catalog for these tools.

- Hand-written delete/robot submit/robot config migrate to
  LeafSpec+RunE with manual cmdcore.ConfirmRisk; robot result becomes
  a plain declared leaf. Legacy write guard, runtime_gate annotation
  and now-dead helpers are removed; the homology gate is strengthened
  to declare-only for the devapp tree.
- New CommandSpec/LeafSpec ConfirmFirst knob reproduces the devapp
  guard-first semantics (confirmation_required before parameter
  validation) without changing shortcut ordering.
- dry_run is published for all 31 leaves via the reviewed capability
  registry (invocation preview, no remote reads).
- Catalog regenerated: dry_run blocks added, unified-app-id/
  version-id/member-type/user-ids correctly marked required,
  require_one_of constraints published for get/webapp config/security
  config, and robot config name corrected to optional (CLI upsert
  runtime truth; the remote schema's required was not CLI-accurate).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:37:06 +08:00
玉澜andCursor a80ac662f5 chore(schema): regenerate catalog for contract attribution fixes
Rebaseline after the declare-or-annotate framework work: confirmation and
parameter description provenance now cite cmdcore.contract (runtime_gate /
native_annotation) instead of hints; delivered values unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:37:02 +08:00
玉澜andCursor a5cefd67f3 feat(cmdcore): typed SchemaDecl final source with assembly guards
- SchemaDecl on CommandSpec/LeafSpec declares the final ToolSpec payload;
  framework converts in-process (no JSON bridge) and Schema assembly
  pass-throughs it.
- Assembly fails closed on declared identity mismatched with the bound
  entry and on reviewed fields in the declaration payload.
- RFC/homology/AGENTS docs pin declare=final-source, safety precedence
  Final > Risk > gate, and light runtime write semantics.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:36:33 +08:00
玉澜andCursor 8c9c22f4b0 feat(cmdcore): declare-or-annotate homology with full ToolSpec authority
Pin path A: Contract fields declare CLI surface; write-guard uses runtime_gate;
RFC §5.0/§5.0.4 covers every Schema ToolSpec field group so none are ownerless.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:36:31 +08:00
玉澜andCursor 37a05db6e9 fix(shortcut): fail closed when write confirm has no stdin
Treat EOF/closed stdin as confirmation_required instead of an
interactive decline so agent/CI no longer get exit 0 for writes that
never ran. Align cmdcore.ConfirmRisk the same way.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:36:28 +08:00
玉澜andCursor 41fdf79aea refactor(leaf): declare params on LeafSpec; keep Call as execution
Lift business flags/const params out of Call/PostMount, add typed
flag defaults and policy gates, and realign the RFC acceptance bar to
"no Execute/Call body exists only to assemble params".

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 20:32:08 +08:00
玉澜 ceca98c573 refactor(cmdcore): layer dispatch into Invoke/Orchestrate behind a shared Ctx
The single Dispatch hook assumed every command is one MCP call, so the
Shortcut projection could only ever describe a command, never run it. Split
dispatch into Invoke (assembled toolArgs) and Orchestrate (multi-step), keep
RunE as the escape hatch, and reject specs that declare anything other than
exactly one at construction time. Ctx gives both hooks the same typed flag
accessors so orchestration no longer reaches for framework-specific plumbing.

Catalog output stays byte-identical.
2026-07-30 20:32:04 +08:00
玉澜 c84a42b0d5 fix(cmdcore): address review findings in the Phase 2 additions
Adversarial review confirmed the Phase 1 extraction is a verbatim move (no BLOCKERs) and that cmdcore.BoolFlag is equivalent to both original readers. All fixes below are in the Phase 2 additions.

Correctness: a CommandSpec declaring neither RunE nor Dispatch no longer runs the whole pipeline (write-confirmation prompt included) and silently exits 0 — it now fails with a typed internal error, which also defuses the FromShortcut trap. FromShortcut no longer double-renders the 参数约束 section (shortcutLongHelp already appends it and NewCommand appends ConstraintHelp again): it now maps intent prose only. Flag usage keeps mount()'s flagHelp decoration (必填/可选值) so projected help matches the live shortcut, and the constraint Flags slice is copied instead of aliasing the shortcut registry.

Honesty: the FromShortcut doc block now lists every dropped semantic (Required Changed-vs-effective-value divergence, typed bool/int/slice defaults, Enum, Hidden, Tips to Example, custom constraint, required/enum runtime-schema annotations). cmdcore's package doc no longer claims catalog drift proves runtime behavior — drift covers the build-time projection, unit tests cover the runtime pipeline. leaf.go's stale Phase 1 header updated. Panic messages say command not leaf; doc comments lead with the exported names.

Tests: new mount-equivalence test compares the projected command's flag set/types/usage and rendered Long against live mount(s) — the test that would have caught both bugs above; new root-to-child test exercises the inherited/root-persistent --yes/--dry-run lookup that the leaf-local helper never reached; the nil-dispatch test is inverted to assert the error. docs/architecture.md documents internal/cmdcore and how it differs from internal/cobracmd.

Verified: drift ok (840 tools, identical hashes), make policy pass, skill-command-integrity ok (1033 paths), cmdcore self-coverage 100%, CI-style changed-code coverage 100% (278 statements), full helpers/cmdcore/shortcut suites green.
2026-07-30 20:32:00 +08:00
玉澜 fb7696293d test(cmdcore): give the shared base its own exhaustive test suite
CI coverage jobs run go test -coverprofile WITHOUT -coverpkg, so each package is measured only by its own tests. cmdcore's logic was exercised only indirectly from internal/helpers, leaving cmdcore self-coverage at 31.5% — failing the CI coverage gate (changed-code 40.5%, overall regression 90.79% to 90.55%) even though the cross-package platform gate reported 100%.

Add direct tests for every cmdcore primitive: flag registration for all four kinds plus hidden aliases/MarkRequired, the explicit-alias-env-default fallback chain incl. Trim/empty skips, integer and slice resolution, required validation, toolArgs assembly incl. Bind/ArgDefault/OmitEmpty/Transform (value, nil-skip, error), constraint declaration panics, constraintProvided (default-not-counted, alias, env, bool, slice main+alias), all three constraint kinds with exact error wording, Risk confirmation (read/--yes/--dry-run/accept/decline), BoolFlag (nil/missing/local/root), schema projection, constraint help, and NewCommand orchestration (order, RunE escape, per-stage abort, decline-cancels, nil dispatch).

cmdcore self-coverage 31.5% to 100%; CI-style changed-code coverage 100%.
2026-07-30 20:31:57 +08:00
玉澜 93d6e1a001 feat(cmdcore): unified CommandSpec + FromLeafSpec/FromShortcut adapters (Phase 2)
Introduce cmdcore.CommandSpec as the single typed leaf definition and
cmdcore.NewCommand as the one orchestration path (flags → constraint decl
checks → Runtime Schema projection → constraint help → PostMount → RunE
escape / generated RunE{required → constraints → Validate → BuildArgs →
ConfirmRisk → Dispatch}). Dispatch becomes a spec property, not a
separate framework.

helpers.NewLeafCommand now delegates to cmdcore.NewCommand(FromLeafSpec),
so every LeafSpec command — including all 27 devapp leaves — flows through
the unified spec. The MCP dispatch (Call / callMCPToolOnServer /
callMCPTool) is captured in the FromLeafSpec closure.

internal/shortcut/adapter.go adds FromShortcut, the typed seam mapping a
Shortcut's shared base (flags of every kind, known constraints, risk,
help identity) into a CommandSpec. It is intentionally NOT wired into the
live mount() path: Shortcut's multi-step Execute, decline-returns-nil
semantics, and Flag.Enum/Hidden/custom-constraint extras are not modeled
by cmdcore yet, so the 376 shipped shortcuts stay byte-identical. Live
wiring is deferred to Phase 3, gated by shortcut-list + schema equivalence.

Commands are provably unaffected: check-generated-drift ok (840 tools,
identical hashes), `dws schema --all` and `shortcut list` unchanged, full
helpers/cmdcore/shortcut suites green, changed-code coverage 100%.
2026-07-30 20:31:54 +08:00
玉澜 4d4817d74f refactor(cmdcore): extract shared leaf base, LeafSpec delegates to it
Phase 1 of converging the command frameworks onto one typed base. Extract
LeafSpec's flag registration, alias/env/default effective-value fallback,
required validation, cross-flag constraint declaration checks + runtime
enforcement, Risk-driven write confirmation (--dry-run/--yes/global-flag
aware via a 3-level bool lookup), toolArgs assembly, and Agent Runtime
Schema projection into a new dispatch-agnostic internal/cmdcore package.

internal/helpers/leaf.go now keeps only the LeafSpec shell (with MCP
dispatch fields) and NewLeafCommand orchestration; LeafFlag/LeafFlagKind/
LeafConstraint/LeafConstraintKind/LeafRisk and their constants become
aliases to cmdcore types, so all 27 devapp call sites compile unchanged.
Dispatch (callMCPTool/OnServer/Call) stays in helpers.

Pure extraction, zero behavior change: catalog is byte-identical
(check-generated-drift ok), the leaf unit + risk/constraint tests pass,
and changed-code coverage is 100% (224 statements across both packages).
Only the leaf framework code is touched; Shortcut delegation is deferred
to Phase 2/3.
2026-07-30 20:31:50 +08:00
玉澜 110780bf74 feat(leaf): add Risk-driven write confirmation to LeafSpec
Close the last capability gap versus the shortcut framework: LeafSpec now
carries a Risk field (read / write / high-risk-write) and enforces the
same pre-dispatch write confirmation as shortcut's confirmRisk. Read (and
empty) risk never prompts; write/high-risk-write prompt unless --yes or
--dry-run, cancelling without dispatch on decline. Prompt wording matches
the shortcut runner verbatim (command path substitutes Service+Command)
so atomic commands and smart shortcuts confirm identically. --yes is read
robustly across local/inherited/root-persistent flags.
2026-07-30 20:31:47 +08:00
玉澜 26b100c6bb feat(leaf): unify LeafSpec with declarative constraints and bool/slice kinds
Converge the atomic LeafSpec framework toward the shortcut framework's
constraint system so both share one flag-registration + validation base,
differing only in dispatch path (single-step MCP vs multi-step
orchestration).

- Add LeafBool / LeafStringSlice flag kinds (registration, effective-value
  detection, required semantics, toolArgs assembly: bool delivers on
  Changed incl. explicit false; slice trims and drops empty elements).
- Add LeafConstraint (at_least_one / exactly_one / mutually_exclusive) on
  LeafSpec. The framework validates them between required checks and the
  Validate hook, with error wording identical to the shortcut runner's
  RuntimeContext validators; "provided" reuses LeafSpec's alias/env
  fallback chain (registration defaults do not count), which the
  shortcut framework's bare Changed check lacks.
- Project constraints to the Agent Runtime Schema (exactly_one =
  require_one_of + mutually_exclusive) and render a 参数约束 help section,
  matching shortcut leaf help. Declaration errors panic at build time.
2026-07-30 20:31:44 +08:00
chichuan a54ee24acb Merge branch 'main' into fix/agent-product-header-separation 2026-07-30 20:21:34 +08:00
chichuan a7074bf53f Merge pull request #838 from DingTalk-Real-AI/codex/fix-scoped-coverage-timeout
fix: align scoped coverage and test timeout
2026-07-30 20:20:01 +08:00
chichuan 21144af79b fix: align scoped coverage and test timeout 2026-07-30 20:06:24 +08:00
chichuan 320582f98c Merge branch 'main' into fix/agent-product-header-separation 2026-07-30 19:04:13 +08:00
Dennis e04ff5a12b Merge remote-tracking branch 'origin/main' into codex/skill-token-shallow-water
# Conflicts:
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog/catalog.json
2026-07-30 17:39:06 +08:00
github-actions[bot] 3bdc30badb Merge pull request #834 from wxianfeng/fix/event-subscription-retry-storm
fix(event): prevent subscription retry storms
2026-07-30 17:18:27 +08:00
wxianfeng c569def067 docs: clarify disabled AI tag argument shape 2026-07-30 16:55:46 +08:00
wxianfeng b82e975429 test(app): preserve audit sink ownership in coverage gate 2026-07-30 16:25:56 +08:00
wxianfeng 2808e71cb6 fix(event): address retry storm review 2026-07-30 16:08:19 +08:00
chichuan 584b1bd9d4 docs(release): add v1.0.56-beta.1 notes 2026-07-30 15:42:05 +08:00
Dennis c350311048 chore: keep analysis report out of PR 2026-07-30 15:20:51 +08:00
Dennis 158e7ec701 perf(skills): reduce common-path context loading 2026-07-30 15:17:48 +08:00
wxianfeng 125a101487 fix: separate Agent Product from claw-type 2026-07-30 14:34:22 +08:00
wxianfeng ec99654854 fix(event): prevent subscription retry storms 2026-07-30 13:49:08 +08:00
github-actions[bot] 9aa76ea748 Merge pull request #806 from DingTalk-Real-AI/fix/param-hallucination
feat(param): 参数概念归一化治理与 IM 场景完善
2026-07-30 04:00:22 +00:00
克谨 885c3fe021 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-07-30 11:46:37 +08:00
github-actions[bot] d0d56cbaf5 Merge pull request #817 from DingTalk-Real-AI/codex/im-shortcut-gap-fill
feat(im): close shortcut capability gaps
2026-07-30 11:42:23 +08:00
chichuan 9dbbd64f3c Merge branch 'main' into codex/im-shortcut-gap-fill 2026-07-30 11:31:19 +08:00
github-actions[bot] 7ba12a8e4c chore: update formula for v1.0.55 [skip ci] 2026-07-30 03:11:41 +00:00
克谨 dfba9546f4 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-07-30 11:06:02 +08:00
chichuan 82d02096f7 Merge pull request #833 from DingTalk-Real-AI/codex/changelog-v1.0.55-promote-beta.8
docs(release): promote v1.0.55-beta.8 baseline
2026-07-30 11:00:51 +08:00
克谨 b3ba9fee97 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-07-30 10:43:51 +08:00
Dennis 41372b0597 Merge remote-tracking branch 'origin/main' into codex/im-shortcut-gap-fill 2026-07-30 10:32:58 +08:00
chichuan ad08b6b499 docs(release): promote v1.0.55-beta.8 2026-07-30 10:32:20 +08:00
Dennis cffc48406c fix(im): resolve direct recipients via contact search 2026-07-30 10:29:39 +08:00
github-actions[bot] 250aab3ef1 chore: update beta formula for v1.0.55-beta.8 [skip ci] 2026-07-30 02:28:33 +00:00
chichuan e36b6dc049 Merge pull request #832 from DingTalk-Real-AI/codex/changelog-v1.0.55-beta.8
docs(release): add v1.0.55-beta.8 notes
2026-07-30 10:19:15 +08:00
Dennis f9e3476d42 Merge remote-tracking branch 'origin/main' into codex/im-shortcut-gap-fill 2026-07-30 10:02:34 +08:00
chichuan d9d62fb2f7 docs(release): add v1.0.55-beta.8 notes 2026-07-30 09:55:16 +08:00
克谨 1e04e301ea Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-07-30 09:44:52 +08:00
克谨 5f038d440b test: reduce parameter alias race runtime 2026-07-30 09:44:30 +08:00
github-actions[bot] f9f61a4cc6 Merge pull request #825 from DingTalk-Real-AI/codex/changelog-v1.0.55
docs(release): add v1.0.55 stable notes
2026-07-30 09:39:02 +08:00
Dennis 2b48f27a4b fix(im): harden shortcut review follow-ups 2026-07-29 23:35:52 +08:00
炳昱 703406df13 feat(event): publish typed OA approval schemas 2026-07-29 22:23:13 +08:00
Dennis bf79a67efe fix(im): close shortcut review gaps 2026-07-29 21:25:24 +08:00
chichuan 8d22cd553a docs(release): add v1.0.55 stable notes 2026-07-29 20:42:19 +08:00
克谨 8936c20ef0 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-07-29 20:07:03 +08:00
Dennis 95d262bbb2 Merge remote-tracking branch 'origin/main' into codex/im-shortcut-gap-fill 2026-07-29 19:32:18 +08:00
Dennis d5c260c7c0 fix(im): address shortcut review regressions 2026-07-29 19:31:48 +08:00
github-actions[bot] 4f31863aae chore: update beta formula for v1.0.55-beta.7 [skip ci] 2026-07-29 10:19:40 +00:00
chichuan 6de2bf1518 docs(release): 合入 beta.7 发布说明(风险等级:低)
发布模块:CHANGELOG。补充 v1.0.55-beta.7 的完整变更说明,并保留失败 beta.6 的审计记录。风险等级:低。
2026-07-29 18:09:24 +08:00
Dennis 9c297d0520 Merge remote-tracking branch 'origin/main' into codex/im-shortcut-gap-fill 2026-07-29 18:02:49 +08:00
chichuan 78b724480e docs(release): 补充 beta.7 完整发布说明(风险等级:低) 2026-07-29 18:02:40 +08:00
Dennis 37230d2d4d chore(schema): refresh shortcut skill source hashes 2026-07-29 18:01:54 +08:00
github-actions[bot] 4724c30f4b Merge pull request #821 from typefield/agent/restore-shared-account-rule
fix(skills): restore multi-account safety rule in dws-shared SKILL.md
2026-07-29 17:56:16 +08:00
Dennis fde6b59074 Merge remote-tracking branch 'origin/main' into codex/im-shortcut-gap-fill
# Conflicts:
#	internal/app/schema_shortcut_contract_test.go
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog/catalog.json
#	internal/cli/schema_command_registry/products/chat.json
#	internal/cli/schema_hints/runtime-surface-completeness.json
#	skills/multi/dingtalk-chat/SKILL.md
#	skills/multi/dingtalk-chat/references/chat.md
2026-07-29 17:51:27 +08:00
玉澜 76b9f1536a test(app): pin multi-account safety rule in embedded dws-shared skill
Replace the CI classifier change with a real PR-level regression
contract: materialize the embedded multi skill source and assert
dws-shared/SKILL.md keeps the 禁止选择第一项、最近登录或最近使用账号 rule
that the MultiSkill e2e release gate requires. The new test file also
makes the revision full-suite so all quality gates run on this PR.
2026-07-29 17:42:04 +08:00
玉澜 809b9b3570 ci: classify skills/ changes as docs-only for fast path
Skill markdown files are agent documentation embedded at build time;
they carry no Go code changes. Without this classification a one-line
SKILL.md edit triggers the full -race test suite on internal/app and
reverse dependencies, which exceeds the 8m job timeout and fails CI
deterministically.
2026-07-29 17:36:56 +08:00
克谨 e2abc70e84 Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-07-29 17:35:07 +08:00
克谨 15a27a9f83 fix(cli): harden parameter preparse normalization 2026-07-29 17:33:44 +08:00
玉澜 0be5b73518 fix(skills): restore multi-account safety rule in dws-shared SKILL.md
Commit dc20ddec dropped the 禁止选择第一项、最近登录或最近使用账号 rule
from dws-shared/SKILL.md during the multi-skill refactor while the
MultiSkill e2e contract still asserts it there, blocking the
v1.0.55-beta.6 release run. Restore the rule as a mandatory-contract
bullet pointing at dingtalk-profile/SKILL.md for the full selection and
cross-org rules.
2026-07-29 17:19:31 +08:00
chichuan a637a44b7a docs(release): 恢复 beta.6 main admission(风险等级:低)
明确 beta.6 五个 PR 审计范围,并由真实用户合入以触发 main CHANGELOG fast-path CI。
2026-07-29 16:52:33 +08:00
github-actions[bot] 579eed81d9 Merge pull request #818 from DingTalk-Real-AI/codex/changelog-v1.0.55-beta.6
docs(release): prepare v1.0.55-beta.6 changelog
2026-07-29 16:38:54 +08:00
chichuan c68d9facb2 docs(release): 补充 CHANGELOG beta.6 五项合入说明(风险等级:低) 2026-07-29 16:33:48 +08:00
github-actions[bot] 1f9138e99a Merge pull request #621 from typefield/agent/sync-wukong-multi-skill
feat(skills): add  multi-skill framework to DWS
2026-07-29 16:24:53 +08:00
玉澜 c3fd814630 Merge remote-tracking branch 'origin/main' into pr621-wukong-sync
# Conflicts:
#	CHANGELOG.md
2026-07-29 16:08:16 +08:00
github-actions[bot] 5922a0717a Merge pull request #816 from wxianfeng/feature/aone82250541-agent-product
feat: support configurable Agent Product identity
2026-07-29 16:04:24 +08:00
玉澜 c5bc1fdad4 Merge remote-tracking branch 'typefield/agent/sync-wukong-multi-skill' into pr621-wukong-sync
# Conflicts:
#	CHANGELOG.md
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog/catalog.json
#	internal/cli/schema_parameter_bindings.json
2026-07-29 15:51:04 +08:00
玉澜 9567cfd3d8 fix(review): align wukong port with upstream behavior and PR #621 review findings
Must-fix: drive permission apply now gates on confirmDangerousAction and
declares confirmation=user_required, matching its help-text promise.

Wukong parity restored: formula-verify --exit-on-error (payload-parsing
exit path) and --targets conflict error, sheet info --include, chat
location/profile message types, search-advanced wukong flag aliases,
and a dedicated drive download-version leaf replacing the removed
polymorphic download --version.

Consistency fixes: transfer-owner --node/--workspace XOR and JSON-aware
dry-run after --yes validation; drive list --versions rejects
--depth/--pattern instead of misleading depth errors; depth BFS resumes
rate-limited folders from the failed page cursor to avoid duplicates;
doc style cover upload honors cmd.Context() and a 20 MiB size cap; chat
user-settings set validates per-item openConversationId and is
risk=medium.

Hardened the skill static audit to scan fenced code blocks and reject
unknown subcommands on group commands, fixing the stale aitable/drive
doc examples it exposed. Added CHANGELOG entry and coverage tests for
all changed statements plus previously untested ported commands.
2026-07-29 15:34:59 +08:00
wxianfeng 4567dd1cd6 fix(im): gate optional resource downloads at runtime 2026-07-29 15:33:01 +08:00
chichuan 1180510f40 merge(agent-product): 同步 main 并解决 CHANGELOG 冲突(风险等级:高)
保留 #816 的 Agent Product 身份说明与 main 中已合入的 Shortcut 修复条目,并完成全仓测试、构建及 Schema 生成漂移校验。
2026-07-29 15:19:28 +08:00
Dennis 75bb01bb64 docs(skill): align IM shortcut routing 2026-07-29 14:45:28 +08:00
chichuan 2456660780 test(chat): 补齐文字表情跨平台覆盖(风险:低)
让 update-text-emotion 映射与缺参测试进入 Darwin/Windows coverage 矩阵,并移除已由 Cobra 必填门禁覆盖的不可达重复校验。
2026-07-29 14:36:41 +08:00
Dennis 11a7ab8b7c fix(im): harden shortcut downloads and message context 2026-07-29 14:20:39 +08:00
chichuan c3dbe866c4 feat(chat): 补齐文字表情原地更新契约(风险:低)
基于 PR #621 现有 update-text-emotion 实现,补齐七参数 RPC 映射、Cobra/Schema 必填约束、mono Skill、CHANGELOG 与别名/缺参回归测试。
2026-07-29 14:17:03 +08:00
wxianfeng 81f130c483 fix: address agent product review feedback 2026-07-29 13:56:25 +08:00
玉澜 6b99685594 fix(schema): bump runtime-surface completeness source_tools to 839
The 26 newly registered commands raised the registry count to 839, but
runtime-surface-completeness.json still declared source_tools=813, so
check-schema-catalog.sh failed the Policy job ("runtime-surface
completeness source must remain unreviewed and interface-free"). The 26
tools are all reviewed in metadata/selection sources, so the unreviewed
71-tool list is unchanged; regenerate dependent schema artifacts.
2026-07-29 13:51:25 +08:00
克谨 2e1cce8501 test(param): align category alias fixtures with title limits 2026-07-29 13:34:59 +08:00
Dennis 41e0fb381a feat(im): close shortcut capability gaps 2026-07-29 13:29:53 +08:00
玉澜 9d59550890 test(helpers): cover new drive/doc-style/sheet/chat commands to 100% changed-code coverage
The CI platform coverage gate enforces 100% coverage of changed
statements via tests named TestCrossPlatformCoverage*/TestAllShortcuts.
Add unit tests for drive list --depth BFS (pagination, rate-limit retry,
dedup, truncation, SIGINT, anomalies), drive list --versions/transfer-
owner/cover/revert paths, doc style cover upload flow, sheet
formula-verify target parsing, and chat group user-settings validation.
Also drop an unreachable resourceID guard in uploadDocStyleImage.
2026-07-29 13:29:22 +08:00
克谨 870fba823b Merge remote-tracking branch 'origin/main' into fix/param-hallucination 2026-07-29 13:18:47 +08:00
克谨 d083de5f84 fix(cli): normalize explicit boolean flag values safely 2026-07-29 13:18:27 +08:00
克谨 1fb966dbff fix(cli): centralize parameter alias generation entrypoint 2026-07-29 13:17:55 +08:00
玉澜 83f13d8e11 Merge remote-tracking branch 'origin/main' into pr621-wukong-sync
# Conflicts:
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog/catalog.json
2026-07-29 12:25:45 +08:00
wxianfeng 86bce64cc8 test: cover agent product header branches 2026-07-29 12:06:43 +08:00
玉澜 68e1a78810 feat(cli): port drive list --depth and doc style, register all new commands in Schema
- Port drive list --depth N BFS recursive listing (pan + workspace routes,
  rate-limit requeue, SIGINT partial emit, --pattern/--quiet)
- Port doc style cover set/clear, background set/clear, get with local
  image validation and attachment-upload subflow
- Register all 26 newly ported commands in schema_command_registry with
  reviewed metadata/selection hints instead of exclusions (813->839 tools)
- Review fixes: drive list --node usage text no longer implies required
  in agent schema; remove broken formula-verify --exit-on-error; error on
  --range without --sheet-id; portable stdin read; drop local --yes
  shadowing root -y on drive revert/transfer-owner; use
  confirmDangerousAction for non-delete confirms; explicit
  recursiveChange=false now transmitted; sheet version revert and
  comment delete moved into sheet confirmationGuards registry
2026-07-29 11:57:57 +08:00
玉澜 e63a4bdf47 feat(cli): add chat group get-mute-config command from wukong develop 2026-07-29 11:18:49 +08:00
github-actions[bot] 6ab01a365e Merge pull request #815 from DingTalk-Real-AI/codex/im-shortcut-optimization
feat(chat): harden and publish IM shortcuts
2026-07-29 11:05:56 +08:00
玉澜 d855edaad2 feat(cli): add chat message update-text-emotion command 2026-07-29 11:03:04 +08:00
玉澜 75fce5c4ff Merge remote-tracking branch 'origin/main' into pr621-wukong-sync
# Conflicts:
#	internal/cli/schema_catalog.json
2026-07-29 10:53:12 +08:00
玉澜 d28a50c0f4 fix(cli): resolve schema parameter mapping for drive download
Remove --version flag from drive download (polymorphic tool dispatch
incompatible with schema validation). Regenerate schema catalog and
add new commands to schema_command_exclusions.json.
2026-07-29 10:12:15 +08:00
克谨 7987fb3a35 chore(ci): retrigger pull request checks 2026-07-29 10:02:28 +08:00
克谨 3d6a9c6232 test(pipeline): cover shared flag matchers 2026-07-29 10:02:28 +08:00
克谨 195569ddfa fix(cli): harden parameter preparse integration 2026-07-29 10:02:28 +08:00
克谨 7827856876 fix(param): align aliases and bound exhaustive tests 2026-07-29 10:02:28 +08:00
克谨 f79f41e930 chore(param): exclude normalization specs from review 2026-07-29 10:02:27 +08:00
克谨 bfd53df9b2 feat(param): expand reviewed IM parameter normalization 2026-07-29 10:02:27 +08:00
克谨 4db117893e fix(param): freeze reviewed normalization baseline
Restore calendar helper behavior to main, finalize reviewed alias/guard decisions, cover payload and dry-run paths, and record the local migration freeze checkpoint.
2026-07-29 10:02:27 +08:00
克谨 b314749ef7 test(param): cover final alias payloads and guard errors 2026-07-29 10:02:27 +08:00
克谨 5133103a54 fix(param): harden command-scoped normalization safety 2026-07-29 10:02:27 +08:00
克谨 9faa332306 chore: ignore stray compiled param-aliases generator binary 2026-07-29 10:02:27 +08:00
克谨 17fa1e1b34 refactor(calendar): read canonical flags in event list after normalization
Now that alias spellings are normalized to canonical flags in the PreParse
pipeline, drop the redundant flagOrFallback tails in the event-list handler and
read --start/--end/--calendar-id/--cursor/--limit directly (keeping --count as a
deliberately separate flag). Behaviour is unchanged; the pilot test guards it.
2026-07-29 10:01:53 +08:00
克谨 af1f8ccd05 test(param): fixture regression through delivery path + co-occurrence gate
Add the ⑥ regression gate that replays every reviewed validation_fixture bad case
through the real embedded PreParse pipeline and asserts the canonical outcome
(accepting either semantic rewrite or native real-flag acceptance, failing only
on a genuine unknown-flag hallucination). Add check-param-concepts.sh (dictionary
schema/loader invariants) and check-param-alias-cooccurrence.sh (full-tree
co-occurrence scan), and wire all three into make policy.
2026-07-29 10:01:53 +08:00
克谨 c26cbbbbb8 feat(param): wire semantic alias table into PreParse; pilot calendar event list
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.
2026-07-29 10:01:53 +08:00
克谨 2733f510af feat(param): generate per-command alias table from concepts
Add internal/generator/cmd_param_aliases: reads the reviewed dictionary plus the
live Cobra tree, reduces each concept against a command's real flags (>=2 visible
real flags without a reviewed ambiguous entry fails generation), and emits the
committed internal/cli/param_aliases_generated.go table with lookup helpers.
Extend generate-schema and check-generated-drift.sh to treat the dictionary as a
reviewed input and byte-guard the generated table.
2026-07-29 10:01:53 +08:00
克谨 abc62622fb feat(param): add reviewed param-concept dictionary, closed schema, and loader
Introduce internal/cli/param_concepts.json as the single reviewed source of
parameter-normalization concepts and per-command overrides, guarded by a closed
JSON schema and a go:embed loader with contract tests. Add the design spec.
2026-07-29 10:00:41 +08:00
Dennis ecbd2e3009 Merge remote-tracking branch 'origin/main' into codex/im-shortcut-optimization
# Conflicts:
#	internal/cli/schema_catalog.json
2026-07-29 09:57:46 +08:00
Dennis 33df6ee794 test(chat): close IM shortcut coverage gaps 2026-07-29 09:46:00 +08:00
github-actions[bot] 18e1c7870e Merge pull request #676 from typefield/feat/command-surface-naming
feat(helpers): declarative LeafSpec command framework + devapp migration
2026-07-29 09:43:34 +08:00
玉澜 bbecd2f3a6 style: gofmt chat.go 2026-07-29 09:23:27 +08:00
玉澜 a705c9de0b feat(cli): implement wukong-internal commands in open-source CLI
Port 19 command leaves from wukong internal CLI:
- drive star add/remove/list (文档收藏)
- drive cover (节点封面)
- drive revert (文件版本回滚)
- drive list --versions / download --version (文件历史版本)
- drive permission transfer-owner/apply-info/apply
- sheet version save/list/revert
- sheet formula-verify
- sheet comment list/create/reply/update/delete
- chat group user-settings query/set

Restore corresponding skill docs and register commands in schema
exclusions pending Schema review.
2026-07-29 01:06:36 +08:00
玉澜 1ff4941082 Merge remote-tracking branch 'upstream/main' into feat/command-surface-naming 2026-07-29 00:53:26 +08:00
玉澜 f5d57c2e07 Merge remote-tracking branch 'origin/main' into pr621-wukong-sync 2026-07-29 00:32:22 +08:00
Dennis f3231ed2a8 Merge remote-tracking branch 'origin/main' into codex/im-shortcut-optimization
# Conflicts:
#	internal/shortcut/chat/compatibility_coverage_test.go
#	internal/shortcut/smart/compatibility_coverage_test.go
2026-07-29 00:29:53 +08:00
玉澜 7762abc1ae refactor(helpers): drop unused LeafInt64 kind (CR C1)
No production LeafSpec uses LeafInt64; devapp only needs LeafInt
(non-zero-only putInt semantics). The default MCP dispatch and Server
routing stay — they are the framework's documented main path for
future MCP-direct products.
2026-07-29 00:29:21 +08:00
Dennis 8d1b76caa7 feat(chat): align and harden IM shortcuts 2026-07-29 00:21:26 +08:00
玉澜 9d0995a61d test(helpers): cover parse-error path in required validation 2026-07-28 23:02:06 +08:00
玉澜 967cf26d44 fix(skills): remove commands absent from open-source CLI
Remove references to wukong-internal-only commands that fail CI
Interface Integrity: drive permission transfer-owner/apply/apply-info,
drive star/cover/revert/list --versions, sheet comment/formula-verify/
version, chat group user-settings. Delete sheet-comment.md and
sheet-version.md entirely.
2026-07-28 22:52:46 +08:00
玉澜 571eb20457 refactor: align required/args semantics, trim-aware fallback, helper dedupe
Post-review cleanup round:
- leaf.go: required validation now matches leafArgs inclusion rules
  (LeafInt explicit 0 / LeafInt64 <= 0 count as missing) via
  leafHasEffectiveValue; fallback-chain candidates are judged after
  TrimSpace when Trim is set so pure-whitespace values fall through.
- command_meta.go: drop catalogStringVal/catalogStringSliceVal in favor
  of existing schemaString/schemaStringSlice.
- fetch_mcp_metadata: cross-owned canonicals skip name-coincidence
  direct merges; the reviewed cross-server identity is the sole source.
2026-07-28 22:52:33 +08:00
github-actions[bot] 7937d09eed Merge pull request #757 from DingTalk-Real-AI/fix/shortcut-audit-batch
fix(shortcut): 修复按姓名解析漏掉外部联系人 + resource-url 补 --msg-id 别名
2026-07-28 22:32:59 +08:00
玉澜 bc39d24559 fix(fetch-mcp-metadata): refresh cross-server tools via reviewed interface_refs
Live matching only recognized srv.ID+"."+name == registry canonical, so
the 101 canonicals whose reviewed interface_ref routes to a differently
named server/tool were silently skipped and stayed frozen at the
previous snapshot (or degraded to stubs). Build a reverse index from the
previous snapshot's reviewed interface_refs (live key → canonicals) and
fan the live descriptor out to every owning canonical, preserving the
reviewed ref through the existing merge semantics.
2026-07-28 22:04:32 +08:00
玉澜 d31cae2b0c Revert "docs(skills): add create→transfer-owner bridge for group owner scenario"
This reverts commit 4e71f56f97.
2026-07-28 22:04:21 +08:00
wxianfeng e998e2609d feat: support agent product identity to #82250541 2026-07-28 21:46:20 +08:00
玉澜 4e71f56f97 docs(skills): add create→transfer-owner bridge for group owner scenario
group create does not support --owner; agents need an explicit pointer
to transfer-owner when users ask to specify a group owner at creation.
2026-07-28 21:44:59 +08:00
炳昱 753d538140 feat(event): complete personal OA approval events 2026-07-28 21:38:17 +08:00
玉澜 3717053d24 chore(helpers): drop dead devapp flag-registration helpers
addDevAppVersionLocatorFlags and registerDevAppMemberMutationFlags lost
their last callers when the dev app command surface was reworked; the
uncovered dead code regressed overall coverage below the merge base.
2026-07-28 21:33:26 +08:00
玉澜 2a3df50d0f refactor(cli): deterministic alias collision resolution and helper cleanup
alias-vs-alias collisions in the command meta lookup now resolve to the
owner with the lexicographically smallest primary path instead of map
iteration order. Move catalogStringVal next to its sibling helpers in
command_meta.go and drop the redundant captureBaseHelpFunc alias in the
calendar help wrapper. Unify the Safety help annotation to English
"(requires --yes)".
2026-07-28 21:13:49 +08:00
玉澜 03b3cf68e4 feat(coverage-gate): log files exempted for having no executable statements
Silently dropping non-executable changed files made the exemption
invisible in CI logs; each exempted path is now reported to stderr in
sorted order.
2026-07-28 21:13:40 +08:00
玉澜 bbdf843ef3 fix(fetch-mcp-metadata): count registry stubs as unmatched in coverage
matched_tools claimed every surface tool matched even when entries were
registry stubs with no live MCP metadata, and unmatched_tools was
hardcoded to 0. Coverage now excludes stubs from matched_tools, reports
them as unmatched, and a registry JSON parse failure warns instead of
silently producing a stub-only snapshot. The schema catalog policy
invariant is relaxed to match the honest accounting.
2026-07-28 21:13:40 +08:00
玉澜 eb2658ca68 fix(helpers): honor alias/env/default fallback for integer leaf flags
The leaf fallback chain read only string flags, so LeafInt/LeafInt64
flags could never satisfy Required via alias or env, alias values for
integer flags were silently dropped, and a registered Default shadowed
alias/env values. Resolution order is now explicit flag > alias > env >
Default > ArgDefault, aliases register with the primary flag's Kind, and
unparsable integer env values fail loudly.
2026-07-28 21:13:29 +08:00
玉澜 69b8df3e40 fix(skills): reconcile wukong sync with latest main CLI surface
Restore capabilities now supported on main (doc read --scope/--tags,
drive upload --node overwrite, chat category, dingtalk-markdown routing),
remove commands still absent from the open-source CLI (calendar event
instances, sheet info --include, chat group create --owner), remap
folded services (attendance/ding/oa/report/sheet) to dingtalk-misc in
the shortcut generator, and regenerate shortcut sections and schema
metadata.
2026-07-28 20:56:37 +08:00
Dennis a5ac09218b fix(chat): close IM shortcut validation gaps 2026-07-28 20:55:25 +08:00
玉澜 8d988bc350 Merge remote-tracking branch 'origin/main' into pr621-wukong-sync
# Conflicts:
#	skills/multi/dingtalk-aitable/SKILL.md
#	skills/multi/dingtalk-attendance/SKILL.md
#	skills/multi/dingtalk-calendar/SKILL.md
#	skills/multi/dingtalk-chat/SKILL.md
#	skills/multi/dingtalk-chat/references/chat.md
#	skills/multi/dingtalk-contact/SKILL.md
#	skills/multi/dingtalk-contact/references/contact.md
#	skills/multi/dingtalk-ding/SKILL.md
#	skills/multi/dingtalk-doc/SKILL.md
#	skills/multi/dingtalk-doc/references/doc.md
#	skills/multi/dingtalk-doc/references/doc/doc-comment.md
#	skills/multi/dingtalk-doc/references/doc/doc-read.md
#	skills/multi/dingtalk-drive/SKILL.md
#	skills/multi/dingtalk-drive/references/drive.md
#	skills/multi/dingtalk-mail/SKILL.md
#	skills/multi/dingtalk-minutes/SKILL.md
#	skills/multi/dingtalk-oa/SKILL.md
#	skills/multi/dingtalk-report/SKILL.md
#	skills/multi/dingtalk-sheet/SKILL.md
#	skills/multi/dingtalk-todo/SKILL.md
#	skills/multi/dingtalk-todo/references/todo.md
#	skills/multi/dingtalk-wiki/SKILL.md
#	skills/multi/dws-shared/SKILL.md
2026-07-28 20:25:16 +08:00
玉澜 dc20ddecf6 feat(skills): sync wukong 13-sub-skill multi layout with open-source cleanup
Replace skills/multi with wukong's consolidated structure (long-tail
products folded into dingtalk-misc), keeping GitHub-only skills
(dingtalk-dev/event/pat/profile/skill). Prune MCP-only product refs and
align all documented commands/flags with the open-source Cobra tree:
remove markdown/*, drive task get, drive version flags, doc read
--scope, --async modes, retired conference/chat-file-upload mentions.
2026-07-28 20:20:12 +08:00
DennisandClaude Opus 4.8 d41214988a fix(shortcut): keep external contacts in name resolution; alias resource-url msg-id
Two independent shortcut correctness fixes surfaced by the audit:

- Name→ID resolution (chat +dm / +broadcast / … via the shared resolver) dropped
  every search_contact_by_key_word row with an empty userId. External /
  cross-org contacts arrive with only an openDingTalkId, so they were silently
  discarded — making resolution report a real person as missing, or collapse to
  the wrong single match when an in-org namesake existed. Keep any row with at
  least one usable identity (userId or openDingTalkId) and fall the display name
  back through nick/showName/flowerName/staffName/userName.

- chat +messages-resource-url required --message-id with no alias, so an agent
  copying the message list's openMessageId/msgId output field hit "unknown
  flag". Accept --msg-id / --open-message-id as aliases (declared via an
  at-least-one constraint since a shortcut's Required check only sees the
  primary flag name), mirroring the earlier chat message download-media fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 20:11:26 +08:00
Dennis bfb812bfa0 feat(chat): publish and harden all IM shortcuts 2026-07-28 18:59:04 +08:00
玉澜 a09790fc3f Merge remote-tracking branch 'origin/main' into pr621-wukong-sync
# Conflicts:
#	test/skill_static/skill_static_test.go
2026-07-28 18:10:50 +08:00
玉澜 4b0f71eedc test: close changed-code coverage gaps to satisfy the coverage gate
- fetch_mcp_metadata: extract run()/resolveToken()/writeMetadata with
  injectable deps (keychain, servers, lister, registry, exit); full-path
  tests reach 100% file coverage.
- internal/cli: drop dead initSafetyByCLIPath (superseded by ResolveMeta),
  split buildMetaByCLIPath / assembleSchemaCatalogSnapshot /
  assembleCommandRegistryFrom / mergedCommandRegistryJSON so shard and
  malformed-snapshot failure modes are testable; cover catalog structure
  violation formatting (sort/truncate) and RenderSafetyAnnotation.
- generators: cover registry shard merge and catalog shard write failure
  modes.
- helpers/cmdutil: cover LeafSpec default/server dispatch, transform error
  propagation, default env hint, devapp member remove validate chain, and
  the required-flags error helpers.

Local gate: overall 90.17% vs merge-base 89.96%, changed-code 100%
(861 statements); make policy and go test ./... green.
2026-07-28 18:05:20 +08:00
玉澜 5c30d01522 fix(coverage-gate): exempt files without executable statements
A changed production Go file with no function bodies (pragma carriers such
as internal/cli/gen.go, doc-only files) can never appear in a coverage
profile, so the missing-profile check failed every PR touching one. Parse
changed files and exempt those without executable statements; unreadable
or unparsable files stay conservative.
2026-07-28 18:05:19 +08:00
玉澜 c94190f90e fix: honor alias/env fallback for plain required LeafSpec flags
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.
2026-07-28 16:48:42 +08:00
玉澜 6763ddd154 fix: resolve command metadata via compat aliases
ResolveMeta copies Catalog aliases into CommandIdentity and registers each
alias path against the same metadata (primary cli_path wins on collision),
so compat paths like 'report list' resolve instead of returning ok=false.
2026-07-28 16:48:42 +08:00
玉澜 235cad4cc7 fix: report honest MCP snapshot coverage
snapshot_services now counts only services whose tools/list succeeded and
missing_services names the failures, so a partially failed refresh can no
longer write a snapshot that claims full coverage.
2026-07-28 16:48:42 +08:00
wxianfeng f890dda7e7 feat(event): add personal OA approval events
Use the Event-specific pre-release control and stream ticket endpoints by default.
2026-07-28 16:15:18 +08:00
玉澜 96d0d430e6 Merge upstream main into feat/command-surface-naming 2026-07-28 16:12:38 +08:00
github-actions[bot] 5783c4e82a chore: update beta formula for v1.0.55-beta.5 [skip ci] 2026-07-28 07:10:13 +00:00
chichuanandchichuan baafd6fe7d docs(CHANGELOG): 补充 v1.0.55-beta.5 精确发布说明(风险等级:文档级) (#812)
Co-authored-by: chichuan <haofeng.hf@alibaba-inc.com>
2026-07-28 15:02:24 +08:00
github-actions[bot] 23c3b74979 Merge pull request #803 from DingTalk-Real-AI/codex/fix-contract-defects
fix: harden dws contract edge cases
2026-07-28 14:46:06 +08:00
Dennis 258e7ed872 fix: align doc rename schema contract 2026-07-28 14:30:29 +08:00
Dennis 69d813afef fix: address contract review feedback 2026-07-28 12:09:57 +08:00
Dennis 00305d941d fix: preserve document info schema compatibility 2026-07-28 11:37:32 +08:00
Dennis ec7fdb0f0d fix: harden dws contract edge cases 2026-07-28 11:36:44 +08:00
github-actions[bot] a8e83e5e7e Merge pull request #804 from wxianfeng/feature/aone84760010-qwenwork-agent-host
feat: add agent host observation metadata
2026-07-28 03:02:43 +00:00
wxianfeng c870d2ebdc Merge remote-tracking branch 'upstream/main' 2026-07-28 10:52:41 +08:00
修雨 488d90b73c Merge branch 'main' into feature/aone84760010-qwenwork-agent-host 2026-07-28 10:51:27 +08:00
修雨 2c10be2a1a feat(schema): publish 210 built-in shortcuts (#802)
Publishes all 210 public built-in shortcuts as reviewed Agent-visible
leaf tools across 16 product groups, with stable canonical identities,
executable +shortcut CLI paths, parameter and cross-parameter
constraints, selection guidance, interface metadata, and runtime-aligned
safety/confirmation semantics. Catalog grows from 603 to 813 tools.
2026-07-28 00:11:29 +08:00
wxianfeng 3985c4c98f feat: add agent host observation metadata (Aone 84760010) 2026-07-27 22:09:58 +08:00
wxianfeng 196bf929c1 Merge remote-tracking branch 'upstream/main' 2026-07-27 20:50:49 +08:00
github-actions[bot] 2dfc39f0d3 Merge pull request #790 from wxianfeng/feature/dws-event-im-phase3
feat(event): add multi-event and group lifecycle subscriptions
2026-07-27 10:00:25 +00:00
wxianfeng 97a16c43b4 fix(event): harden targeted consumer stop 2026-07-27 17:50:31 +08:00
wxianfeng afe7d860ff Merge remote-tracking branch 'upstream/main' 2026-07-27 15:57:39 +08:00
wxianfeng 63b3e72fad test(event): close coverage gate gaps 2026-07-27 15:35:49 +08:00
wxianfeng 984529b4cb test(event): cover multi-event edge paths 2026-07-27 14:59:00 +08:00
wxianfeng 298ea5b341 Merge remote-tracking branch 'upstream/main' into feature/dws-event-im-phase3
# Conflicts:
#	CHANGELOG.md
2026-07-27 13:47:23 +08:00
github-actions[bot] 3e4886fc71 chore: update beta formula for v1.0.55-beta.4 [skip ci] 2026-07-27 03:32:08 +00:00
修雨 72cb8f188e ci: trigger code admission on main after bot merges 2026-07-27 11:16:24 +08:00
github-actions[bot] 2f8614aa1f Merge pull request #798 from DingTalk-Real-AI/changelog-v1.0.55-beta.4
chore(release): prepare v1.0.55-beta.4
2026-07-27 10:58:01 +08:00
修雨 0e60d09980 chore(release): prepare v1.0.55-beta.4 2026-07-27 10:26:32 +08:00
github-actions[bot] 4d182dea45 Merge pull request #795 from DingTalk-Real-AI/codex/fix-chat-bots-projection
fix(shortcut): prevent projection data loss
2026-07-27 10:18:14 +08:00
修雨 f56d3263e8 chore: extend changelog entry and align npm propagation test with workflow
- CHANGELOG [Unreleased] entry now covers all three projection fixes
- npm dist-tag propagation test expects 60 attempts, matching release.yml
2026-07-26 21:49:12 +08:00
Dennis 22c94900d7 docs(changelog): note shortcut projection fix 2026-07-26 16:58:10 +08:00
Dennis 0871c5d88c fix(shortcut): preserve bot search and mail thread fields 2026-07-26 16:19:21 +08:00
Dennis 15a15a1c12 fix(shortcut): preserve chat bots projection 2026-07-26 15:48:21 +08:00
修雨 5c01ae845f fix: move event changelog entry to [Unreleased] + update npm propagation test
- Add missing ## [Unreleased] heading required by Policy CI check
- Update npm test assertion (12 → 60) to match extended propagation wait
2026-07-25 09:44:39 +08:00
修雨 36b58d08b0 Merge branch 'main' into feature/dws-event-im-phase3 2026-07-25 09:33:53 +08:00
修雨 0cc049eaa1 fix(release): handle Gitee API 200 null response for missing releases
Gitee API returns HTTP 200 with null body when a release tag doesn't
exist, unlike GitHub which returns 404. Treat empty release_id as
"no release exists" instead of erroring out.
2026-07-24 18:58:30 +08:00
修雨 dd2d91ae7e feat(ci): add release asset sync to Gitee mirror workflow
Add `sync_release_version` input to mirror-to-gitee.yml for ad-hoc
release asset synchronization that bypasses the release.yml verify
gate when tag metadata cannot be updated.
2026-07-24 18:54:47 +08:00
修雨 98309fa239 fix(ci): increase npm CDN propagation timeout with incremental backoff
npm registry behind CDN can take 1-5 minutes for dist-tag to propagate
to edge nodes. Extend max attempts from 12 to 60 and use incremental
backoff: 5s for first 12 attempts, then 10s.
2026-07-24 18:48:08 +08:00
修雨 b4e92f4e65 chore(release): prepare v1.0.55-beta.3 2026-07-24 18:48:03 +08:00
修雨 b34bbc9aa0 ci: enforce beta and stable release roles (#791) 2026-07-24 18:15:55 +08:00
wxianfeng 3749c6b793 docs: update changelog for personal events 2026-07-24 17:59:19 +08:00
github-actions[bot] 40444a6f78 chore: update beta formula for v1.0.55-beta.3 [skip ci] 2026-07-24 09:35:06 +00:00
修雨 97248bf7c2 chore(release): prepare v1.0.55-beta.3 2026-07-24 16:41:45 +08:00
修雨 fd6b3be046 ci: tier PR quality gates and automate review routing (#788)
Tier PR validation by risk, distribute peer review automatically, and enable a streamlined quality-preserving merge path.
2026-07-24 16:37:03 +08:00
wxianfeng e2390c3385 Merge remote-tracking branch 'upstream/main' into feature/dws-event-im-phase3
# Conflicts:
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog.json
#	skills/mono/SKILL.md
2026-07-24 16:31:29 +08:00
修雨 835c9229fd ci: tier PR quality gates and automate review routing 2026-07-24 16:24:59 +08:00
修雨 ea7d8cc666 Merge pull request #783 from DingTalk-Real-AI/fix/shortcut-projection-data-loss
fix(shortcut): fix projection-data-loss silent-empty returns
2026-07-24 15:50:25 +08:00
wxianfeng 125bc88d52 fix(event): switch personal defaults to production 2026-07-24 15:40:37 +08:00
DennisandClaude Opus 4.8 d2e36a76e2 fix(shortcut): address review — minutes taskUuid only; English test messages
- minutes: drop the minutesId/minutes_id candidate from the taskUuid mapping.
  minutesId is the minutes document id, a different identifier from the
  recording taskUuid that +record-pause/resume/stop consume via --id, so
  substituting it would feed record control a wrong id. The backend list
  already returns taskUuid; the guard test now asserts taskUuid/task_uuid.
- Rewrite the guard-test failure messages and fixture data in English to match
  the repository convention (only the two assertions that match the
  production Chinese validation string are kept).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 15:20:00 +08:00
Dennis 52cf261000 Merge remote-tracking branch 'origin/main' into fix/shortcut-projection-data-loss 2026-07-24 14:46:54 +08:00
炳昱 6b9fcf9289 fix(event): preserve nested message context when flattened 2026-07-24 14:26:34 +08:00
修雨 8dac7d5fa5 Merge pull request #708 from anxiangbo/feat/20260714_hrbrain
Feat/20260714 hrbrain
2026-07-24 12:31:42 +08:00
修雨 4543e9935c Merge branch 'main' into feat/20260714_hrbrain 2026-07-24 11:38:50 +08:00
修雨 e79c3ea5b9 Merge pull request #786 from DingTalk-Real-AI/codex/fix-homebrew-publish-identity
fix(release): use designated Homebrew publisher
2026-07-24 11:34:26 +08:00
修雨 ad2ba15a45 fix(release): use designated Homebrew publisher 2026-07-24 11:23:20 +08:00
修雨 74350b3c7b Merge pull request #784 from DingTalk-Real-AI/codex/simplify-release-pipeline
fix(release): make publication retries seamless
2026-07-24 11:17:01 +08:00
修雨 02f66df599 fix(release): make publication retries seamless 2026-07-24 11:05:14 +08:00
anxb 883f082425 fix(changelog): move HR Brain entry to Unreleased
The HR Brain entry was incorrectly placed in the released
[1.0.55-beta.1] section during merge conflict resolution. Move it
back to ## [Unreleased] ### Added since hrbrain has not shipped yet.
2026-07-24 10:27:08 +08:00
DennisandClaude Opus 4.8 b1e7b43f85 fix(shortcut): fix projection-data-loss silent-empty returns
Several read shortcuts returned an empty list with exit 0 and no error
envelope even though the underlying MCP tool returned data, so agents misread
"no data" and made wrong decisions.

Root causes:
- Container key mismatch: the resolver probed the wrong key —
  processCodeList / values / wikiSpaces / itemList / groupList / recentItems /
  emailAccounts / deptUserList / labelUserList / roles / report_list, plus
  get_org_labels grouped labels[] needing a descend.
- Item fields nested under a VO wrapper, not unwrapped: shiftVO / entityVO /
  userInfo.
- Param exceeded a backend limit: todo +created-todos sent pageSize=50 while
  the backend silently returns empty for pageSize>20; now uses the shared pager
  (pageSize=20).

Affected: contact/oa/wiki/drive/minutes/calendar/attendance/chat/report/smart
resolvers. Every fix ships a guard test that feeds the real backend response
shape (and, for minutes, both the taskUuid and minutesId item shapes) and
asserts the projection is non-empty with a usable id.

scripts/shortcut_real_result.py now compares the upper (projection) output
against the lower (raw backend) layer, and record_real_shortcut_run.py captures
the lower layer in memory (persisting only derived counts, never raw PII) so an
exit-0 empty projection over a non-empty backend is scored as
projection-data-loss instead of real-ok. The Python self-test runs in CI via
test/scripts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 10:07:02 +08:00
anxb 571a096004 feat: 组织大脑修复7 2026-07-24 09:59:08 +08:00
anxb 0d3fef0037 feat: 组织大脑修复6 2026-07-24 09:46:31 +08:00
anxb 72934ddc39 Merge branch 'refs/heads/main' into feat/20260714_hrbrain
# Conflicts:
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog.json
#	internal/cli/schema_hints/runtime-surface-completeness.json
#	skills/mono/SKILL.md
#	test/fixtures/cli-interface-baseline.txt
2026-07-24 09:43:16 +08:00
修雨 eaf53bc2d2 Merge pull request #781 from DingTalk-Real-AI/automation/homebrew-beta-v1.0.55-beta.2
chore: update Homebrew beta formula for v1.0.55-beta.2
2026-07-23 23:28:52 +08:00
DWS Release Bot 3e148c6745 chore: update beta formula for v1.0.55-beta.2 2026-07-23 11:10:16 +00:00
修雨 07bc528c6c Merge pull request #777 from PeterGuy326/codex/release-v1.0.55-beta.2
chore(release): prepare v1.0.55-beta.2
2026-07-23 18:34:38 +08:00
修雨 7ee87d93ee chore(release): prepare v1.0.55-beta.2 2026-07-23 18:32:35 +08:00
修雨 7b77b4e615 Merge pull request #776 from PeterGuy326/codex/sync-wukong-capabilities-20260723
feat: sync Wukong chat, contact, doc, drive, Markdown, and todo
2026-07-23 18:28:29 +08:00
anxb 5dcf95ce07 Merge remote-tracking branch 'origin/feat/20260714_hrbrain' into feat/20260714_hrbrain 2026-07-23 18:23:35 +08:00
anxb e70b21ae8a Merge branch 'refs/heads/main' into feat/20260714_hrbrain 2026-07-23 18:22:43 +08:00
修雨 897eb6515b Merge branch 'main' into codex/sync-wukong-capabilities-20260723 2026-07-23 18:17:36 +08:00
anxiangbo ad0582ea48 Merge branch 'main' into feat/20260714_hrbrain 2026-07-23 18:10:28 +08:00
修雨 f9443af460 fix: preserve schema compatibility for synced capabilities 2026-07-23 17:43:13 +08:00
修雨 876afcddfb feat: sync Wukong capabilities through 3306c3307 2026-07-23 17:43:12 +08:00
修雨 c771d48d6c Merge pull request #756 from shangguanxuan633-lab/codex/auth-legacy-token-compat
fix(auth): migrate legacy tokens and preserve unresolved accounts
2026-07-23 17:35:47 +08:00
修雨 9e48ef759f Merge remote-tracking branch 'origin/main' into codex/auth-legacy-token-compat 2026-07-23 17:24:36 +08:00
修雨 9fb2b76f9a Merge pull request #775 from PeterGuy326/codex/minimize-release-latency
perf(release): shorten guarded release critical path
2026-07-23 17:14:38 +08:00
上官玄 228c62bc0f docs(changelog): note legacy auth compatibility 2026-07-23 16:35:52 +08:00
修雨 321514ad99 perf(release): shorten guarded release critical path 2026-07-23 16:07:58 +08:00
wxianfeng 5a3617c21d feat(event): flatten group member events 2026-07-23 15:30:49 +08:00
玉澜 0d866911e0 fix: refresh existing MCP metadata 2026-07-23 14:20:23 +08:00
anxb 883f397554 feat: 组织大脑修复5 2026-07-23 14:17:22 +08:00
玉澜 2bf5401f55 fix: load split registry for MCP metadata refresh 2026-07-23 14:16:27 +08:00
玉澜 c76c30a0b7 Merge upstream main into feat/command-surface-naming 2026-07-23 14:12:18 +08:00
anxb 276d5bcd73 Merge branch 'refs/heads/main' into feat/20260714_hrbrain 2026-07-23 14:08:53 +08:00
anxb 8321f1ffea Merge remote-tracking branch 'upstream/main' into feat/20260714_hrbrain
# Conflicts:
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog.json
#	internal/cli/schema_hints/runtime-surface-completeness.json
#	test/fixtures/cli-interface-baseline.txt
2026-07-23 14:05:24 +08:00
上官玄 888e4432a3 Merge remote-tracking branch 'upstream/main' into codex/auth-legacy-token-compat 2026-07-23 13:45:07 +08:00
修雨 cb200af3b2 Merge pull request #771 from DingTalk-Real-AI/codex/release-v1.0.55-beta.1
chore(release): prepare v1.0.55-beta.1
2026-07-23 13:43:58 +08:00
修雨 cf5b76de07 chore(release): prepare v1.0.55-beta.1 2026-07-23 13:35:30 +08:00
上官玄 3832e7f5e4 Merge remote-tracking branch 'upstream/main' into codex/auth-legacy-token-compat 2026-07-23 13:35:02 +08:00
上官玄 71f90ee45f test(keychain): cover Windows registry failures 2026-07-23 13:31:23 +08:00
修雨 423e16ced0 Merge pull request #767 from DingTalk-Real-AI/codex/align-chat-file-upload
fix(chat): align local file sending with Wukong
2026-07-23 13:25:10 +08:00
上官玄 0d175c4d53 test(auth): isolate Windows credential fixtures 2026-07-23 13:20:43 +08:00
上官玄 a5a6a0f2ce test(auth): close legacy compatibility coverage gaps 2026-07-23 13:01:10 +08:00
修雨 c1a4bd6781 fix(chat): preserve interface while retiring discovery 2026-07-23 13:00:05 +08:00
修雨 02817bc043 Merge main and complete chat media retirement 2026-07-23 12:56:11 +08:00
上官玄 b08f0f77e3 Merge remote-tracking branch 'upstream/main' into codex/auth-legacy-token-compat 2026-07-23 12:22:11 +08:00
上官玄 6d7cc41284 fix(auth): harden legacy token compatibility 2026-07-23 12:21:58 +08:00
修雨 9f76c1844a Merge pull request #697 from FloralTide/feat/mcp-url-get
feat(mcp): add URL resolution command
2026-07-23 11:59:34 +08:00
炳昱 857d9b8c1b test(mcp): cover URL command error paths 2026-07-23 11:31:12 +08:00
anxb 5bdaad092a feat: 组织大脑修复,add hrbrain command nodes to interface baseline)。 2026-07-23 10:42:11 +08:00
anxb 55cf6cfb71 Merge branch 'refs/heads/main' into feat/20260714_hrbrain
# Conflicts:
#	CHANGELOG.md
2026-07-23 10:38:38 +08:00
炳昱 a7fdcea086 test(cli): update public interface baseline 2026-07-23 10:19:14 +08:00
上官玄 1bc17bc4bd Merge remote-tracking branch 'upstream/main' into codex/auth-legacy-token-compat 2026-07-23 00:59:31 +08:00
炳昱 9fc63b6405 fix(mcp): expose URL command in schema 2026-07-23 00:46:14 +08:00
炳昱 3233e1fe93 feat(mcp): add URL resolution command 2026-07-23 00:46:14 +08:00
修雨 f7e61feacf Merge remote-tracking branch 'origin/main' into codex/align-chat-file-upload
# Conflicts:
#	CHANGELOG.md
2026-07-23 00:45:11 +08:00
修雨 cdc3fbe328 test(chat): cover ID routing helpers 2026-07-23 00:38:50 +08:00
Dennis4477 b4ea1f168d fix(chat): render cards, forwards and encrypted messages
Normalize message projections across read shortcuts, preserve mixed user JSON, expand forwarded records, mask ciphertext, and accept media-download message ID aliases while retaining the Cobra/Schema required contract.
2026-07-23 00:18:46 +08:00
修雨 b03017997d fix(schema): preserve chat interface contract 2026-07-23 00:11:41 +08:00
修雨 902e084d8a fix(chat): align local file sending with wukong 2026-07-23 00:03:58 +08:00
上官玄 ccb69f93b8 fix(auth): preserve legacy login state across token backends 2026-07-23 00:01:09 +08:00
修雨 412e77f215 Merge pull request #763 from DingTalk-Real-AI/codex/retry-gitee-transient-outages
fix: retry transient Gitee read outages safely
2026-07-22 17:56:50 +08:00
修雨 2a0bf1ebea fix: retry transient Gitee read outages safely 2026-07-22 17:46:03 +08:00
修雨 9ce13da6ed Merge pull request #762 from DingTalk-Real-AI/codex/extend-gitee-upload-window
fix: extend Gitee upload window
2026-07-22 16:50:49 +08:00
修雨 0e5731166b fix: extend Gitee upload window 2026-07-22 16:39:55 +08:00
anxb 58b4d6f9f4 Merge branch 'refs/heads/main' into feat/20260714_hrbrain 2026-07-22 16:38:40 +08:00
anxb a3478ad587 feat: 组织大脑修复4 2026-07-22 16:31:10 +08:00
anxb 5d1092da73 Merge branch 'refs/heads/main' into feat/20260714_hrbrain 2026-07-22 16:09:29 +08:00
修雨 92edd8ea53 Merge pull request #761 from DingTalk-Real-AI/codex/fix-gitee-slow-upload-timeout
fix: allow slow Gitee binary uploads
2026-07-22 16:08:28 +08:00
修雨 931d75beaf fix: allow slow Gitee binary uploads 2026-07-22 15:57:21 +08:00
修雨 7667cb30a3 Merge pull request #759 from DingTalk-Real-AI/codex/fix-gitee-upload-expect
fix: disable Expect for Gitee uploads
2026-07-22 15:13:33 +08:00
修雨 015daae064 fix: disable Expect for Gitee uploads 2026-07-22 15:02:13 +08:00
修雨 e7510ea5f0 Merge pull request #758 from DingTalk-Real-AI/codex/fix-gitee-upload-timeouts
fix: harden Gitee release repair
2026-07-22 14:39:15 +08:00
修雨 582b73cb40 fix: harden Gitee release repair 2026-07-22 14:27:47 +08:00
anxb 46fe02f72c feat: 组织大脑修复3 2026-07-22 14:24:14 +08:00
修雨 04ea184ff6 Merge pull request #752 from DingTalk-Real-AI/automation/homebrew-beta-v1.0.54-beta.2
chore: update Homebrew beta formula for v1.0.54-beta.2
2026-07-22 14:12:31 +08:00
anxb 2dba64b880 feat: 组织大脑修复2 2026-07-22 13:56:13 +08:00
wxianfeng 1c9b09b23f Merge remote-tracking branch 'upstream/main' into feature/dws-event-im-phase3
# Conflicts:
#	internal/app/event_command.go
#	internal/app/event_personal_command.go
#	internal/cli/schema_agent_metadata/event.json
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog.json
#	internal/cli/schema_hints/selection/event.json
#	internal/event/personal/registry_test.go
#	skills/mono/references/products/event.md
#	skills/multi/dingtalk-event/SKILL.md
#	skills/multi/dingtalk-event/references/event-im.md
2026-07-22 11:54:32 +08:00
修雨 0908b2ca6e Merge branch 'main' into automation/homebrew-beta-v1.0.54-beta.2 2026-07-22 11:48:29 +08:00
修雨 070febd7bf Merge pull request #755 from DingTalk-Real-AI/automation/homebrew-v1.0.54
chore: update Homebrew formula for v1.0.54
2026-07-22 11:47:19 +08:00
anxb e564c8d923 Merge branch 'refs/heads/main' into feat/20260714_hrbrain
# Conflicts:
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog.json
2026-07-22 11:09:37 +08:00
DWS Release Bot e3782231be chore: update formula for v1.0.54 2026-07-21 16:07:10 +00:00
DWS Release Bot 167a547a65 chore: update beta formula for v1.0.54-beta.2 2026-07-21 15:55:51 +00:00
修雨 8f62c19104 Merge pull request #749 from DingTalk-Real-AI/release/changelog-v1.0.54-beta.2
docs(changelog): add v1.0.54-beta.2 section
2026-07-21 23:37:15 +08:00
修雨 82798dc7fc docs(changelog): add v1.0.54-beta.2 section 2026-07-21 23:35:36 +08:00
修雨 3319cf62d5 Merge pull request #748 from DingTalk-Real-AI/release/changelog-v1.0.54
docs(changelog): fold v1.0.54-beta.1 into v1.0.54 stable section
2026-07-21 23:28:45 +08:00
修雨 1626818a98 docs(changelog): retain released v1.0.54-beta.1 section under v1.0.54 2026-07-21 23:26:23 +08:00
修雨 a03d6ebacc docs(changelog): fold v1.0.54-beta.1 into v1.0.54 stable section 2026-07-21 23:23:40 +08:00
修雨 4ee4a44e16 Merge pull request #745 from DingTalk-Real-AI/release/changelog-v1.0.54-beta.1
docs(changelog): add v1.0.54-beta.1 section
2026-07-21 23:00:03 +08:00
修雨 40181f8c0c docs(changelog): add v1.0.54-beta.1 section 2026-07-21 22:57:59 +08:00
修雨 14ff02ebe1 Merge pull request #743 from wxianfeng/fix/event-data-format-compat
fix(event): make flattened output opt-in
2026-07-21 22:50:21 +08:00
wxianfeng 55574fe12e Merge upstream/main into fix/event-data-format-compat 2026-07-21 22:37:26 +08:00
修雨 222ee16d51 test(event): close changed-code coverage gaps for flatten output mode
Drop the unreachable defensive tag-skip branch in transportEnvelopeSchema
(every transport.Event field carries a non-empty JSON tag) and add a unit
test for the validatePersonalEventOutputMode success path so the changed
code coverage gate reaches 100%.
2026-07-21 22:28:54 +08:00
修雨 27ced3ee18 Merge pull request #701 from DingTalk-Real-AI/codex/fix-plugin-command-registration
fix: restore plugin CLI overlay commands
2026-07-21 22:03:08 +08:00
修雨 129e8a10ef Merge remote-tracking branch 'origin/main' into codex/fix-plugin-command-registration
# Conflicts:
#	CHANGELOG.md
2026-07-21 21:50:37 +08:00
修雨 02fba09c1e fix(plugin): let replaceable fallbacks pass distribution conflict checks
pluginDescriptorConflictsWithDistribution and the identity-owner seeding
both treated conference as distribution-owned, so the whole plugin server
was skipped before the replaceable-fallback merge in addPluginCommandsSafe
could run. Skip replaceablePluginFallbacks names in both early gates while
keeping reserved-command protection and plugin-vs-plugin ownership intact.
2026-07-21 21:49:47 +08:00
修雨 94b64f74ac Merge pull request #738 from typefield/fix/schema-cli-path-compat
fix(schema): accept compatible CLI path separators
2026-07-21 21:37:10 +08:00
wxianfeng cefcf5b409 fix(event): make flattened output opt-in 2026-07-21 21:25:10 +08:00
玉澜 441289cdfe Merge remote-tracking branch 'upstream/main' into fix/schema-cli-path-compat 2026-07-21 20:50:37 +08:00
玉澜 d03823d772 test(schema): cover unknown compatibility query 2026-07-21 20:50:34 +08:00
修雨 c16a377863 Merge branch 'main' into codex/fix-plugin-command-registration 2026-07-21 20:47:48 +08:00
修雨 31c3acc94b Merge pull request #718 from DingTalk-Real-AI/cleanup/remove-shortcut-eval-pii
chore: 移除含真实 PII 的 shortcut 评测产物
2026-07-21 20:47:19 +08:00
修雨 f7e702df8d Merge branch 'main' into codex/fix-plugin-command-registration 2026-07-21 20:35:02 +08:00
修雨 7fd40ea19a Merge branch 'main' into cleanup/remove-shortcut-eval-pii 2026-07-21 20:34:48 +08:00
玉澜 bee246e62c Merge remote-tracking branch 'upstream/main' into fix/schema-cli-path-compat 2026-07-21 19:50:20 +08:00
玉澜 089caa92a8 test(schema): cover prefixed compatibility query 2026-07-21 19:41:39 +08:00
修雨 2f0f32f56f Merge pull request #739 from DingTalk-Real-AI/fix/release-artifact-raw-version-check
fix(release): verify packaged artifact versions from raw binary bytes
2026-07-21 19:25:39 +08:00
修雨 068d9ff2f5 fix(release): verify packaged artifact versions from raw binary bytes 2026-07-21 19:25:14 +08:00
wxianfeng 4cded1ef6c Merge remote-tracking branch 'upstream/main' 2026-07-21 19:24:43 +08:00
玉澜 21cd3f8bc4 fix(schema): accept compatible CLI path separators 2026-07-21 19:18:07 +08:00
修雨 418928b9a5 Merge pull request #736 from DingTalk-Real-AI/chore/changelog-v1.0.53-stable
docs(changelog): finalize v1.0.53 stable section
2026-07-21 19:00:26 +08:00
修雨 b047b2c3c9 docs(changelog): fold post-beta.7 entries into v1.0.53 stable section 2026-07-21 18:59:56 +08:00
修雨 a516e5f54a Merge pull request #735 from sczheng189/codex/fix-stable-version-verification
fix(release): verify package versions from raw binaries
2026-07-21 18:55:54 +08:00
修雨 70e4e75c66 Merge branch 'main' into codex/fix-stable-version-verification 2026-07-21 18:55:31 +08:00
修雨 1116916b24 Merge pull request #734 from DingTalk-Real-AI/revert-732-fix/release-admission-commit-statuses
Revert "fix(release): check commit statuses in Code Admission gates"
2026-07-21 18:53:57 +08:00
修雨 c0c81b4d70 Merge pull request #733 from DingTalk-Real-AI/revert-730-codex/fix-release-version-verifier
Revert "fix(release): validate packaged version at runtime"
2026-07-21 18:53:53 +08:00
修雨 eedc41ac54 Merge branch 'main' into revert-730-codex/fix-release-version-verifier 2026-07-21 18:52:05 +08:00
zhengyubai c14e24569c fix(release): verify package versions from raw binaries 2026-07-21 19:49:18 +09:00
SCzheng 8add2c00cf Revert "fix(release): check commit statuses in Code Admission gates (#732)"
This reverts commit 29dceec5ce.
2026-07-21 19:48:47 +09:00
修雨 29dceec5ce fix(release): check commit statuses in Code Admission gates (#732)
The "AI Behavior" context is reported as a commit status (via
github.rest.repos.createCommitStatus) rather than a check run, but the
Code Admission gates only queried check runs via
github.rest.checks.listForRef. This caused every release to fail with
"missing: AI Behavior" since the context was never found.

Add a commit-status query after the check-run loop in both the preflight
and sealed-commit Code Admission gates. Statuses are merged only for
required contexts not already covered by a check run, preserving the
existing check-run precedence.
2026-07-21 18:48:03 +08:00
SCzheng b78a0dee47 Revert "fix(release): validate packaged version at runtime" 2026-07-21 19:46:32 +09:00
修雨 807191396e Merge pull request #730 from DingTalk-Real-AI/codex/fix-release-version-verifier
fix(release): validate packaged version at runtime
2026-07-21 18:12:40 +08:00
修雨 cce9b798d5 Merge remote-tracking branch 'origin/main' into codex/fix-release-version-verifier 2026-07-21 17:51:46 +08:00
修雨 bfa3a1bf33 Merge pull request #729 from sczheng189/feat/relax-stable-promotion-contract
feat(release): allow stable promotion with commits after the beta baseline
2026-07-21 17:47:53 +08:00
修雨 e154b4ecde fix(release): validate packaged version at runtime 2026-07-21 17:47:02 +08:00
zhengyubai f83c305749 feat(release): allow stable promotion with commits after the beta baseline
Stable releases previously required a byte-identical tree with the
promoted beta (only CHANGELOG.md could differ) and local releases had
to run exactly at the origin/main tip with an atomic main+tag push.
Together these froze main for the whole beta-to-stable window.

Relax both gates while keeping the beta soak mandatory:
- stable still requires an explicit delivered, non-withdrawn beta whose
  commit is an ancestor of the sealed release commit; the tree-identity
  drift check is removed
- local releases accept any clean sealed commit contained in
  origin/main history (any branch or detached HEAD) and push only the
  release tag; command-compatibility checks compare the sealed HEAD,
  matching CI
2026-07-21 18:35:59 +09:00
炳昱 0182060757 fix(skill): advertise group member event triggers 2026-07-21 17:05:43 +08:00
wxianfeng c9cf1cc9c1 feat(event): support multi-event consume 2026-07-21 16:59:36 +08:00
修雨 b898f5c987 Merge pull request #723 from DingTalk-Real-AI/codex/retry-npm-channel-verification
fix(release): wait for npm channel propagation
2026-07-21 15:56:48 +08:00
修雨 20750df20b fix(release): wait for npm channel propagation 2026-07-21 15:46:19 +08:00
修雨 749149b94a Merge pull request #721 from DingTalk-Real-AI/codex/release-v1.0.53
chore(release): prepare v1.0.53
2026-07-21 15:35:50 +08:00
修雨 e5c8ff9acd chore(release): prepare v1.0.53 2026-07-21 15:27:38 +08:00
修雨 706535b41e Merge pull request #717 from DingTalk-Real-AI/codex/fix-release-ref-fingerprint
fix(release): fingerprint allocated tag refs
2026-07-21 15:10:45 +08:00
DennisandClaude Opus 4.8 e15a2c4efb chore: remove shortcut eval artifacts containing real PII
These files were real-backend capture artifacts committed by mistake and
contain personal data — employee names/emails, mail subjects, conversation &
message IDs, contact userIds/org, and hardcoded real test-target IDs:

- docs/shortcut-real-read-results.json   (raw read responses)
- docs/shortcut-real-write-results.json  (raw write responses)
- docs/shortcut-comparison.html          (embeds the raw responses)
- scripts/run_shortcut_real_read_matrix.py (hardcoded real target IDs)

They are dev-only capture artifacts, not build/CI inputs — the checked-in
public_catalog_generated.go is committed and no workflow/Makefile references
them, so removal does not affect the build. The generator scripts under
scripts/ that read these JSONs are local dev tools; they should consume a
locally-provided, uncommitted capture instead.

Add .gitignore rules so these (and the untracked shortcut-gsb-eval.* variants)
can never be re-committed.

Note: this only removes them going forward. They remain in git history on
origin/main (commit 8687d68); scrubbing history requires a separate,
owner-approved filter-repo/force-push.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:09:47 +08:00
anxb 2e7e6a1010 feat: 组织大脑修复 2026-07-21 15:07:59 +08:00
修雨 9e88116a2d fix(release): fingerprint allocated tag refs 2026-07-21 14:55:11 +08:00
修雨 05a306148a Merge pull request #715 from DingTalk-Real-AI/codex/allow-optional-oss-mirror
fix(release): defer unprovisioned OSS mirror
2026-07-21 14:34:27 +08:00
修雨 0dcc796f4c fix(release): defer unprovisioned OSS mirror 2026-07-21 14:23:35 +08:00
SCzheng 3e792b1c86 Merge pull request #712 from PeterGuy326/codex/fix-local-release-cloud-seal-detection
fix(release): accept guarded local tag metadata
2026-07-21 12:48:59 +08:00
修雨 b9c822d49d fix(release): accept guarded local tag metadata 2026-07-21 12:24:57 +08:00
修雨 f9b9b83f48 Merge pull request #709 from DingTalk-Real-AI/codex/changelog-v1.0.53-beta.5
docs(changelog): seal v1.0.53-beta.5 notes
2026-07-21 11:50:21 +08:00
修雨 aa9e67e7c8 docs(changelog): seal v1.0.53-beta.5 notes 2026-07-21 11:41:58 +08:00
修雨 6c0cf3438b fix: address plugin review blockers 2026-07-21 11:33:03 +08:00
修雨 e3f30420fb fix: restore plugin overlay commands 2026-07-21 11:33:03 +08:00
修雨 16ff02903a Merge pull request #698 from wxianfeng/fix/event-token-lazy-resolution
fix(event): retry stream ticket once with rotated token after 401
2026-07-21 11:29:01 +08:00
修雨 65d3f2959c Merge branch 'main' into fix/event-token-lazy-resolution 2026-07-21 11:08:25 +08:00
anxb f88bc32259 feat: 接入组织大脑5 2026-07-21 10:44:57 +08:00
修雨 cb3087ba9b Merge pull request #707 from DingTalk-Real-AI/codex/cloud-release-withdrawal
ci: add cloud-native releases and cross-platform withdrawal
2026-07-21 10:38:18 +08:00
anxb f3cce5f49b Merge branch 'refs/heads/main' into feat/20260714_hrbrain 2026-07-21 10:37:36 +08:00
上官玄 5068cfdab8 fix: preserve transient retry semantics on truncated responses 2026-07-21 10:34:52 +08:00
xuan 3c81e5d47d Merge branch 'main' into fix/event-token-lazy-resolution 2026-07-21 10:31:01 +08:00
修雨 d93925a892 Merge branch 'main' into codex/cloud-release-withdrawal 2026-07-21 10:28:17 +08:00
修雨 faab9e0282 Merge pull request #700 from DingTalk-Real-AI/codex/ci-test-contract
ci: enforce complete Go test coverage
2026-07-21 10:20:01 +08:00
修雨 76d301268d ci: add cloud release and withdrawal workflows 2026-07-21 10:03:25 +08:00
anxb 2e389fe27d feat: 接入组织大脑4 2026-07-21 09:57:21 +08:00
修雨 7fddace8df ci: enforce complete Go test coverage 2026-07-21 09:41:18 +08:00
shangguanxuan.sgx 99e5a3cceb test: rename 401-refresh tests into TestCrossPlatformCoverage so platform gates count them
The macOS/Windows coverage gates only execute tests matching
^(TestAllShortcuts|TestCrossPlatformCoverage), so the 401 refresh-retry
tests added for this change were invisible to them, leaving 12 changed
statements uncovered (92.73% < 100%). Rename the 12 existing tests into
the TestCrossPlatformCoverage prefix and add a fetchTicketAttempt edge
test covering transport failures, retryable statuses, and missing
endpoint/ticket payload fields.
2026-07-20 22:35:15 +08:00
shangguanxuan.sgx 7e31043875 Merge remote-tracking branch 'upstream/main' into fix/event-portal-401-retry 2026-07-20 21:20:25 +08:00
shangguanxuan.sgx 55d7fbf59a test: close coverage gate gaps on transient auth recovery paths
The Coverage gate flagged 16 uncovered changed statements (90.6% < 100%):

- drop the unreachable handler error / nil response branches in
  runPortalTicketAttempt: makeHandler never fails, matching the pre-port
  portal loop on main
- cover portalStageError nil Error/Unwrap, the reconnect min/max clamp,
  and the acked backoff reset via an end-to-end reconnect test
- cover personalRetryLogError fallback when a token failure carries no
  structured HTTP status
- cover ClassifyRefreshFailure nil/net.Error/redirect branches, the nil
  HTTPStatusError message, and oauthExchangeDisplayError fallback
- cover the personal stream source ForceRefreshToken wiring end to end

Local gate now reports changed code coverage 100.0% (165 statements).
2026-07-20 21:00:21 +08:00
zhengyubai c0f4d21c05 fix(event): keep long-running sources alive across transient auth failures
Ported from 342d44efe (backup/event-token-lazy-resolution-pre-rewrite) and
adapted to the current in-place single 401 refresh+retry design:

- portal source: classify ticket/dial/read/ack failures via portalStageError
  and reconnect with backoff on retryable stages only (DisableReconnect for
  tests and one-shot callers); stage errors never leak response bodies
- personal/portal: transient token provider or refresh failures (network,
  408/429/5xx) go through the reconnect loop instead of killing the source;
  terminal failures (400/401/403) remain fatal
- personalRetryLogError: token resolution/refresh errors log only the
  structured HTTP status, never provider error details

Unlike the original commit, a rejected token is still retried once in place
after a successful refresh, and a second 401 stays fatal (single-refresh
guard agreed in review).
2026-07-20 18:37:23 +08:00
zhengyubai 660c908585 fix(auth): classify refresh failures and keep transient ones recoverable
Restored from the pre-rewrite branch head 342d44efe (backed up as
backup/event-token-lazy-resolution-pre-rewrite); the auth-layer changes
apply verbatim on the rebased branch.

- Add ClassifyRefreshFailure with structured HTTPStatusError so refresh
  failures split into transient (network, timeout, 408/429/5xx) and
  terminal (400/401/403) classes; unknown errors stay fatal.
- GetTokenSnapshot no longer marks a profile expired on transient
  refresh failures, so long-running sources can retry after backoff.
- postJSON returns HTTPStatusError keeping the response body out of the
  error string; the OAuth callback page HTML-escapes the sanitized
  exchange error instead of echoing raw server output.
- isInvalidGrantError also matches the preserved response body.
2026-07-20 18:09:15 +08:00
shangguanxuan.sgx 377ebc5e85 fix(event): classify personal ticket errors by status before reading body
A 401 whose error body failed mid-read (e.g. unexpected EOF) was wrapped
as retryable by the body-read path, letting the outer reconnect loop
re-enter fetchTicket and refresh again on every iteration, bypassing the
single refresh-retry guard.

Classify non-2xx responses by status first; the body is only drained
best-effort since it is never used for error reporting here. 401 stays
fatal regardless of body state, while 2xx body-read failures remain
retryable transport errors.
2026-07-20 17:57:27 +08:00
修雨 076d77da8e Merge pull request #699 from DingTalk-Real-AI/codex/ci-coverage-100
ci: shorten workflow name and require 100% changed-code coverage
2026-07-20 17:44:56 +08:00
shangguanxuan.sgx 2eca203e74 fix(event): retry stream ticket once with rotated token after 401
Portal and personal ticket requests now perform a single controlled
refresh + retry inside the production chain when the server rejects the
resolved access token with HTTP 401:

- Add optional ForceRefreshToken callback to PortalTicketConfig and
  PersonalConfig. It receives the exact rejected token so the app-level
  compare-and-refresh (ForceRefreshRejectedToken) can dedupe concurrent
  rotations, and returns the fresh token.
- requestPortalTicket / fetchTicket retry the ticket request once with
  the rotated token directly instead of surfacing an error and hoping an
  outer loop retries; a second 401 stays fatal to prevent refresh loops.
- Refresh failures keep both the original 401 and the refresh error via
  errors.Join; empty rotated tokens fail fast before hitting the server.
- Wire forceRefreshRejectedAccessToken into event consume (portal) and
  personal stream sources; resolveSourceAccessToken strict semantics are
  unchanged (provider errors still propagate, no static-token fallback).
- Tests: full DingtalkSource.Start -> startPortalTicket chain
  (401 -> refresh -> ticket ok -> WebSocket event), rotated-token reuse,
  refresh failure, nil-callback compatibility, second-401 fatality, and
  app-level wiring.
2026-07-20 17:39:33 +08:00
修雨 6e070a7e24 ci: tighten PR coverage gate 2026-07-20 17:14:35 +08:00
炳昱 b234015e7f feat(event): add group member lifecycle events 2026-07-20 16:10:47 +08:00
修雨 e867abd03c Merge pull request #687 from shangguanxuan633-lab/codex/auth-token-manager-complete
fix(auth): unify token resolution and recover rejected tokens
2026-07-20 15:07:07 +08:00
修雨 9d89965de9 Merge branch 'main' into codex/auth-token-manager-complete 2026-07-20 14:53:06 +08:00
修雨 41088bb965 fix(release): derive OSS_REGION for ossutil v2 V4 signing (#692)
ossutil 2.x signs requests with V4 and refuses to run without an
explicit region, so the OSS mirror sync would fail in CI even with
valid credentials. Derive OSS_REGION from the endpoint host
(including -internal variants) and fail fast when it cannot be
derived.
2026-07-20 14:51:41 +08:00
修雨 8259116f15 test(auth): isolate Windows keychain packages 2026-07-20 14:33:17 +08:00
上官玄 9ec1fa0638 test(auth): use synthetic log redaction sentinel 2026-07-20 14:22:18 +08:00
修雨 9afd3be79b Merge branch 'main' into codex/auth-token-manager-complete 2026-07-20 14:15:48 +08:00
修雨 67da5019e3 Merge pull request #689 from DingTalk-Real-AI/codex/fix-beta4-channel-repair
fix(release): recover immutable mirror channels safely
2026-07-20 14:07:54 +08:00
anxb dd112a845e feat: 接入组织大脑3 2026-07-20 14:02:44 +08:00
shangguanxuan.sgx b0ded7deb8 fix(auth): retry rejected access tokens safely 2026-07-20 13:37:18 +08:00
shangguanxuan.sgx 22905fc41e fix(auth): unify access token resolution 2026-07-20 12:17:04 +08:00
wxianfeng ca6e520610 chore(event): default personal events to pre-release 2026-07-20 11:47:55 +08:00
修雨 ec9ff653fc fix(release): recover immutable mirror channels safely 2026-07-20 11:35:32 +08:00
wxianfeng b731050dad feat(event): add all-message and group lifecycle events 2026-07-20 11:32:46 +08:00
修雨 876cf8e958 Merge pull request #685 from shangguanxuan633-lab/codex/fix-oauth-coverage-fixture-isolation-20260720
test(auth): isolate OAuth coverage fixtures
2026-07-20 10:41:05 +08:00
wxianfeng bb2dd2ba76 Merge remote-tracking branch 'upstream/main' into feature/dws-event-im-phase3 2026-07-20 10:08:19 +08:00
修雨 1c5ed6646e Merge branch 'main' into codex/fix-oauth-coverage-fixture-isolation-20260720 2026-07-20 09:57:51 +08:00
anxb c0cb81c12b Merge branch 'refs/heads/main' into feat/20260714_hrbrain 2026-07-20 09:56:49 +08:00
修雨 80549a80e0 Merge pull request #665 from DingTalk-Real-AI/agent/changelog-fast-path
ci: align Code Admission gates and trusted changelog fast path
2026-07-20 09:34:43 +08:00
上官玄 883d416d83 test(auth): isolate OAuth coverage fixtures 2026-07-20 07:29:24 +08:00
修雨 25c70aeb24 ci: align admission gates and changelog fast path 2026-07-19 23:59:48 +08:00
修雨 6cfa9e3afb ci: fast-path changelog-only pull requests 2026-07-19 23:11:04 +08:00
修雨 544a91e994 Merge pull request #667 from DingTalk-Real-AI/codex/repair-gitee-dispatch
ci(release): add dispatch repair-gitee job to mirror an existing release
2026-07-19 23:01:37 +08:00
修雨 024d487a22 Merge pull request #682 from DingTalk-Real-AI/automation/homebrew-beta-v1.0.53-beta.4
chore: update Homebrew beta formula for v1.0.53-beta.4
2026-07-19 22:52:31 +08:00
修雨 6b50cc41c5 ci(release): add dispatch repair-gitee job to mirror an existing release
The push-triggered mirror-gitee-release job consumes the same run's
finalized-release-dist artifact, so it cannot mirror a tag that was
already published — including one delivered by a recovery dispatch such
as v1.0.53-beta.3. Add a workflow_dispatch repair-gitee job (input
mirror_gitee_version) that re-derives the asset set from the immutable
GitHub Release, verifies it byte-for-byte via checksums, and runs
sync-to-gitee.sh. Guarded to the official repo + default branch and
gated by the existing Gitee secrets.
2026-07-19 21:51:35 +08:00
修雨 11e50662f9 Merge pull request #683 from DingTalk-Real-AI/codex/fix-event-bus-shutdown-race
fix(event): serialize bus shutdown with accept loop
2026-07-19 21:39:53 +08:00
修雨 29abdb6e79 fix(event): serialize bus shutdown with accept loop
Wait for the accept loop to stop before waiting for connection handlers, and track accepted connections before publishing handlers. This removes the WaitGroup Add/Wait race caught by PR #667 CI and follows up the event bus introduced in #589.
2026-07-19 21:21:16 +08:00
DWS Release Bot bc587ddd91 chore: update beta formula for v1.0.53-beta.4 2026-07-19 13:21:07 +00:00
修雨 6196e2565e Merge pull request #678 from DingTalk-Real-AI/fix/release-draft-asset-verify
fix(release): bind draft publication to release ID
2026-07-19 19:52:09 +08:00
修雨 609d56305e fix(release): bind draft publication to release ID 2026-07-19 12:22:44 +08:00
玉澜 51dc237d8a feat: declarative LeafSpec command framework + schema generation/consumption separation
== LeafSpec command framework (internal/helpers/leaf.go) ==
Declarative command construction: LeafSpec/LeafFlag/NewLeafCommand with
Call (pluggable dispatch), LeafInt, PostMount, Trim, Validate. Collapses
per-command hand-written required validation, alias/env fallback, value
transform, and toolArgs assembly into one declarative path.

== devapp migration (28/31 commands) ==
All MCP-direct devapp leaf commands migrated to LeafSpec. Factories
(devAppCall/devAppCallCursor/devAppMeta) fold 33 repeated closures.
fakeDevAppRunner asserts toolArgs for every migrated command. 4 complex
commands (delete/robot submit/result/config) kept hand-written.

== Schema generation/consumption separation ==
- gen.go: isolated //go:generate pragmas from business code.
- command_meta.go: ResolveMeta(cliPath) -> CommandMeta{Identity,Safety,Selection}.
- command_safety.go: SafetyForCLIPath + RenderSafetyAnnotation; safety metadata
  flows from embedded catalog into --help output.
- calendar.go HelpFunc fix: delegates to root HelpFunc at help-time.
- schema_catalog_structure.go: closed catalog structure validation gate.

== Registry + catalog per-product sharding ==
schema_command_registry and schema_catalog split into per-product shards,
eliminating concurrent-PR merge conflicts on these files.

== MCP metadata refresh tool ==
cmd/fetch_mcp_metadata: iterates 26 MCP server endpoints, merges with previous
data for cross-server interface_ref. make fetch-mcp-metadata target.

== AGENTS.md ==
Documents the generation/consumption split.

Verified: make policy exit 0, drift zero, all tests pass.
2026-07-19 09:38:43 +08:00
修雨 e69a1084a7 Merge pull request #675 from DingTalk-Real-AI/codex/fix-release-skip-propagation
fix(release): prevent skipped publication false greens
2026-07-18 12:11:37 +08:00
修雨 978ee6e636 fix(release): fail closed on skipped publication 2026-07-18 11:34:34 +08:00
wxianfeng 049af9fc30 Merge remote-tracking branch 'upstream/main' 2026-07-17 17:38:04 +08:00
wxianfeng d19a15a6ed Merge branch 'main' of github.com:wxianfeng/dingtalk-workspace-cli
# Conflicts:
#	.github/badges/coverage.svg
2026-07-17 17:36:26 +08:00
修雨 987c63d99c Merge pull request #649 from PeterGuy326/codex/fast-quality-release
fix(release): add fast guarded release and recovery paths
2026-07-17 17:35:30 +08:00
修雨 e565746fb7 Merge remote-tracking branch 'origin/main' into codex/fast-quality-release
# Conflicts:
#	CHANGELOG.md
2026-07-17 17:19:02 +08:00
修雨 4f76d7cb4c fix(release): verify release token capabilities 2026-07-17 17:18:12 +08:00
修雨 e94f230236 Merge pull request #668 from DingTalk-Real-AI/release/changelog-v1.0.53-beta.4
docs(changelog): seal v1.0.53-beta.4
2026-07-17 17:08:20 +08:00
修雨 5242a0e1b1 docs(changelog): promote Unreleased into v1.0.53-beta.4
Seal the accumulated personal IM event subscription expansion and the
flattened event consume output (#651) into a dated beta.4 section.
2026-07-17 16:53:17 +08:00
修雨 c3e57b874b fix(ci): satisfy release workflow shellcheck 2026-07-17 16:13:16 +08:00
修雨 fa558372d2 fix(release): add fast guarded recovery path 2026-07-17 16:13:15 +08:00
修雨 ec9ae33a43 Merge pull request #651 from wxianfeng/feat/dws-event-im-2phase
feat(event): expand personal IM events and flatten output
2026-07-17 16:04:50 +08:00
修雨 2b49a2f365 Merge pull request #662 from LastdianXuan/agent/fix-eval-confirmed-bugs
fix: address confirmed CLI contract issues from v1.0.53 evaluation
2026-07-17 15:46:40 +08:00
修雨 ae9b14e536 Merge main into feat/dws-event-im-2phase 2026-07-17 15:46:38 +08:00
修雨 996c4ab250 fix: propagate structured output write failures 2026-07-17 15:04:13 +08:00
修雨 cf36ccb46e Merge remote-tracking branch 'origin/main' into codex/pr662-current 2026-07-17 14:56:36 +08:00
修雨 361115956f Merge pull request #648 from LastdianXuan/agent/fix-chat-update-icon-media-id
fix: accept uploaded media IDs for group icons
2026-07-17 14:50:51 +08:00
anxb 2b76047164 Merge branch 'refs/heads/main' into feat/20260714_hrbrain 2026-07-17 14:41:05 +08:00
anxb 241444e992 feat: 接入组织大脑2 2026-07-17 14:20:36 +08:00
SCzheng e82574cdde Merge pull request #661 from DingTalk-Real-AI/codex/changelog-v1.0.53-beta.3
docs(changelog): prepare v1.0.53-beta.3
2026-07-17 14:08:44 +08:00
修雨 b2f917aa47 docs(changelog): prepare v1.0.53-beta.3 2026-07-17 13:57:56 +08:00
修雨 7cb0398bae Merge pull request #653 from audanye-sudo/feat/multi-account-profile-support
feat(auth): support multiple accounts in one organization
2026-07-17 13:45:34 +08:00
张卓澎 91bd7c7802 fix: emit structured audit verification output 2026-07-17 13:44:18 +08:00
张卓澎 5a8376ac0f fix: keep JSON command output machine-readable 2026-07-17 13:44:18 +08:00
张卓澎 31c984e18c fix: use MCP group ID key for message lists 2026-07-17 13:44:18 +08:00
修雨 69c0eb1a49 Merge remote-tracking branch 'origin/main' into codex/pr648-current 2026-07-17 12:25:43 +08:00
张卓澎 82e98d98a3 test: cover group icon validation on all platforms 2026-07-17 12:15:22 +08:00
修雨 ef509ecdeb Merge pull request #654 from LastdianXuan/codex/fix-aitable-import-file-size
fix(aitable): require import upload file size
2026-07-17 12:05:04 +08:00
张卓澎 8a7e1c7be7 fix: accept uploaded media IDs for group icons 2026-07-17 12:01:25 +08:00
wxianfeng f59be6c19a fix(event): address PR review gates 2026-07-17 11:57:27 +08:00
audanye-sudo fbc2575c93 fix(auth): address multi-account review feedback 2026-07-17 11:45:03 +08:00
修雨 58cb4789cd test(aitable): cover import upload in owning package 2026-07-17 11:31:53 +08:00
修雨 63b5fe3143 Merge remote-tracking branch 'origin/main' into codex/pr654-coverage-fix 2026-07-17 11:26:10 +08:00
修雨 41a65f268f Merge pull request #646 from DingTalk-Real-AI/bugfix-im-shortcut-ai-tag
fix: add AI tag to IM send shortcuts
2026-07-17 11:15:46 +08:00
修雨 03796388c3 test(shortcut): cover platform compatibility paths 2026-07-17 11:03:09 +08:00
audanye-sudo 69b31da4ba fix(auth): gate identity diagnostics behind opt-in 2026-07-17 10:47:24 +08:00
修雨 833d0cc05e Merge main into bugfix-im-shortcut-ai-tag
Resolve the shortcut catalog constraint migration and preserve both fake caller response modes.
2026-07-17 10:37:55 +08:00
张卓澎 b7cbef1c6f fix(aitable): require import upload file size 2026-07-17 10:23:40 +08:00
修雨 bdc480cf49 Merge pull request #647 from DingTalk-Real-AI/automation/homebrew-beta-v1.0.53-beta.2
chore: update Homebrew beta formula for v1.0.53-beta.2
2026-07-17 10:01:30 +08:00
audanye-sudo 43eaadcf07 chore(docs): drop internal profile planning artifacts
Remove the internal design and execution plans from docs/plans and docs/superpowers so the public pull request contains only implementation and maintained user-facing documentation.

The four files were introduced only on this branch. No runtime code, generated output, README, CHANGELOG, or Skill documentation references them.

Verification:
- Confirmed origin/main does not contain the files.
- Confirmed no remaining repository references.
- Ran git diff --check before committing.
2026-07-17 09:39:32 +08:00
修雨 f8e1be5970 fix(homebrew): use sealed GitHub beta checksums 2026-07-17 09:38:10 +08:00
Dennis 8d1ccd1b98 fit chat shortcut aliases 2026-07-17 09:36:28 +08:00
Dennis c84b5d05f4 fix chat search shortcut keyword alias 2026-07-17 09:29:06 +08:00
audanye-sudo 5224d9c527 fix(auth): harden multi-account profile compatibility
Preserve manual-token defaults across explicit profile refreshes and selective logout while keeping legacy marker behavior compatible.

Make profile login, refresh, switch, and logout writes rollback-safe; reject unsupported future profile versions before remote side effects; and prevent cross-profile token fallback.

Forward token overrides through usage recording, use the newly authenticated identity for post-login authorization, distinguish unavailable profile state, and propagate Windows registry deletion failures.
2026-07-17 02:11:45 +08:00
audanye-sudo e9360fe11b docs(auth): document multi-account profile compatibility
Describe exact and friendly profile selector forms, deterministic organization-current behavior, profile listing semantics, and single-account or organization logout examples.

Update both mono and multi skills so agents avoid implicit account selection and request corpId:userId when an organization is ambiguous.

Record the compatibility design and implementation plan, including profiles v2 migration, legacy command support, storage mirrors, risk controls, and end-to-end acceptance criteria.
2026-07-17 00:57:12 +08:00
audanye-sudo 2d143589f8 feat(cli): add deterministic multi-account profile workflows
Accept corpId:userId and friendly organization/account selectors across global --profile, profile switch/use, event child processes, and multi-profile command execution.

List every local account in storage order with live identity-token status, preserve exact current and previous identities, and require explicit selection when an organization has no deterministic current account.

Extend auth logout to remove one exact account, every account in one organization, or all local accounts while revoking each token with its persisted credentials.

Use in-memory login tokens for identity enrichment before persistence, refresh generated Schema artifacts, and cover the complete CLI flow with isolated beta.3 end-to-end tests.
2026-07-17 00:56:57 +08:00
audanye-sudo 93854178e3 feat(auth): support exact multi-account profile identities
Store DingTalk credentials in corpId:userId identity slots while retaining organization and legacy mirrors for forward compatibility.

Resolve organization, account, friendly-name, current, previous, and deletion selectors without silently choosing among ambiguous accounts.

Make identity tokens the source of truth, serialize migration and refresh reads, reject unsafe mirror recovery, and sweep orphan token entries during reset.

Persist token source and client ID for exact remote revocation, require user identity before first-login persistence, and add cross-platform regression coverage for migration, deletion, refresh, and keychain failures.
2026-07-17 00:56:43 +08:00
wxianfeng c7c9a6f926 Merge remote-tracking branch 'upstream/main' into feat/dws-event-im-2phase
# Conflicts:
#	CHANGELOG.md
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog.json
2026-07-16 23:20:35 +08:00
修雨 669518682c chore: update beta formula for v1.0.53-beta.2 2026-07-16 19:05:16 +08:00
修雨 642e676f79 Merge pull request #642 from DingTalk-Real-AI/codex/parallelize-ci-jobs
ci: parallelize PR test and coverage jobs
2026-07-16 18:57:29 +08:00
玉澜 52045fb290 Merge upstream/main into agent/sync-wukong-multi-skill 2026-07-16 18:17:17 +08:00
wxianfeng a0224e1cbd fix(event): refine schema contracts and metadata 2026-07-16 17:45:03 +08:00
修雨 da7b490e08 ci: include app subpackages in race shard 2026-07-16 17:05:43 +08:00
修雨 e2ab422787 ci: parallelize PR test and coverage jobs 2026-07-16 16:58:10 +08:00
修雨 4d05ea4fc1 Merge pull request #628 from PeterGuy326/codex/fix-windows-portable-export-contract
fix(auth): reject unsupported Windows portable export
2026-07-16 16:22:16 +08:00
修雨 e3bbb33c18 Merge remote-tracking branch 'origin/main' into pr628-merge
# Conflicts:
#	CHANGELOG.md
2026-07-16 16:19:31 +08:00
Dennis 0d81f061d8 fix shortcut IM AI message tag 2026-07-16 15:39:39 +08:00
xuan 1c09115bd6 Merge pull request #645 from PeterGuy326/codex/fix-delivered-stable-recovery-proof
fix(release): recognize reviewed stable recovery
2026-07-16 15:39:39 +08:00
修雨 a0a4b5dfbe fix(release): recognize reviewed stable recovery 2026-07-16 15:36:23 +08:00
修雨 3f653d9da0 Merge pull request #644 from DingTalk-Real-AI/codex/fix-v1.0.53-beta.2-changelog-gate
docs(changelog): unblock v1.0.53-beta.2 preflight
2026-07-16 15:19:27 +08:00
wxianfeng cd22cfb530 chore(event): switch personal events to production 2026-07-16 15:04:04 +08:00
修雨 6e0917a3ed docs(changelog): avoid beta placeholder false positive 2026-07-16 15:03:24 +08:00
修雨 b5f431ba51 Merge pull request #641 from DingTalk-Real-AI/codex/changelog-v1.0.53-beta.2
docs(changelog): prepare v1.0.53-beta.2
2026-07-16 14:49:42 +08:00
修雨 950de23e74 Merge branch 'main' into codex/fix-windows-portable-export-contract 2026-07-16 14:31:53 +08:00
修雨 0108b1ca28 docs(changelog): prepare v1.0.53-beta.2 2026-07-16 14:27:52 +08:00
wxianfeng adc528c206 Merge remote-tracking branch 'upstream/main' into feat/dws-event-im-2phase
# Conflicts:
#	.github/badges/coverage.svg
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog.json
#	internal/event/consume/formatter.go
2026-07-16 14:27:39 +08:00
修雨 34aad4596c Merge pull request #638 from DingTalk-Real-AI/codex/feat-contact-enterprise-onboarding
feat(contact): add enterprise onboarding commands
2026-07-16 14:15:37 +08:00
修雨 07d2e4597e test(auth): keep portable fixtures platform-neutral 2026-07-16 13:55:58 +08:00
修雨 57d753cd1d Merge remote-tracking branch 'origin/main' into codex/pr628-main-sync-20260716
# Conflicts:
#	CHANGELOG.md
#	internal/app/auth_command.go
#	internal/app/auth_command_test.go
#	internal/app/config_test.go
#	internal/app/root.go
#	internal/app/skill_setup_test.go
#	internal/app/timing_test.go
#	internal/auth/portable_store.go
#	internal/logging/logger.go
2026-07-16 13:42:38 +08:00
修雨 9e12da0219 Merge remote-tracking branch 'origin/main' into codex/feat-contact-enterprise-onboarding 2026-07-16 13:18:04 +08:00
修雨 7ca9ebeb57 Merge pull request #625 from PeterGuy326/codex/test-coverage-100-v2
fix: harden auth and reentrant CLI with 100% coverage
2026-07-16 13:13:27 +08:00
修雨 d202d58963 Merge remote-tracking branch 'origin/main' into codex/pr628-main-sync-20260716 2026-07-16 12:40:55 +08:00
修雨 4068847742 Merge remote-tracking branch 'origin/main' into codex/test-coverage-100-v2 2026-07-16 12:40:55 +08:00
修雨 536fd66029 Merge pull request #620 from PeterGuy326/codex/release-guardrails-v1
feat(release): add guarded beta and stable pipeline
2026-07-16 12:38:36 +08:00
修雨 a519a2ff8a fix(contact): validate enterprise onboarding writes 2026-07-16 12:38:05 +08:00
修雨 270771de0c test(contact): include onboarding cases in coverage gate 2026-07-16 12:27:25 +08:00
修雨 3ec8320680 feat(contact): add enterprise onboarding commands 2026-07-16 12:27:24 +08:00
修雨 2c4d539a02 Merge remote-tracking branch 'origin/main' into codex/test-coverage-100-v2 2026-07-16 12:26:53 +08:00
修雨 59abfc7dfd Merge remote-tracking branch 'origin/main' into codex/pr620-fix 2026-07-16 12:24:23 +08:00
修雨 a676f3d606 Merge pull request #616 from typefield/agent/fix-calendar-rooms-help
fix: correct calendar rooms help metavar
2026-07-16 12:22:54 +08:00
修雨 c79c5dfffe test(windows): isolate portable import side effects 2026-07-16 12:21:27 +08:00
修雨 681f2db87a Merge origin/main into codex/fix-windows-portable-export-contract 2026-07-16 12:12:41 +08:00
修雨 7dc5b6f2ed test(coverage): exercise portable auth platform guards 2026-07-16 12:12:26 +08:00
修雨 f9b37486fd Merge remote-tracking branch 'origin/main' into codex/pr620-fix 2026-07-16 12:10:39 +08:00
修雨 ad6e22d8cf fix(coverage): keep keychain GCM seam in profiled file 2026-07-16 12:09:38 +08:00
修雨 05c0f1af1e Merge main@f56de38b into agent/fix-calendar-rooms-help 2026-07-16 12:08:28 +08:00
修雨 605d9360fc Merge pull request #636 from DingTalk-Real-AI/automation/homebrew-beta-v1.0.53-beta.1
chore: update Homebrew beta formula for v1.0.53-beta.1
2026-07-16 12:05:23 +08:00
修雨 fb4e6a0fdb Merge origin/main into codex/fix-windows-portable-export-contract 2026-07-16 12:05:22 +08:00
修雨 2b715e35ad test(calendar): include help check in platform coverage 2026-07-16 12:04:31 +08:00
修雨 f56de38b79 Merge pull request #634 from typefield/feat/dws-devapp-get-by-appkey
feat(devapp): support get by app-key for app detail lookup
2026-07-16 12:01:17 +08:00
修雨 c2e5fec967 test(calendar): cover rooms help in helpers package 2026-07-16 11:59:31 +08:00
修雨 f0642c73d7 fix: preserve crypto errors and document behavior fixes 2026-07-16 11:56:44 +08:00
修雨 c1bbc183ad Merge pull request #560 from shangguanxuan633-lab/codex/pat-org-policy-denied-error
fix(pat): classify org policy denials
2026-07-16 11:56:09 +08:00
修雨 579cd86c6c Merge origin/main into agent/fix-calendar-rooms-help 2026-07-16 11:54:18 +08:00
玉澜andCursor b6c85f31c4 fix(devapp): cover get --app-key in platform coverage gate
Rename the get locator tests to TestCrossPlatformCoverage* so macOS/Windows changed-code coverage actually executes them.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-16 11:40:55 +08:00
修雨 88d82f201f Merge origin/main into codex/fix-windows-portable-export-contract
# Conflicts:
#	CHANGELOG.md
2026-07-16 11:37:36 +08:00
玉澜andCursor b6df97fba1 fix(devapp): regenerate schema for get --app-key
Keep embedded catalog/bindings in sync with the new cobra flag so schema help-flag and policy checks pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-16 11:35:53 +08:00
玉澜andCursor a9ee1cd24d feat(devapp): support get by app-key for app detail lookup
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-16 11:34:54 +08:00
修雨 2614d225f2 Merge remote-tracking branch 'origin/main' into codex/sync-pr636-main 2026-07-16 11:32:45 +08:00
修雨 e4faa0daa8 Merge remote-tracking branch 'origin/main' into codex/sync-pr560-main 2026-07-16 11:32:44 +08:00
修雨 975f378559 Merge pull request #632 from shangguanxuan633-lab/codex/jq18-schema-policy-portability
ci(schema): support jq 1.8 policy evaluation
2026-07-16 11:27:23 +08:00
修雨 28e83dfa57 Merge pull request #637 from DingTalk-Real-AI/codex/fix-devapp-interface-baseline
fix(ci): sync public interface baseline
2026-07-16 11:26:51 +08:00
修雨 7f07c22cd5 test: include coverage fixtures in native gates 2026-07-16 10:58:56 +08:00
修雨 089a661124 fix(ci): sync public interface baseline 2026-07-16 10:58:46 +08:00
shangguanxuan.sgx d2f7c667e2 Merge upstream/main into codex/pat-org-policy-denied-error 2026-07-16 10:56:15 +08:00
修雨 ff4961ebbe fix(release): harden mirror credential transport 2026-07-16 10:56:04 +08:00
修雨 68d3c76d2d Merge remote-tracking branch 'origin/main' into codex/test-coverage-100-v2
# Conflicts:
#	internal/helpers/todo.go
2026-07-16 10:41:59 +08:00
修雨 3811a0d82e test(pat): include denial paths in platform coverage 2026-07-16 10:39:50 +08:00
修雨 e00019039c fix(auth): preserve force validation before support guard 2026-07-16 10:36:51 +08:00
修雨 bc332133a2 fix(ci): sync public interface baseline 2026-07-16 10:32:38 +08:00
DWS Release Bot 3fdf06fb51 chore: update beta formula for v1.0.53-beta.1 2026-07-16 10:31:05 +08:00
修雨 ede677e413 fix(ci): align interface coverage and baseline 2026-07-16 10:26:19 +08:00
修雨 b5abe6d328 fix(release): preserve latest main integration 2026-07-16 10:26:19 +08:00
修雨 51f531c3fd fix(release): isolate helper variables 2026-07-16 10:26:19 +08:00
修雨 8d21510aec fix(ci): enforce clean release workflows 2026-07-16 10:26:19 +08:00
修雨 1a51f3a8da fix(release): pin goreleaser to pushed tag 2026-07-16 10:26:19 +08:00
修雨 4d284c3740 fix(release): rebase guardrails onto sealed main 2026-07-16 10:26:18 +08:00
修雨 fe5f484c29 fix(ci): integrate code admission dependencies 2026-07-16 10:26:18 +08:00
修雨 8f4ab176a8 ci: add code admission gate (#53)
* ci: add code admission gate

* ci: fix fork release baseline

(cherry picked from commit 7ff2f3a5f0435209908822e141a6355fc6fa4aa6)
2026-07-16 10:26:18 +08:00
修雨 d288820b64 fix(release): preserve unreleased changelog entries (#55)
(cherry picked from commit e7989c1bb03ec461c638de89337d175f8ef115e4)
2026-07-16 10:26:18 +08:00
修雨 22d19863fb feat(release): add guarded prerelease and stable pipeline (#54)
* feat(release): add guarded prerelease and stable pipeline

* feat(release): add guided dws-release entry

(cherry picked from commit f7fa7b78f325f3574f0487862fc3e65bba5cdc96)
2026-07-16 10:26:18 +08:00
修雨 c02df07b64 Merge origin/main into codex/test-coverage-100-v2 2026-07-16 10:25:38 +08:00
修雨 e615bd433c fix: surface invalid sheet and todo targets (#623)
* fix: surface invalid sheet and todo targets

* docs: record invalid target fixes

* fix: expose todo attachment listing schema

* fix: make Windows helper coverage portable

* test: run quality regressions in platform coverage
2026-07-16 10:24:58 +08:00
修雨 e26e96eadc Merge remote-tracking branch 'origin/main' into codex/pr560-fix
# Conflicts:
#	CHANGELOG.md
2026-07-16 10:20:06 +08:00
anxb 309f833f15 Merge branch 'refs/heads/main' into feat/20260714_hrbrain 2026-07-16 10:19:44 +08:00
anxb 115cce7308 feat: 接入组织大脑 2026-07-16 10:18:05 +08:00
修雨 5b746f610a fix(pat): short-circuit organization policy denials 2026-07-16 10:17:18 +08:00
修雨 74fa24ee1e fix(auth): reject unsupported Windows portable import 2026-07-16 10:13:41 +08:00
修雨 474ce88d47 fix(ci): harden CLI smoke and Schema compatibility gates (#629)
* fix(ci): harden PR gate enforcement

* fix(schema): allow compatible positional evolution
2026-07-16 09:59:21 +08:00
修雨 6a0cdbc323 fix: address coverage review follow-ups 2026-07-16 09:57:52 +08:00
修雨 b35d67b811 Merge pull request #592 from DingTalk-Real-AI/feature/shortcut
feat(shortcut): declarative shortcut layer for DingTalk MCP (366 commands)
2026-07-16 09:07:38 +08:00
修雨 397654890e fix(auth): isolate concurrent secure writes 2026-07-16 01:27:58 +08:00
修雨 a945663674 test: cover platform-specific coverage gaps 2026-07-16 00:49:16 +08:00
修雨 a14525ed1d fix(auth): isolate concurrent secure writes 2026-07-16 00:45:21 +08:00
修雨 816c356bbf docs(changelog): record shortcut command layer 2026-07-16 00:35:47 +08:00
修雨 f28dd6ee07 test(event): wait for personal source before reading logs 2026-07-16 00:28:28 +08:00
修雨 765338eb5b fix(audit): initialize writer at execution boundary 2026-07-16 00:16:39 +08:00
修雨 0201340b28 fix: close reentrant CLI file handles 2026-07-16 00:09:17 +08:00
修雨 0bd767a3fe fix(ci): serialize authoritative coverage measurement 2026-07-15 23:56:31 +08:00
修雨 e75df36dfc test: wait for event bus readiness 2026-07-15 23:54:44 +08:00
修雨 648d604757 test: preserve complete helper coverage after merge 2026-07-15 23:38:37 +08:00
修雨 42627e769e fix(ci): keep platform coverage profiles bounded 2026-07-15 23:21:49 +08:00
修雨 3b22bb4994 fix(lint): normalize agent hint error text 2026-07-15 23:18:03 +08:00
修雨 d78de010ff Merge remote-tracking branch 'origin/main' into codex/test-coverage-100-v2
# Conflicts:
#	internal/cli/stdin_test.go
#	internal/helpers/atomicwrite_test.go
#	internal/helpers/connect_agent_options_test.go
#	internal/helpers/connect_codex_appserver_test.go
#	internal/helpers/connect_daemon_test.go
#	internal/helpers/connect_lock.go
#	internal/helpers/doc.go
2026-07-15 23:14:16 +08:00
修雨 6c192c2bf4 Merge remote-tracking branch 'origin/main' into codex/fix-pr-592-merge-gates
# Conflicts:
#	internal/cli/schema_agent_metadata/index.json
#	internal/cli/schema_agent_metadata_audit.json
#	internal/cli/schema_catalog.json
2026-07-15 23:10:37 +08:00
修雨 be5ce782b6 fix(shortcut): close review and CI gaps 2026-07-15 23:05:04 +08:00
修雨 de35b08c8c test: make coverage fixtures portable on Windows 2026-07-15 23:02:20 +08:00
修雨 e1a50f08a6 Merge pull request #624 from LastdianXuan/codex/sync-wukong-sheet-import
feat: sync Sheet import and Aitable workflow writes
2026-07-15 22:55:03 +08:00
修雨 d14ce3c8d2 docs(changelog): record sheet import and workflow writes 2026-07-15 22:45:18 +08:00
修雨 e0dc26c8c7 test: stabilize Windows native coverage 2026-07-15 22:19:49 +08:00
wxianfeng a2f1e79603 Merge remote-tracking branch 'upstream/main' into feat/dws-event-im-2phase
# Conflicts:
#	internal/cli/cobra_schema_test.go
#	skills/mono/references/products/event.md
#	skills/multi/dingtalk-event/SKILL.md
#	skills/multi/dingtalk-event/references/event-im.md
2026-07-15 20:36:56 +08:00
shangguanxuan.sgx adc87a92e4 ci(schema): support jq 1.8 policy evaluation 2026-07-15 18:55:19 +08:00
SCzheng b876b9b4ae Merge pull request #626 from sczheng189/codex/optimize-policy-script-builds
refactor(policy): reuse built binaries
2026-07-15 18:53:48 +08:00
张卓澎 82c6bcfcbf test(windows): avoid POSIX permission assumption 2026-07-15 18:31:56 +08:00
张卓澎 48a79b17c6 fix(sheet): expose import action to agent schema 2026-07-15 18:21:30 +08:00
修雨 d23910ce35 test: isolate portable auth coverage on Windows 2026-07-15 17:50:28 +08:00
修雨 084188c7ac test: stabilize native coverage gates 2026-07-15 17:40:53 +08:00
Dennis 9b44eeb5b0 Merge origin/main into feature/shortcut 2026-07-15 17:38:58 +08:00
修雨 1822b82232 test(logging): clarify terminal replacement coverage 2026-07-15 17:23:12 +08:00
修雨 44403e4d23 ci: retrigger pull request checks 2026-07-15 17:20:14 +08:00
修雨 ec29dc0e22 fix(logging): make file logger close terminal 2026-07-15 17:16:53 +08:00
修雨 9f0966c05a test: stabilize cross-platform coverage CI 2026-07-15 17:07:51 +08:00
修雨 bf105d3d29 fix(logging): close replaced file logger 2026-07-15 17:03:13 +08:00
Dennis 6de77f4c34 docs: present shortcut catalog without hidden release wording
Replace release-hidden terminology with a generated public shortcut catalog, remove include-hidden discovery, and keep real-test followups as an internal CR artifact.
2026-07-15 17:02:42 +08:00
Dennis 00f1379874 docs: integrate visible shortcuts into skills
Generate product skill shortcut sections from the shortcut registry and release-hidden list so agents see the current public shortcut surface instead of only a hidden-command warning.
2026-07-15 16:54:21 +08:00
修雨 48681eb41c test: isolate app audit environment 2026-07-15 16:53:55 +08:00
修雨 5e41b8a6e8 test(windows): make app coverage portable 2026-07-15 16:45:57 +08:00
Dennis 8687d68567 feat: gate unverified shortcuts for release
Hide shortcuts that did not pass real testing from public help/list discovery while keeping commands available for internal retest.

Record real shortcut inputs/outputs, backend issue summaries, next-release hidden list, and refresh skill guidance to mirror the lark-cli shortcut integration pattern.
2026-07-15 16:45:51 +08:00
张卓澎 bf74159737 fix(windows): make helper coverage tests portable 2026-07-15 16:34:45 +08:00
修雨 296e9a73f0 fix(auth): reject unsupported Windows portable export 2026-07-15 16:04:17 +08:00
修雨 3929719b51 Merge remote-tracking branch 'origin/main' into codex/test-coverage-100-v2 2026-07-15 16:03:42 +08:00
zhengyubai 3ba0b90f9e refactor(policy): reuse built binaries 2026-07-15 16:58:43 +09:00
张卓澎 c2a6ce01aa feat(aitable): sync workflow create and update 2026-07-15 15:32:01 +08:00
张卓澎 09a300867c feat(sheet): sync workbook import from wukong 2026-07-15 15:31:08 +08:00
修雨 73e010a992 fix: make Gitee release sync resilient (#622)
* fix: make Gitee release sync resilient

* fix: address Gitee sync review feedback

* fix: bound the full Gitee sync path
2026-07-15 15:28:04 +08:00
修雨 d8ffea02ab test: close remaining coverage gaps 2026-07-15 15:19:25 +08:00
SCzhengand修雨 809d026b7e ci: add CLI compatibility and PR quality gates (#602)
* ci(interface): 添加接口完整性和CLI烟雾测试检查

- 在Makefile中新增interface-integrity、update-interface-baseline、cli-smoke和mock-mcp-smoke目标
- 实现接口基线脚本以比较CLI公共命令树与基线文件
- 新增脚本确保二进制文件可渲染所有顶层命令的帮助信息
- 添加mock-mcp-smoke测试验证HTTP和stdio MCP请求/响应传输
- 在GitHub Actions CI工作流中加入interface-integrity、cli-smoke和mock-mcp-smoke检查
- 新增AI行为检查工作流,限制AI生成PR的改动范围和禁改保护文件
- 文档中补充PR质量门禁要求及接口变更流程说明

* feat(policy): 增加多项兼容性和完整性检查

- Makefile中新增reset-interface-baseline、schema-compatibility、update-schema-baseline、skill-command-integrity等目标
- CI流程新增schema-compatibility和skill-command-integrity步骤
- 文档中详细说明接口兼容性基线更新和重置流程及schema基线注意事项
- 实现interface-baseline工具支持兼容性重置和合并,多项接口兼容性检查逻辑完善
- 新增schema-compat工具用于标准化schema列表及兼容性检查与合并
- 新增skill-command-integrity检查确保技能中引用命令存在
- 为interface-baseline和schema-compat添加单元测试覆盖基本兼容性规则

* test(skill-command-check): 增加命令解析和解析结果测试用例

- 补充对 parseReference 函数的边界情况和跳过条件的测试
- 新增 resolveCommandReference 函数测试,覆盖有效、无效及跳过场景
- 增加 isPlaceholder 函数的测试,验证占位符识别准确性

refactor(skill-command-check): 优化命令路径解析和验证逻辑

- 使用 resolveCommandReference 统一处理命令解析结果,清晰区分有效、无效和跳过情况
- 新增 commandResolution 类型及常量,提升代码可读性和扩展性
- 调整 parseReference 增加对 shell 组合符 “ & ” 的跳过处理

docs(mono): 更新最佳实践和产品文档命令示例

- 优化《best_practices/07-minutes.md》中行动项与摘要拉取示例命令,提升准确性
- 修改产品文档中 ALIDOC 表格数据和多维表格记录相关命令,命令路径更规范统一
- 同步多端技能文档,确保命令示例一致且正确

* ci: check interface against PR merge-base

* feat(policy): enforce CLI contract compatibility

* ci: enforce PR coverage thresholds

* ci: add fail-closed CI gate

* ci: adapt schema compatibility gate to runtime catalog

* fix(ci): close schema compatibility gate gaps

* test(auth): skip POSIX mode assertion on Windows

* test(auth): scope POSIX file-backend checks

* fix(ci): enforce native platform coverage

* test(ci): make skill paths portable

---------

Co-authored-by: 修雨 <huyizhou.hyz@alibaba-inc.com>
2026-07-15 14:53:34 +08:00
玉澜 87ac7048bd Merge remote-tracking branch 'upstream/main' into codex/pr-616-fix
# Conflicts:
#	internal/cli/schema_catalog.json
2026-07-15 12:10:41 +08:00
修雨 fafb6f47b9 test: reach complete unit coverage 2026-07-15 12:08:39 +08:00
修雨 8633246eff test: expand unit coverage 2026-07-15 11:41:30 +08:00
玉澜 275c3430b8 fix(skills): reconcile multi-skill runtime contracts 2026-07-15 10:26:51 +08:00
修雨 3e9e76df2c feat: add stable and beta Homebrew channels (#613)
* feat: add stable and beta Homebrew channels

* fix: align beta formula with tap conventions

* fix: use dedicated token for Homebrew PRs

* chore: minimize release token permissions

* docs: record Homebrew token setup

* docs: keep Homebrew automation token long-lived

* fix(verify): assert channel versions and prove homebrew coexistence

The six-channel verifier previously ran `dws version` without comparing
it to the version each channel advertises, so a stale or wrong binary
still reported PASS. It also uninstalled stable before installing beta,
which could not prove the keg-only beta coexists with stable.

- smoke() now takes an expected version and fails the channel on mismatch
- npm/homebrew derive the expected version from the package manager;
  curl/upgrade derive it from the latest GitHub release tag
- homebrew installs keg-only beta while stable stays installed, then
  asserts stable's version, binary SHA and PATH link are unchanged
- cleanup uninstalls both script-installed formulae, still refusing to
  touch a pre-existing user install
- add regression tests for version assertion and coexistence semantics

* style(formula): satisfy brew style for stable and beta formulae

- reword desc so it no longer starts with the formula name
- drop the unnecessary `require "fileutils"` and use the mixed-in cp_r
  instead of the FileUtils. qualifier

* fix(verify): compare channel versions exactly

* fix(homebrew): keep generated formulae style-clean

* fix(homebrew): sync formulae with latest releases
2026-07-15 10:16:26 +08:00
玉澜 56116bf99e feat(skills): align Wukong multi-skill docs 2026-07-15 01:17:45 +08:00
修雨 4e59f9aa7a docs(changelog): seal v1.0.52 release notes (#619) 2026-07-14 23:04:01 +08:00
修雨 047ac54afe fix(connect): forward complex message payloads (#612)
Remove content-shape and message-type attachment filtering, recover forwarded unknown attachments, and preserve original media across all agent backends.
2026-07-14 22:31:21 +08:00
修雨 9a78a6494a Merge pull request #618 from DingTalk-Real-AI/codex/sync-wukong-im-read-results
feat(im): sync Wukong read-result semantics
2026-07-14 22:31:07 +08:00
wxianfeng 1adb4bc681 feat(event): support openDingtalkId subscription targets 2026-07-14 20:58:13 +08:00
修雨 73bf77d479 feat(im): sync Wukong read-result semantics 2026-07-14 19:04:05 +08:00
玉澜 5fde6222d4 fix: correct calendar rooms help metavar 2026-07-14 18:34:40 +08:00
修雨 a98ae9c6cf Merge pull request #598 from typefield/feat/schema-on-main
feat(schema): add stable Agent command catalog
2026-07-14 17:26:12 +08:00
玉澜andCursor 918c73f418 docs(changelog): record stable 22-product Agent catalog for #598
Document the embedded Schema catalog delivery under Unreleased Added so
the PR documentation gate matches the shipped surface.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 17:11:44 +08:00
玉澜andCursor ef3c2feafb feat(schema): cover audit export/tail/verify from #555
Merge upstream main and publish the three public audit leaves into the
CommandRegistry, metadata/selection hints, and regenerated Catalog so
reverse completeness stays green.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 16:54:29 +08:00
玉澜 0a7b6d8406 Merge upstream/main into feat/schema-on-main
Bring in #555 audit export/tail/verify public leaves for schema completeness.
2026-07-14 16:36:01 +08:00
wxianfeng 723c577484 Merge remote-tracking branch 'upstream/main' into feat/dws-event-im-2phase
# Conflicts:
#	internal/app/event_personal_command.go
#	internal/event/consume/run.go
#	skills/mono/references/products/event.md
#	skills/multi/dingtalk-event/SKILL.md
#	skills/multi/dingtalk-event/references/event-im.md
2026-07-14 16:15:13 +08:00
wxianfeng 766930f6e7 feat(event): flatten personal event output 2026-07-14 15:39:21 +08:00
玉澜andCursor b2561388fe fix(schema): keep Cobra hard-required as required projection floor
Stop letting manual/hint overlays project MarkFlagRequired flags as
optional; add final payload regression and gofmt the disposition test.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 15:27:23 +08:00
SCzheng da30780684 Merge pull request #555 from DingTalk-Real-AI/feat/audit-log-v2
feat(audit): implement user operation audit log
2026-07-14 14:54:44 +08:00
修雨 96986dfbff style(audit): gofmt trailing newline in audit_runtime_test.go
Fixes the Lint (Format Check) CI failure introduced by the previous
commit; gofmt flagged a trailing blank line at EOF.
2026-07-14 14:35:29 +08:00
修雨 27c3449036 fix(audit): drain forwards on error exit, fail CSV on corrupt JSONL
Address second-round review on PR #555:

- Move CloseAuditSink into the unconditional Execute defer so async remote
  forwards are drained on BOTH success and failure paths. Cobra skips
  PersistentPostRunE when RunE returns an error, which previously dropped
  in-flight forwards for failed commands. Make CloseAuditSink idempotent via
  sync.Once so the success-path hook and the defer can both call it.
- CSV export now returns a "文件:行号" error on malformed JSONL instead of
  silently skipping the line and exiting 0.
- Add regressions: TestCloseAuditSinkDrainsOnErrorPath (error-path drain),
  TestExportCSVFailsOnMalformedJSON (corrupt JSONL visible), and
  TestAuditIdentityReresolvesOnProfileSwitch (per-profile Actor via an
  injectable token loader seam).
2026-07-14 14:22:13 +08:00
玉澜andCursor e9cd8c9ad9 fix(schema): align event.stop confirmation with runtime --yes gate
Catalog safety now matches the existing CLI confirmation requirement so
Agent metadata and TestEventRegistry stay consistent.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 14:16:12 +08:00
玉澜andCursor 5856a897d1 feat(schema): split human hints into metadata and selection
Own safety/gates/parameters in metadata/ and Agent prose in selection/,
drop the monolithic Manual file, and keep confirmation aligned with
per-tool runtime_gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 13:40:09 +08:00
修雨 d0787ce8ee fix(audit): stateless hash chain, waitable forwarder, profile actor, observability
Address PR #555 review:
- chain: derive prev_hash from file tail under cross-process flock, drop the
  global .chain sidecar so per-day files stay independently verifiable and
  concurrent dws processes cannot fork the chain
- forward: track async forwards with WaitGroup and add bounded Close(ctx) so
  in-flight deliveries are not dropped on process exit
- actor: resolve Actor from the active runtime profile (profile-keyed cache)
- observability: BuildSink returns init errors; write/forward failures reported
  to file log and to stderr when DWS_AUDIT_DEBUG is set
- cli: reject `audit tail --lines` < 1; check CSV writer/flush errors
- wire CloseAuditSink into PersistentPostRunE
- add regression tests for cross-date/cross-process chain, forwarder
  wait/timeout, init-failure, tail validation, CSV export
2026-07-14 11:56:09 +08:00
玉澜andCursor 363ca3de9b feat(schema): curate agent selection hints from live MCP and runtime gates
Rewrite use_when/avoid_when/examples with live dws schema plus Skill/Cobra
review, expand runtime_gates to 70 confirmed commands, and regenerate catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 11:37:47 +08:00
Dennis fde008a25d fix(shortcut): address review safety notes 2026-07-14 11:20:55 +08:00
玉澜andCursor 987273f32b feat(schema): align confirmation with runtime via index+products hints
Make agent hints authoritative for confirmation by loading
internal/cli/schema_hints/index.json + products/*, and gate catalog
user_required to the reviewed runtime_gates set.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-14 10:33:49 +08:00
修雨 32c1d772de fix(root): expose audit command in help and reserve it from plugins 2026-07-14 10:30:05 +08:00
修雨 39b3003e2f Merge branch 'main' into feat/audit-log-v2 2026-07-14 10:21:15 +08:00
玉澜 f423031074 ci: allow full schema race gate to finish 2026-07-14 08:24:41 +08:00
玉澜 2879683cad fix(schema): enforce final delivery and runtime contracts 2026-07-14 08:11:27 +08:00
玉澜 878bafe55d perf(schema): keep delivery gates within race budget 2026-07-14 01:56:03 +08:00
玉澜 114c47b62d test(event): align restart hint with safe stop flow 2026-07-14 01:30:59 +08:00
玉澜 6d97bf7204 Merge upstream/main into feat/schema-on-main
Complete the registry-first Schema delivery invariants, bind the event command surface, and preserve the event subprocess contract from main.
2026-07-14 01:25:05 +08:00
玉澜 06cea56e92 fix(schema): close resolver and runtime contract gaps 2026-07-14 00:06:37 +08:00
玉澜 1f2b992e9c fix(schema): align capability contracts and delivery 2026-07-13 22:51:38 +08:00
SCzheng 43798de088 fix(connect): preserve rich text image attachments (#606) 2026-07-13 22:13:56 +08:00
Ari c4d8987f6c feat(event): AI-subprocess contract and cobra-synthesized schema (#609)
Align `dws event consume` with an AI-subprocess contract an orchestrator
can drive deterministically, and expose a machine-readable input schema
for event commands via `dws schema`.

Subprocess contract:
- Fixed stderr ready line `[event] ready event_key=<key> bus_pid=<pid>`;
  block on it instead of sleeping.
- Final `[event] exited — received N event(s) in Xs (reason: ...)` line;
  exit 0 on controlled exit, non-zero and no exited line on failure.
- stdin-EOF graceful shutdown, armed only for a pipe stdin on an
  unbounded run; an interactive TTY and `< /dev/null` never trigger it.
- Ownership-based subscription cleanup: a run-created subscription is
  unsubscribed on any clean exit while a --subscribe-id-reused one is
  kept (--ephemeral still forces cleanup). Forward --profile to the
  detached bus so non-default orgs resolve the right credentials, and
  surface the child's real startup error over the ready pipe.

Schema:
- `dws schema "event consume"` (or event.consume) synthesizes a flat,
  machine-readable schema from the command's cobra flags:
  {description, path, source:"cobra",
  parameters{<flag>:{type,required,description,default?}}} plus an
  `arguments` array for positional inputs. Intermediate nodes list
  subcommands. Inherited global flags and hidden internal flags are
  excluded so the schema describes just that command.
- Reusable registry (cobraSchemaRoots); event is the first consumer and
  more command trees can opt in without further wiring.

Docs: mono + dingtalk-event skills document the contract and the two
schema surfaces; design notes in docs/event-subprocess-contract.md.
2026-07-13 22:08:22 +08:00
玉澜 fc415919d1 fix(ci): remove ripgrep dependency from schema policy 2026-07-13 21:15:56 +08:00
玉澜 125f0c8fe0 Merge remote-tracking branch 'upstream/main' into feat/schema-on-main
# Conflicts:
#	test/scripts/package_script_test.go
2026-07-13 21:09:27 +08:00
玉澜 55afc656ac fix(schema): validate agent example delivery 2026-07-13 20:33:56 +08:00
玉澜 f6c2ce655d refactor(schema): unify registry-first delivery 2026-07-13 19:49:40 +08:00
Aemeathand张卓澎 657d2c25e3 feat: sync open product command capabilities (#608)
Co-authored-by: 张卓澎 <zhuopeng.zzp@alibaba-inc.com>
2026-07-13 17:21:47 +08:00
johnand玉澜 9f7107b6bb ci: sign macOS releases with Apple Developer ID (#605)
* fix release upload of signed macOS assets

* ci: sign macOS releases with Developer ID

* fix release publication atomicity

* harden Developer ID release verification

* fix: run release script tests in CI

---------

Co-authored-by: 玉澜 <yulan.wqy@alibaba-inc.com>
2026-07-13 17:10:23 +08:00
Dennis eb5569ca21 docs(shortcut): refresh lark capability comparison 2026-07-13 16:56:42 +08:00
Dennis b7c14c118f fix(shortcut): adapt tool callers to main interface 2026-07-13 16:08:33 +08:00
Dennis fb4cf70c93 Merge remote-tracking branch 'origin/main' into feature/shortcut 2026-07-13 16:05:41 +08:00
Dennis 890dfea477 fix(shortcut): harden orchestration and usage tracking 2026-07-13 16:04:47 +08:00
wxianfeng 2e3311c955 feat(event): expose sender message event 2026-07-13 15:47:27 +08:00
修雨 bfd48b6a71 fix: preserve macOS auth across keychain mode changes (#597)
* fix: preserve auth across macOS keychain modes

* docs(auth): clarify per-profile recovery

* fix(auth): add safe macOS keychain migration

* ci: add native Windows auth coverage

* ci: scope Windows checks to auth paths

* fix(auth): address keychain review boundaries
2026-07-13 15:27:39 +08:00
wxianfeng 1b4bb6b498 refactor(event): rename emotion events to reaction 2026-07-13 15:06:03 +08:00
玉澜 d41ea586bf fix(schema): enforce catalog and interface completeness 2026-07-13 14:01:26 +08:00
玉澜 f77232d7c1 feat(schema): add agent-friendly manual hints 2026-07-13 13:41:30 +08:00
玉澜 31faf7205b docs: add repository agent guidance 2026-07-13 11:53:18 +08:00
玉澜 45e0423d46 fix(schema): enforce command and safety completeness 2026-07-13 11:50:20 +08:00
wxianfeng 368e439280 chore(event): default personal events to pre-release 2026-07-13 11:36:41 +08:00
wxianfeng 8965fd2707 feat(event): add read recall and emotion events 2026-07-13 11:19:27 +08:00
玉澜 1b70d8f3f2 Merge remote-tracking branch 'upstream/main' into feat/schema-on-main 2026-07-13 11:05:19 +08:00
玉澜 a6f309d011 fix(schema): lazily load embedded catalog 2026-07-13 11:05:08 +08:00
github-actions[bot] e85d9bc314 chore: update coverage badge [skip ci] 2026-07-13 02:36:36 +00:00
wxianfeng c0a7ad88a4 Merge branch 'main' of github.com:wxianfeng/dingtalk-workspace-cli 2026-07-13 10:33:45 +08:00
wxianfeng b62b1848aa Merge remote-tracking branch 'upstream/main' 2026-07-13 10:30:52 +08:00
github-actions[bot] 5dd7f9abd3 chore: update coverage badge [skip ci] 2026-07-13 02:11:20 +00:00
wxianfeng eefee3c063 Merge branch 'main' of github.com:wxianfeng/dingtalk-workspace-cli 2026-07-13 10:08:11 +08:00
修雨 390b6115bf fix(connect): harden daemon restart lifecycle (#599) 2026-07-12 23:11:08 +08:00
玉澜 a6b2972a1e feat(schema): review sheet range and filter agent semantics
Add explicit reviewed Agent hints for high-frequency sheet range/filter/filter-view, condition-format and dropdown tools. Replace generic avoid_when with concrete read/write/clear/style/filter-view disambiguation, tighten destructive operations, and regenerate schema metadata/catalog.

Validated with drift/catalog gates and go test ./internal/cli ./internal/app ./internal/generator/... .
2026-07-11 17:39:57 +08:00
玉澜 1e0a171ceb feat(schema): review attendance agent semantics
Add explicit attendance Agent review hints for all 38 attendance tools, replacing template avoid_when with business-specific selection guidance and marking them reviewed. Tighten high-impact attendance writes such as boss-check and settings/balance updates with high risk and user confirmation.

Regenerate schema metadata/catalog and update parameter binding hash. Drift/catalog gates and key schema tests pass.
2026-07-11 17:32:39 +08:00
玉澜 cb4d1c215c feat(schema): generate catalog from live Cobra tree without fallback
Stop registering runtime catalog fallback commands and make command-surface generation use the real Cobra tree directly. Regenerate schema surface, agent metadata and catalog from executable commands (20 products / 537 tools), add runtime-surface completeness hints, and update catalog gates/tests to use dynamic counts instead of old 504/21/461 constants.

This makes schema describe the actual executable CLI surface; drift/catalog gates and go test ./internal/cli ./internal/app ./internal/generator/... pass.
2026-07-11 16:07:23 +08:00
玉澜 538754bbba feat(schema): sharpen aitable view summaries and sibling disambiguation
Add explicit reviewed summaries for aitable view get/update subcommands so Agents
can distinguish filter, sort, group, visible-fields, aggregate, card and other
view operations. Regenerate sibling-disambiguation avoid_when entries from the
new summaries, making cross-tool guidance precise instead of generic.

Results: 395/504 tools carry sibling-command disambiguation and reviewed coverage
rises to 104/504. drift/catalog gates and go test ./internal/cli pass.
2026-07-11 14:31:38 +08:00
玉澜 c2010b912b chore(schema): drop accidentally committed dwsbin binary and ignore it 2026-07-11 14:03:10 +08:00
玉澜 cf8cf95087 feat(schema): add sibling-command disambiguation to avoid_when
Add skills/mono/schema-hints/sibling-disambiguation.json: for each multi-segment
command sub-group (aitable view update, sheet range, chat message, ...), append
explicit cross-referencing avoid_when entries pointing agents to the correct
sibling command. Regenerate embedded agent metadata + catalog: 395/504 tools now
carry sibling disambiguation, improving tool-selection beyond template-only
avoid_when. drift/catalog gates and go test ./internal/cli pass.
2026-07-11 14:02:05 +08:00
玉澜 f86d10ae63 feat(schema): add --compact mode and update SKILL.md schema guide
- Add --compact flag to schema command (canonical.go)
- Implement stripSchemaPayloadCompact to recursively remove provenance/
  debug/redundant fields (runtime_schema.go)
- Strip 27 top-level keys (agent_metadata_source, agent_source_refs,
  interface_ref, primary_cli_path, etc.) and 3 per-parameter keys
  (interface_description, interface_type, property)
- Add 3 tests covering leaf/overview/product compact modes
- Replace stale SKILL.md schema section with progressive query guide,
  compact field reference, and schema-vs-help decision table
- Regenerate schema artifacts (make generate-schema)

Size reduction:
  leaf: 9.5KB -> 6.0KB (36%)
  --all: 644KB -> 414KB (36%)
2026-07-11 13:41:19 +08:00
玉澜 3f3ece933b feat(schema): complete reviewed agent metadata 2026-07-11 12:23:55 +08:00
玉澜 3fac462410 fix(schema): keep defaulted pagination optional 2026-07-11 11:42:06 +08:00
玉澜 753866867f feat(schema): complete catalog contract and smoke gates 2026-07-11 11:21:44 +08:00
玉澜 a62bcdf460 fix(schema): audit fallback parameter bindings 2026-07-11 10:42:02 +08:00
玉澜 561525a18b feat(schema): generate stable agent command catalog 2026-07-11 10:28:10 +08:00
玉澜 e1ea573247 feat(schema): align dws schema with prior branch and GWS/Lark contract
Serve the versioned embedded Command Catalog (21 products / 504 tools) from
NewSchemaCommand instead of only the live tree, matching the prior branch's
release behavior and the GWS flat-leaf / Lark stable-canonical contract. Add
--all and route output through internal/output for --format/--jq/--fields.
Port schema_catalog_test.go asserting 504/21 embedded catalog integrity.
Helper subtree and live Cobra tree remain as fallbacks.
2026-07-11 01:58:36 +08:00
玉澜 eb9e6be944 merge feat/schema-gws-flat into upstream static-endpoint schema branch
Consolidate the prior schema branch (old discovery-based architecture) into the
upstream-based dynamic-schema implementation. Merged tree keeps the upstream
static-endpoint architecture with dynamic schema; old discovery/generator/compat
packages are not carried over (incompatible with upstream, superseded by the
live-tree dynamic schema). Old schema data assets (agent metadata, destructive
safety annotations, conference metadata) remain present via the ported runtime.

Brings origin/feat/schema-gws-flat history in, so pushing is a fast-forward.
2026-07-11 01:46:18 +08:00
玉澜 ec59f7b042 feat(schema): implement dynamic schema on static-endpoint architecture
Restore dynamic dws schema on top of upstream static-endpoint runtime
(v1.0.52) without re-introducing service discovery:
- port schema runtime (runtime_schema/schema_catalog/schema_agent_metadata/
  schema_hints) + embedded agent & interface metadata + ir data structures
- ir/catalog.go: drop discovery-dependent BuildCatalog, keep runtime types
- canonical.go NewSchemaCommand: build schema from the live Cobra tree via
  runtimeSchemaPayload instead of the stub
- add schema_support.go and design doc docs/schema-dynamic-endpoint-design.md

go build ./... passes; go test ./... 44 packages pass (only unrelated
post-goreleaser packaging tests fail with a known tar format issue).
2026-07-11 01:26:22 +08:00
玉澜 63c0b26cf6 feat: add conference agent metadata (summary/effect/reviewed)
Add skills/mono/schema-hints/conference.json annotating all 33 conference
meeting-control tools with agent_summary, effect and reviewed=true. Mark
end-meeting-for-all as risk=high + confirmation=user_required; mute-all and
cloud-record start/stop as risk=medium.

Coverage: missing agent_summary 81->48, missing effect 173->140,
reviewed=true 4->37. Drift/catalog gates, go test and 560-case smoke pass.
2026-07-11 00:04:54 +08:00
玉澜 5004fcd285 feat: add destructive-operation safety metadata to agent schema
Annotate 34 high-risk tools via skills/mono/schema-hints/destructive-safety.json
(30 destructive + 4 disable) with risk=high and confirmation=user_required, and
fix mergeToolMetadata effect precedence (effectSourceRank) so explicit hints
override command-verb inference. Regenerate embedded agent metadata and catalog.

risk=high coverage 22->56, effect=destructive 29->48; drift/catalog gates,
go test, and 560-case schema smoke all pass.
2026-07-10 23:50:53 +08:00
修雨 fc9acb9007 fix: align smart category args and runtime network errors (#591)
* fix: align smart category args and remove eval fixtures

* fix: classify runtime network failures

* fix: validate smart category inputs
2026-07-10 21:40:36 +08:00
aa6abc5ed6 feat(event): add personal event subscriptions (#589)
* dws event

* fix consume fail

* test: add stream ticket injection probe

* feat: add portal ticket stream mode

* user event

* fix: allow portal ticket normal without app secret

* event

* user event

* eventType filter

* refactor(event): 优化IPC端点路径处理和改进相关测试

- 用dwsevent.IPCEndpoint替代原先根据GOOS判断的路径逻辑
- 新增event包实现Unix socket路径长度限制及长路径fallback机制
- 添加endpoint_test.go覆盖路径短长及唯一性的单元测试
- 修改busctl模块使用统一的IPC端点获取方法,避免重复实现
- transport_unix.go新增checkSocketPath函数检查路径长度,防止EINVAL错误
- 在监听和连接Unix socket时加入路径限制检查,提升错误明晰度
- 去除多个文件中无用的runtime导入,简化代码依赖

* opt

* event skill

* default value

* install script event

* fix: remove subscribe id event fanout filter

* fix(personal): 修正指定发送人消息描述错误

* more im event

* filter subId

* fix: align personal event schema with stream payload

* fix: avoid duplicate app helper name

* feat: simplify personal event schemas

* feat: simplify event schema output

* docs: refine dingtalk event skill references

* feat: align personal event consume flags

* fix event stop and status visibility

* hide app event public entrypoints

* hide incomplete personal sender event

* remove external event reference comments

* chore(event): prepare official release

* fix(event): harden personal stream lifecycle

---------

Co-authored-by: 玉澜 <yulan.wqy@alibaba-inc.com>
Co-authored-by: zhengyubai <zhengyubai618@gmail.com>
2026-07-10 17:54:43 +08:00
修雨 ea6fd16d11 chore(changelog): prepare v1.0.51 stable (#595) 2026-07-10 17:35:12 +08:00
玉澜 eec64bdf35 fix: align schema aliases and one-of coverage 2026-07-10 17:13:29 +08:00
玉澜 ddad2f648c fix: align agent schema parameter contracts 2026-07-10 15:12:49 +08:00
玉澜 391e761b59 fix: stabilize agent schema metadata 2026-07-10 13:42:10 +08:00
玉澜 a15fb19fd2 test: retire obsolete discovery compatibility suite 2026-07-10 13:06:24 +08:00
玉澜 70107e008f feat: embed agent-optimized schema metadata 2026-07-10 12:53:51 +08:00
DennisandClaude Opus 4.8 b9f2733821 feat(app): assemble and wire shortcut commands into the CLI
builtin blank-imports every service + smart package so their registrations run,
exposes Commands(), and provides the zero-side-effect coverage suite
(TestAllShortcutsAssemble / TestAllToolLiteralsAreReal / TestNoDuplicateCommands
/ TestAllHaveIntent). legacy loads user YAML shortcuts then merges built-in
shortcut leaves into the helper command tree; root wraps the tool caller with the
usage recorder and registers dws shortcut.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:04:10 +08:00
DennisandClaude Opus 4.8 83543b20df feat(shortcut): P2 usage tracking (opt-in) + user-defined YAML shortcuts
Optional high-frequency distillation: a recording tool-caller logs each MCP
call's shape (not values; sensitive/free-text redacted) to ~/.dws/usage.jsonl —
OFF by default, opt-in via DWS_USAGE_TRACKING=1. Powers dws shortcut
list/stats/suggest/add. userdef compiles ~/.dws/shortcuts/*.yaml into registered
shortcuts at runtime (conflicts with built-ins skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:04:10 +08:00
DennisandClaude Opus 4.8 4e4705c673 feat(shortcut): smart orchestration layer (68 shortcuts)
Multi-step / intelligent shortcuts under internal/shortcut/smart: name→ID
resolvers (user/base/table/dept/space), name-based actions (chat +dm/+broadcast/
+group-members, todo +assign, calendar +book with rollback/+free/+invite/
+suggest-time), time & self intelligence (calendar +today/+tomorrow/+week/
+next-event/+my-free and +conflicts/+free-slots scheduling intelligence),
convenience reads (contact +me, oa +pending/+done-approvals, todo +due-today/
+related-tasks, mail +recent-mail/+find-mail-user, attendance +this-month) and
aggregation (minutes +detail, aitable +record-share-links). Projections hardened
against real DingTalk responses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:04:10 +08:00
DennisandClaude Opus 4.8 cd652bf9ab feat(shortcut): 298 one-to-one MCP tool wrappers across 16 services
Declarative 1:1 shortcuts (dws <service> +<command>) wrapping DingTalk MCP tools
with named flags, required/enum validation, risk confirmation and a
natural-language Intent; list/read commands add clean output projection. Scoped
to tools the helper command layer does NOT already expose, plus a handful that
add projection — the redundant re-wraps were pruned. Tool names/params are taken
verbatim from internal/helpers ground truth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:04:10 +08:00
DennisandClaude Opus 4.8 c5463424be feat(shortcut): declarative shortcut framework
A declarative Shortcut{Service,Command,Product,Risk,Flags,Validate,Execute}
struct compiled into cobra commands by the runner. RuntimeContext offers
CallMCP (terminal, prints), CallMCPData (multi-step, returns parsed data,
cross-server) and Output (projection honouring --format/--jq/--fields), plus
cross-field validators (MutuallyExclusive/AtLeastOne/ExactlyOne/RangeInt/
RequireAll) and a Register/Commands registry. helpers exports
CallMCPToolTextOnServer so multi-step shortcuts can consume intermediate results.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:04:10 +08:00
玉澜 15e0851e06 fix: cover attendance schema smoke cases 2026-07-09 16:11:10 +08:00
玉澜 f1ca55c649 fix: make schema smoke mail search deterministic 2026-07-09 13:52:08 +08:00
玉澜 4440479c5c feat: align runtime schema smoke validation 2026-07-09 11:32:25 +08:00
修雨 5ac5fcbf16 Merge remote-tracking branch 'origin/main' into feat/audit-log-v2
# Conflicts:
#	internal/helpers/devapp_connect.go
2026-07-08 13:59:54 +08:00
shangguanxuan.sgx 00bb595768 fix(pat): classify org policy denials 2026-07-06 18:23:41 +08:00
修雨 4a717bd92f feat(audit): implement user operation audit log
- Add internal/audit package: Event struct, FileSink, date rotation, L1 hash chain, HTTP forwarding, 3-tier redaction
- Integrate with runner: emit audit event in executeInvocation defer
- Add dws audit tail/export/verify command group
- Register DWS_AUDIT* env vars in configmeta, enabled by default
2026-07-06 11:55:40 +08:00
玉澜 604ec5f50a Merge remote-tracking branch 'origin/feat/dws-event' into feat/dws-event 2026-07-06 10:04:19 +08:00
玉澜 27296ec426 fix: remove subscribe id event fanout filter 2026-07-06 10:04:12 +08:00
wxianfeng 6a38a168dd install script event 2026-07-02 20:41:46 +08:00
wxianfeng 9771053d81 default value 2026-07-02 20:14:30 +08:00
wxianfeng 10c0c5083e event skill 2026-07-02 19:42:10 +08:00
wxianfeng a0187b5297 Merge branch 'feat/dws-event' of github.com:wxianfeng/dingtalk-workspace-cli into feat/dws-event 2026-07-02 17:15:48 +08:00
wxianfeng 81991f1c07 opt 2026-07-02 17:14:55 +08:00
xianfeng wang 836670ef50 Merge pull request #24 from sczheng189/feat/dws-event
fix(event): unix socket 路径超长时 fallback 到短路径,修复深层配置目录下 bus 无法启动
2026-07-02 16:54:58 +08:00
zhengyubai 53ce0a8303 refactor(event): 优化IPC端点路径处理和改进相关测试
- 用dwsevent.IPCEndpoint替代原先根据GOOS判断的路径逻辑
- 新增event包实现Unix socket路径长度限制及长路径fallback机制
- 添加endpoint_test.go覆盖路径短长及唯一性的单元测试
- 修改busctl模块使用统一的IPC端点获取方法,避免重复实现
- transport_unix.go新增checkSocketPath函数检查路径长度,防止EINVAL错误
- 在监听和连接Unix socket时加入路径限制检查,提升错误明晰度
- 去除多个文件中无用的runtime导入,简化代码依赖
2026-07-02 17:25:56 +09:00
wxianfeng 78867f3601 eventType filter 2026-07-02 16:19:28 +08:00
wxianfeng 37438659e6 user event 2026-07-01 15:58:09 +08:00
wxianfeng 3c12c835a3 Merge branch 'feat/dws-event' of github.com:wxianfeng/dingtalk-workspace-cli into feat/dws-event 2026-07-01 14:22:06 +08:00
wxianfeng 389f83241f event 2026-07-01 14:21:36 +08:00
玉澜 3714adc2db Merge remote-tracking branch 'origin/feat/dws-event' into feat/dws-event
# Conflicts:
#	internal/app/event_command.go
2026-07-01 14:20:17 +08:00
玉澜 f37d0569a1 fix: allow portal ticket normal without app secret 2026-07-01 14:17:12 +08:00
wxianfeng 798b58bf3c fix conflict 2026-07-01 11:15:48 +08:00
wxianfeng d926bed3cc user event 2026-07-01 11:07:57 +08:00
玉澜 5ac180d3dd feat: add portal ticket stream mode 2026-06-30 20:38:27 +08:00
玉澜 35e60407d3 test: add stream ticket injection probe 2026-06-30 15:33:17 +08:00
wxianfeng ea46132cf6 merge upstream main 2026-06-29 16:15:13 +08:00
github-actions[bot] 6f5d17335b chore: update coverage badge [skip ci] 2026-06-04 10:04:00 +00:00
wxianfeng 478dc155e8 fix consume fail 2026-06-04 10:41:50 +08:00
wxianfeng 08ecb38a42 dws event 2026-06-03 19:12:23 +08:00
2144 changed files with 549113 additions and 30932 deletions
+34
View File
@@ -0,0 +1,34 @@
# Release fragments
普通功能、修复和面向用户的行为变更不要再修改根目录 `CHANGELOG.md` 的
`Unreleased` 区域。每个 PR 在本目录新增一个独立的 Markdown fragment,避免
并行 PR 争用同一文件。
文件名使用能唯一定位变更的短名,通常是 PR 号,例如
`1234-chat-reply-mentions.md`。文件名必须匹配
`^[a-z0-9][a-z0-9._-]*\.md$`,且必须是普通文件,不能是符号链接。本目录顶层
只接受 `README.md`、`released/` 和符合该规则的 fragment:fragment 一律平铺在
顶层,不接受任何其它子目录,本目录自身也不能被替换成文件或符号链接。其余条目
会被 CI 直接拒绝而不是忽略,以免非法条目跳过校验后拖垮下一个 PR。文件格式
严格如下:
```markdown
---
category: Added
---
- **Chat reply mentions** (#1234) — supports mentioning selected members.
```
`category` 只能是 `Added`、`Changed`、`Deprecated`、`Removed`、`Fixed` 或
`Security`。正文至少包含一个 Markdown 列表项,且不得包含 `TODO` 或 `TBD`。
发布 beta 时,`scripts/release/prepare-changelog.sh` 会按分类和文件名稳定排序,
将未归档 fragments 汇总为唯一的版本章节,并移动到
`.changes/released/<version>/`。因此 release-seal PR 是唯一会修改
`CHANGELOG.md` 的 PR;它同时归档已消费的 fragments,供审计追溯。
归档只能在同一个 release-seal PR 中以原样移动完成;CI 会拒绝直接修改、
删除或重写已归档文件。
无需面向用户发布说明的改动不添加 fragment。评审者根据改动是否可见来判断该
例外是否成立。
@@ -0,0 +1,8 @@
---
category: Added
---
- **Agent version and extended context passthrough** (Aone 85384225) — adds
validated `DWS_AGENT_VER` and sensitive JSON `DWS_AGENT_EXT` metadata to
ordinary non-plugin MCP requests without forwarding it to A2A, OAuth,
Discovery, or third-party plugins.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **Chat message send help** - Clarifies Markdown image syntax for inline mixed text and images.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Drive file comments** (#961) — adds `dws drive comment list` and `dws drive comment create` for comments on ordinary preview files.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Chat automatic pagination controls** (#970) — adds bounded `--max-items` and cancellable `--page-delay` support to the core IM list shortcuts, with safe continuation metadata and truncation reporting.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **Doc/drive/wiki routing descriptions** — clarifies the document-space container-vs-content boundary across the doc, drive, and wiki skill descriptions for more predictable first-round Agent selection, without changing CLI behavior.
@@ -0,0 +1,20 @@
---
category: Fixed
---
- **Drive `--latest` refuses incomplete Top-N** (#899) — `dws drive list --latest` used to
exit 0 with a "Top-N" computed over a partially scanned tree whenever a directory read
failed mid-recursion (permission denied, API error), letting an incomplete set pose as the
globally newest files. Truncation at the 2000-item scan cap and mid-recursion directory
failures now both fail closed (`LATEST_SCAN_TRUNCATED` / `LATEST_SCAN_INCOMPLETE`), report
the first failing folder with its depth and reason, and emit a recovery command that
reproduces the original candidate set — query domain, `--folder`, `--pattern`, `--type`,
`--start` and `--end` are all carried over. On POSIX shells each user-supplied value is
quoted so a URL query string or a shell metacharacter cannot change how the copied command
parses. On Windows no quoting form is safe for both `cmd.exe` and PowerShell, so values
containing metacharacters are not inlined at all: the command carries a placeholder and the
original value is shown on a separate line marked as data rather than an executable command.
Unrecoverable errors under `--latest` return the root cause instead of a partial result.
Remote-controlled folder names and server error text are stripped of ANSI escapes and
control characters before they reach the plain-text stderr message. The internal `sortTime`
sort key no longer leaks into `drive list --depth` output on any path.
@@ -0,0 +1,12 @@
---
category: Added
---
- **Drive list type/time filtering** (#942) — `dws drive list` gains `--type
file|folder`, `--start`, and `--end` for client-side filtering by node type
and modification time on both the pan and workspace routes. Filtering runs
a bounded full scan of the target directory (2000-entry cap, reported via
`truncated=true`), composes with `--latest`/`--pattern`/`--depth`, and is
mutually exclusive with `--versions`/`--cursor`/`--order-by`/`--order`/
`--limit`. Time values accept relative forms (`24h`/`7d`/`2w`), RFC 3339,
zone-less ISO 8601 (Asia/Shanghai), or a plain date.
@@ -0,0 +1,12 @@
---
category: Fixed
---
- **Drive list pattern filtering** (#942) — `dws drive list --pattern` on the
single-layer pan route now filters the returned page by name pattern; the
flag was previously accepted but silently ignored.
- **Drive list `--type folder --latest` composition** (#942) — `--latest` now
ranks the filtered entries (folders included when `--type folder` is set)
instead of unconditionally dropping folders, so the documented combination
returns the most recently modified folders rather than an empty list.
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Chat message time defaults** (#973) — default omitted `chat message list-all` time bounds in `Asia/Shanghai` when emitting timezone-less `yyyy-MM-dd HH:mm:ss` values, matching parsing semantics and rejecting reversed windows.
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Doc and Drive parameter aliases** — normalizes reviewed identifier, pagination, path, version, and role synonyms while blocking ambiguous values before dispatch.
@@ -0,0 +1,15 @@
---
category: Added
---
- **Drive folder synchronization** — adds `dws drive status`, `dws drive pull`,
`dws drive push`, and `dws drive sync` for file-level comparison and transfer
between a local folder and a Drive folder. Differences come from exact MD5 by
default or from modification time with `--quick`; `status` is read-only, `pull`
and `push` are one-directional with `--if-exists skip|smart|overwrite`, and
`sync` is bidirectional with `--on-conflict remote-wins|local-wins|keep-both|ask`.
Only regular files are transferred — online documents and shortcuts are skipped,
neither side deletes extra files, downloads are staged through a temporary file
and committed with an atomic rename, and remote names that would escape
`--local-folder` are reported as failures instead of being written. Every command
prints a structured summary on stdout and exits non-zero when any item fails.
@@ -0,0 +1,5 @@
---
category: Added
---
- **International DingTalk region support** — adds `.io` login and MCP routing, pre-release endpoint overrides, and profile-aware gateway selection while preserving the existing `.com` flow.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **Chat identity routing** — validates explicit `openDingTalkId` inputs and improves name, `userId`, and `openDingTalkId` routing for message shortcuts.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Privacy-safe CLI telemetry** (#1009) — reports reviewed command outcomes and profile identity dimensions while excluding command arguments, output, paths, device fingerprints, and automatic system dimensions; `DO_NOT_TRACK=1` disables reporting.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Feedback survey entry in root help** (#1019) — `dws --help` now closes with a Feedback section linking the user-experience survey form.
@@ -0,0 +1,7 @@
---
category: Changed
---
- **Chat IM ID flags** (#954) — standardizes chat command entry points on `--conversation-id` for conversation IDs and `--message-id` for message IDs, so help, Schema, and Agent recommendations use the same canonical flags.
- **Legacy chat flag compatibility** (#954) — keeps older chat IM ID flags such as `--group`, `--id`, `--chat`, `--open-conversation-id`, `--msg-id`, and `--open-message-id` working as compatibility aliases where applicable, while hiding migrated aliases from recommended help and Schema surfaces.
- **Chat group bots target flag** (#954) — keeps `dws chat group bots` on the visible `--group` flag; this command does not register `--group-name`, and `--group` accepts either an openConversationId or a uniquely resolved group name.
@@ -0,0 +1,6 @@
---
category: Fixed
---
- **Chat card update evidence** — distinguishes an accepted update request from an independently verified visible update, preserving the real `bizId` and warning callers not to repeat an unverified write.
- **Chat command guidance** — splits message and group references by task and explains that `--from` is ambiguous between sender and time-range intent.
@@ -0,0 +1,8 @@
---
category: Changed
---
- **Faster Schema Catalog assembly** — projects typed values into payload JSON
without re-running a validation scan over documents `json.Marshal` has just
produced, cutting roughly a third of the projection work across the full tool
set. Untrusted JSON input keeps its existing validation.
@@ -0,0 +1,9 @@
---
category: Added
---
- **Wiki Shortcut workflows** — publishes 20 reviewed space, member, node, and
activity shortcuts with strict collection validation, cursor handling,
write-terminal evidence, safe read-backs where the backend supports them,
task-oriented routing, and documented backend
boundaries.
@@ -0,0 +1,11 @@
---
category: Fixed
---
- **Aitable pagination and Minutes unshare verification** (#1006) — keeps
record queries on the service's 20-record page boundary so multi-page reads
and mutation readbacks no longer report false retryable failures, preserves
`totalCount` when supplied, validates `--dry-run` plans before transport,
follows active deletion readback continuations before proving absence, and
rejects Minutes unshare success until the listening note exists and the
service acknowledges the exact task and member targets.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Robot group reference replies** (#928) — `chat message send-by-bot` supports paired `--reply` and `--ref-sender` flags for Markdown replies that quote an existing group message.
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Document write verification** (#960) — avoids false partial-success results when normalized Markdown, paginated blocks, inline images, or version reverts are confirmed by server readback. Document reverts and media inserts now require explicit readback evidence and report partial success when the server cannot prove the requested result.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **AI Table parameter aliases** — accepts reviewed equivalent spellings for Base, table, workflow, search, pagination, and description parameters while keeping role-changing or semantically ambiguous inputs blocked.
@@ -0,0 +1,9 @@
---
category: Added
---
- **AI Table server-side statistics** — adds `dws aitable record stats` for
ungrouped record-set metrics through `query_records_stats`, plus `dws aitable
record group-stats` for grouped, distinct, and advanced aggregation through
`query_stats`; both commands validate their JSON aggregation contracts before
dispatch.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Calendar event share-info** (#980) — adds `dws calendar event share-info` to fetch a calendar event's share info (title, organizer, location, join info) for sharing with others; supports `--calendar-id` and `--language`.
@@ -0,0 +1,11 @@
---
category: Added
---
- **Calendar and To-do Shortcut workflows** — aligns 47 public task-oriented
entries with lark-cli where the DingTalk backend supports equivalent
semantics, rejects malformed or missing collections instead of returning
false empty success, preserves truthful pagination, and requires stable
identifiers plus read-back or explicit terminal receipts for writes. Adds
deterministic contract coverage, a PII-safe live E2E runner, and a sanitized
capability review with documented platform boundaries.
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Chat sender identity guards** — preserves unverified mixed sender inputs after exact message `senderId` matches and aligns `--sender-query` Skill guidance with fail-closed Runtime behavior.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **Doc/drive description scope** — restates the `dingtalk-doc` description as document-entity-and-content operations with an explicit exclusion list, and narrows `dingtalk-drive` to file-level management of DingTalk documents, so first-round Agent selection separates content work from file management without changing CLI behavior.
@@ -0,0 +1,10 @@
---
category: Added
---
- **Doc and Sheet comment lifecycle commands** — adds `comment batch-query`,
`comment resolve`, `comment restore`, and the lightweight
`comment react-reply` to both `dws doc` and `dws sheet`. The two domains share
the same `doc-comment` MCP capabilities; batch queries preserve input order
for repeated `topicId:commentKey` references, while reaction replies require
DingTalk reaction names such as `憨笑` or `鼓掌` rather than raw Unicode emoji.
@@ -0,0 +1,6 @@
---
category: Added
---
- **Sheet SourceRange dropdowns** — supports range-backed dropdowns across direct, cell, and batch write paths, with structured readback for valid and invalid references. Batch `set-dropdown` now rejects unsupported top-level `colors` / `source-colors`; Inline colors belong in `options[].color`, while SourceRange color writes remain unsupported.
- **Sheet read completion metadata** — documents and preserves returned ranges, truncation reasons, and partial-read status for large range and CSV reads.
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Windows event bus lifecycle** — start event consumers without unsupported inherited file descriptors, stop buses through local IPC with a termination fallback, and preserve subscription cleanup when startup fails.
@@ -0,0 +1,14 @@
---
category: Changed
---
- **Attendance and Mail Shortcuts** (#1045) — publishes only capabilities with
strict response, identity, pagination, and real-data verification while
retaining historical CLI discovery and argument compatibility for commands
that remain unavailable to agents. Mailbox auto-resolution now accepts both
reviewed string and object response shapes, and Attendance date ranges cover
the complete requested end date without dropping cross-midnight punches whose
actual check time is inside the requested range. The schedule query remains
CLI-compatible but is withheld from the Agent catalog because its downstream
service returns a successful process exit with a null body for both populated
and empty ranges.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **Chat group roles** (#1058) — exposes the single-value `--role-id` flag for assigning one custom group role while preserving hidden `--role-ids` compatibility.
@@ -0,0 +1,5 @@
---
category: Added
---
- **招聘职位管理** (#976) — 新增招聘职位列表、详情查询和职位创建命令。
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
---
category: Fixed
---
- **Chat user mentions** — preserves literal `<@openDingTalkId>` tokens in current-user Markdown messages and rejects mismatches between message-body mentions and mention flags before sending.
- **Chat direct media** — uses the IM upload target field for current-user direct file, audio, and video uploads, then uses the Chat receiver field for final message delivery.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **CLI compatibility governance** — adds a reviewed two-stage path for hiding retained legacy commands or optional `NoOpt=true` boolean flags from Help and Schema when their activated capability moves to a dedicated command, with legacy-leaf, complete parameter/constant mapping, durable runtime constant evidence, protected framework bridges, dry-run preservation, parameter-collision, and fail-closed required-parameter checks.
@@ -0,0 +1,5 @@
---
category: Added
---
- **OA admin approval query** — `oa approval list-by-admin` queries approval instances of a template with admin scope, with simple flags and an advanced `--request` mode; `startTime`/`endTime` use `yyyy-MM-dd HH:mm:ss` strings per the 2026-08 MCP contract update (ISO-8601 flag inputs auto-convert), and pageSize/time format are validated client-side with localized errors.
+9
View File
@@ -19,3 +19,12 @@
# Cache directory (optional, defaults to ~/.dws/cache)
# DWS_CACHE_DIR=
# Agent integration metadata (optional; ordinary non-plugin MCP requests only)
# DWS_AGENT_PRODUCT=example-agent
# DWS_AGENT_HOST=cloud
# DWS_AGENT_VER=0.1.5
# DWS_AGENT_EXT='{"umt":"example-redacted","miniwua":"example-redacted","ua":"ExampleAgent/0.1.5"}'
# The outer single quotes above are shell syntax and are not part of the value.
# DWS_AGENT_EXT is sensitive caller-declared JSON (max 8 KiB); never put real
# tokens in committed files or use this metadata alone for authentication.
-4
View File
@@ -1,4 +0,0 @@
# Default code owners for all files
# These users will be automatically requested for review on PRs.
* @DingTalk-Real-AI/cli-maintainers
+32 -4
View File
@@ -3,15 +3,43 @@
- What changed?
- Why is this change needed?
## Risk tier
- [ ] Documentation-only: prose/assets only; no executable, generated, workflow,
packaging, or interface behavior changed
- [ ] Standard: ordinary implementation change with a stable package graph
- [ ] High-risk: workflow/policy, package graph, generated Schema/registry,
platform, auth/keychain, installer, packaging, release, transport, recovery,
or another fail-closed infrastructure change
## Verification
- [ ] `make build`
- [ ] `make lint`
- [ ] `make test`
- [ ] `make policy`
Record the smallest targeted evidence that proves the changed behavior. Do not
repeat the entire CI suite locally only to fill this checklist: CI expands the
selected tier from documentation checks, through affected-package tests, to
the complete high-risk suite.
- [ ] Release fragment added for a user-visible behavior/interface change (otherwise `N/A`):
`.changes/<unique-name>.md`; ordinary PRs must not edit `CHANGELOG.md`.
- [ ] Release-seal validation (otherwise `N/A`):
`./scripts/policy/check-changelog-pr.sh --content-only "$(git merge-base HEAD origin/main)" HEAD`
- [ ] Targeted test/check commands and results:
- [ ] Behavior evidence (test name, CLI output shape, or before/after result):
- [ ] Documentation links/content/rendering checked (documentation-only, otherwise
`N/A`)
- [ ] Full local suite run because the change is high-risk (optional for other
tiers; record command/result or `N/A`)
- [ ] `./scripts/policy/check-generated-drift.sh`
(when generator inputs or generated artifacts may change)
- [ ] `./scripts/policy/check-command-surface.sh --strict` (if command surface changed)
- [ ] `./scripts/release/verify-package-managers.sh`
(after `make package`, if packaging or installer surfaces changed)
## Notes
- Any risks, follow-up work, or intentional scope cuts
The repository automatically requests one eligible peer reviewer, including
after a new head push when another review is needed. Once the latest push has
peer approval and all nine required checks are current and green, auto-merge
completes the PR; authors do not need to coordinate a separate routine merge.
+6
View File
@@ -0,0 +1,6 @@
paths:
.github/workflows/release.yml:
ignore:
# GitHub Actions added concurrency.queue in 2026. actionlint v1.7.12's
# bundled workflow schema has not caught up with the platform syntax.
- 'unexpected key "queue" for "concurrency" section'
+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="108" height="20" role="img" aria-label="coverage: 54.2%"><title>coverage: 54.2%</title><filter id="blur"><feGaussianBlur in="SourceGraphic" stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="108" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="47" height="20" fill="#dd4343"/><rect width="108" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".80" filter="url(#blur)" transform="scale(.1)" textLength="510">coverage</text><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="835" y="150" fill="#010101" fill-opacity=".80" filter="url(#blur)" transform="scale(.1)" textLength="370">54.2%</text><text aria-hidden="true" x="835" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">54.2%</text><text x="835" y="140" transform="scale(.1)" fill="#fff" textLength="370">54.2%</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 100.0%"><title>coverage: 100.0%</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#4b0"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="315" y="150" fill-opacity=".8" filter="url(#blur)" textLength="510">coverage</text><text x="315" y="150" fill-opacity=".3" textLength="510">coverage</text></g><text x="315" y="140" textLength="510">coverage</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="865" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">100.0%</text><text x="865" y="150" fill-opacity=".3" textLength="430">100.0%</text></g><text x="865" y="140" textLength="430">100.0%</text></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

+61
View File
@@ -0,0 +1,61 @@
# /eval 自助触发允许名单
#
# 名单内的 GitHub 登录名可对【自己创建的 PR】触发 /eval 评测;
# 对任意 PR 触发仍需仓库 write/maintain/admin 权限(维护者背书)。
# 授权读取的始终是默认分支上的本文件,PR 无法修改自身授权。
#
# 变更本文件必须走 PR 评审。每行一个 GitHub login,# 开头为注释。
aftersss
notable-open
EdgarWang0925
ayunya
yutongshe
qingyang1014
caiTriumph
xlb1130
Anonymity-0
FuShu-Yang
guimingyue
AlwaysLee
TaoJikun
zengyoulingzyl-stack
liyuan333
huangyoo
lifeihong
nitonitori
cywan1998
gangwn
junlonghuo2
aqruan
Freda0909
ShawnWhite777
PeterGuy326
abucraft
pengzhihan47-star
rainyak8
gongrongyun
huangyuanzhuo-coder
ybcstudy
bigqy
liwang-ai
meng93
wxianfeng
Patrick-Star-CN
rossluo28-hz
dxy704330469
gtezg30062
Neige-Premaire
zhuoyu20
avicii-chen
typefield
Haofeng0705
Huwenjiao
liuzeyang
maoqxxmm
FloralTide
lingyun9833
dxb121
C0922
xiaoji121
H3java
+285
View File
@@ -0,0 +1,285 @@
'use strict';
// 评审归属是受保护分支上的声明式规则;未知路径不猜测,交给工作流负载均衡兜底。
const REVIEWER_POOL = ['wxianfeng', 'typefield', 'haofeng0705', 'hlzjsong'];
const PRODUCT_GROUPS = [
{
primary: 'wxianfeng',
backup: 'typefield',
products: ['chat', 'contact', 'ding', 'event', 'mail', 'live', 'conference', 'dev', 'devapp', 'mcp', 'aiapp'],
},
{
primary: 'typefield',
backup: 'wxianfeng',
products: ['doc', 'drive', 'wiki', 'markdown', 'docparse', 'aidesign', 'devdoc', 'blackboard', 'finance', 'law', 'credit'],
},
{
primary: 'haofeng0705',
backup: 'typefield',
products: ['minutes', 'sheet', 'aitable', 'calendar', 'todo', 'oa', 'attendance', 'report', 'agoal', 'aisearch', 'yida', 'hrbrain'],
},
];
const pathStartsWith = (prefixes) => (path) => prefixes.some((prefix) => path.startsWith(prefix));
const MODULES = [
{
id: 'security',
label: '登录、认证、权限、安全',
primary: 'hlzjsong',
backup: 'typefield',
requiresSecondary: true,
matches: pathStartsWith([
'internal/auth/',
'internal/keychain/',
'internal/audit/',
'internal/pat/',
'internal/security/',
'internal/safety/',
'pkg/edition/',
]),
},
{
id: 'delivery',
label: 'CI、测试、发布、安装',
primary: 'haofeng0705',
backup: 'wxianfeng',
requiresSecondary: true,
matches: (path) =>
path.startsWith('.github/') ||
path.startsWith('scripts/release/') ||
path.startsWith('scripts/policy/') ||
path.startsWith('scripts/dev/') ||
path.startsWith('scripts/install') ||
path.startsWith('Formula/') ||
path.startsWith('build/') ||
path.startsWith('internal/upgrade/') ||
path.startsWith('internal/app/upgrade') ||
path.startsWith('test/') ||
path.startsWith('verify/') ||
path.startsWith('.workflow/') ||
path === 'coverage.txt' ||
path === 'coverage-base.txt' ||
path === '.goreleaser.yaml' ||
path === 'package.json' ||
path === 'package-lock.json' ||
path === 'docs/releasing.md',
},
{
id: 'architecture',
label: 'DWS 架构、公共内核',
primary: 'wxianfeng',
backup: 'typefield',
requiresSecondary: true,
matches: pathStartsWith([
'cmd/',
'internal/apiclient/',
'internal/app/',
'internal/cli/',
'internal/cobracmd/',
'internal/corecmd/',
'internal/errors/',
'internal/executor/',
'internal/generator/',
'internal/i18n/',
'internal/interfacesnapshot/',
'internal/jsonutil/',
'internal/localio/',
'internal/logging/',
'internal/output/',
'internal/pipeline/',
'internal/plugin/',
'internal/profilectx/',
'internal/registry/',
'internal/syncdata/',
'internal/testseam/',
'internal/transport/',
'pkg/',
]),
},
{
id: 'compatibility',
label: '兼容性',
primary: 'wxianfeng',
backup: 'typefield',
requiresSecondary: true,
matches: (path) =>
/(?:^|[/_.-])compat(?:ibility)?(?=$|[/_.-])/.test(path) ||
path.includes('schema_compat'),
},
];
function productMatches(path, product) {
const aliases = product === 'blackboard' ? ['blackboard', 'whiteboard'] : [product];
return aliases.some((alias) => new RegExp(`(?:^|[/_.-])${alias}(?=$|[/_.-])`).test(path));
}
const PRODUCT_MODULES = PRODUCT_GROUPS.flatMap((group) =>
group.products.map((product) => ({
id: `product:${product}`,
label: `产品:${product}`,
primary: group.primary,
backup: group.backup,
requiresSecondary: false,
matches: (path) => productMatches(path, product),
})),
);
const ALL_MODULES = [MODULES[0], MODULES[1], ...PRODUCT_MODULES, MODULES[2], MODULES[3]];
function normalizedPaths(file) {
return [file?.filename, file?.previous_filename]
.filter((path) => typeof path === 'string' && path !== '')
.map((path) => path.toLowerCase());
}
function compareStats(left, right) {
return right.files - left.files || left.module.order - right.module.order || left.module.id.localeCompare(right.module.id);
}
function classifyFiles(files) {
const counts = new Map();
for (const file of files || []) {
const matchingModules = new Set();
for (const path of normalizedPaths(file)) {
const matches = ALL_MODULES.filter((module) => module.matches(path));
const securityOrDelivery = matches.filter(
(module) => module.id === 'security' || module.id === 'delivery',
);
const effectiveMatches = securityOrDelivery.length > 0
? [...securityOrDelivery, ...matches.filter((module) => module.id === 'compatibility')]
: matches;
for (const match of effectiveMatches) {
matchingModules.add(match.id);
}
if (
effectiveMatches.length === 0 &&
(path.startsWith('internal/helpers/') || path.startsWith('internal/shortcut/'))
) {
matchingModules.add('architecture');
}
}
for (const moduleID of matchingModules) {
counts.set(moduleID, (counts.get(moduleID) || 0) + 1);
}
}
return [...counts.entries()]
.map(([id, files]) => {
const index = ALL_MODULES.findIndex((module) => module.id === id);
return {module: {...ALL_MODULES[index], order: index}, files};
})
.sort(compareStats);
}
function chooseModuleReviewer(module, unavailable) {
return [module.primary, module.backup].find(
(reviewer) => REVIEWER_POOL.includes(reviewer) && !unavailable.has(reviewer),
);
}
function addReviewer(reviewers, reviewer) {
if (reviewer && !reviewers.includes(reviewer)) {
reviewers.push(reviewer);
}
}
function reviewerCandidates({preferredReviewers, fallbackReviewers, eligibleReviewers}) {
const eligible = new Set(eligibleReviewers.map((reviewer) => reviewer.toLowerCase()));
const candidates = [];
for (const reviewer of [...preferredReviewers, ...fallbackReviewers]) {
if (
eligible.has(reviewer.toLowerCase()) &&
!candidates.some((candidate) => candidate.toLowerCase() === reviewer.toLowerCase())
) {
candidates.push(reviewer);
}
}
return candidates;
}
async function requestReviewersWithFallback({
candidates,
requiredReviewers,
satisfiedReviewers = [],
requestReviewer,
onFailure = () => {},
}) {
const alreadySatisfied = new Set(
satisfiedReviewers.map((reviewer) => reviewer.toLowerCase()),
);
const satisfied = new Set();
const requested = [];
for (const reviewer of candidates) {
if (satisfied.size >= requiredReviewers) {
break;
}
const normalizedReviewer = reviewer.toLowerCase();
if (alreadySatisfied.has(normalizedReviewer)) {
satisfied.add(normalizedReviewer);
continue;
}
try {
const shouldContinue = await requestReviewer(reviewer);
if (shouldContinue === false) {
return {requested, satisfiedReviewers: [...satisfied], aborted: true};
}
requested.push(reviewer);
satisfied.add(normalizedReviewer);
} catch (error) {
onFailure(reviewer, error);
}
}
return {requested, satisfiedReviewers: [...satisfied], aborted: false};
}
function resolveReviewRouting({files, author, latestPusher, fallbackReviewers = REVIEWER_POOL}) {
const modules = classifyFiles(files);
const unavailable = new Set([author, latestPusher].filter(Boolean).map((login) => login.toLowerCase()));
const reviewers = [];
const primaryModule = modules[0];
if (!primaryModule) {
return {modules: [], reviewers, requiredReviewers: 1, reason: 'unknown_paths'};
}
addReviewer(reviewers, chooseModuleReviewer(primaryModule.module, unavailable));
const requiresSecondary =
modules.length > 1 || modules.some(({module}) => module.requiresSecondary);
const secondaryModule = modules.find(({module}) => module.id !== primaryModule.module.id) || primaryModule;
if (requiresSecondary) {
addReviewer(
reviewers,
chooseModuleReviewer(secondaryModule.module, new Set([...unavailable, ...reviewers])),
);
}
for (const reviewer of fallbackReviewers) {
if (reviewers.length >= (requiresSecondary ? 2 : 1)) {
break;
}
if (REVIEWER_POOL.includes(reviewer) && !unavailable.has(reviewer)) {
addReviewer(reviewers, reviewer);
}
}
return {
modules: modules.map(({module, files}) => ({id: module.id, label: module.label, files})),
reviewers,
requiredReviewers: requiresSecondary ? 2 : 1,
reason: requiresSecondary ? 'cross_or_sensitive' : 'single_module',
};
}
module.exports = {
REVIEWER_POOL,
classifyFiles,
requestReviewersWithFallback,
resolveReviewRouting,
reviewerCandidates,
};
+148
View File
@@ -0,0 +1,148 @@
'use strict';
const assert = require('node:assert/strict');
const {
requestReviewersWithFallback,
resolveReviewRouting,
reviewerCandidates,
} = require('./reviewer-routing');
function route(files, author = 'author', latestPusher = author) {
return resolveReviewRouting({files: files.map((filename) => ({filename})), author, latestPusher});
}
{
const result = route(['internal/helpers/chat_toolbar.go']);
assert.deepEqual(result.reviewers, ['wxianfeng']);
assert.equal(result.requiredReviewers, 1);
assert.deepEqual(result.modules.map((module) => module.id), ['product:chat']);
}
{
const result = route(['internal/helpers/chat_toolbar.go', 'internal/helpers/doc_style.go']);
assert.deepEqual(result.reviewers, ['wxianfeng', 'typefield']);
assert.equal(result.requiredReviewers, 2);
}
{
const result = route(['.github/workflows/ci.yml']);
assert.deepEqual(result.reviewers, ['haofeng0705', 'wxianfeng']);
assert.equal(result.requiredReviewers, 2);
assert.equal(result.reason, 'cross_or_sensitive');
}
{
const result = route(['internal/auth/login.go'], 'hlzjsong');
assert.deepEqual(result.reviewers, ['typefield', 'wxianfeng']);
assert.equal(result.requiredReviewers, 2);
}
{
const result = route(['internal/upgrade/downloader.go']);
assert.deepEqual(result.reviewers, ['haofeng0705', 'wxianfeng']);
assert.equal(result.requiredReviewers, 2);
}
{
const result = route(['internal/app/upgrade.go', 'scripts/dev/test-release.sh']);
assert.deepEqual(result.reviewers, ['haofeng0705', 'wxianfeng']);
assert.equal(result.requiredReviewers, 2);
}
{
const result = route(['pkg/edition/edition.go']);
assert.deepEqual(result.reviewers, ['hlzjsong', 'typefield']);
assert.equal(result.requiredReviewers, 2);
}
{
const result = route(['internal/shortcut/chat/compatibility_coverage_test.go']);
assert.deepEqual(result.reviewers, ['wxianfeng', 'typefield']);
assert.equal(result.requiredReviewers, 2);
assert.deepEqual(result.modules.map((module) => module.id), ['product:chat', 'compatibility']);
}
{
const result = route(['internal/helpers/leaf_dispatch.go']);
assert.deepEqual(result.reviewers, ['wxianfeng', 'typefield']);
assert.equal(result.requiredReviewers, 2);
}
{
const result = route(['docs/unknown-area.md']);
assert.deepEqual(result.reviewers, []);
assert.equal(result.reason, 'unknown_paths');
}
async function testSingleReviewerFallback() {
const candidates = reviewerCandidates({
preferredReviewers: ['wxianfeng'],
fallbackReviewers: ['wxianfeng', 'typefield', 'haofeng0705'],
eligibleReviewers: ['wxianfeng', 'typefield', 'haofeng0705'],
});
const attempts = [];
const result = await requestReviewersWithFallback({
candidates,
requiredReviewers: 1,
requestReviewer: async (reviewer) => {
attempts.push(reviewer);
if (reviewer === 'wxianfeng') {
throw Object.assign(new Error('cannot request primary'), {status: 422});
}
return true;
},
});
assert.deepEqual(attempts, ['wxianfeng', 'typefield']);
assert.deepEqual(result.requested, ['typefield']);
assert.equal(result.satisfiedReviewers.length, 1);
}
async function testTwoReviewerFallback() {
const candidates = reviewerCandidates({
preferredReviewers: ['haofeng0705', 'wxianfeng'],
fallbackReviewers: ['haofeng0705', 'wxianfeng', 'typefield', 'hlzjsong'],
eligibleReviewers: ['haofeng0705', 'wxianfeng', 'typefield', 'hlzjsong'],
});
const attempts = [];
const result = await requestReviewersWithFallback({
candidates,
requiredReviewers: 2,
requestReviewer: async (reviewer) => {
attempts.push(reviewer);
if (reviewer === 'wxianfeng') {
throw Object.assign(new Error('temporary failure'), {status: 503});
}
return true;
},
});
assert.deepEqual(attempts, ['haofeng0705', 'wxianfeng', 'typefield']);
assert.deepEqual(result.requested, ['haofeng0705', 'typefield']);
assert.equal(result.satisfiedReviewers.length, 2);
}
async function testLowerPriorityExistingRequestDoesNotReplaceOwner() {
const attempts = [];
const result = await requestReviewersWithFallback({
candidates: ['wxianfeng', 'typefield'],
requiredReviewers: 1,
satisfiedReviewers: ['typefield'],
requestReviewer: async (reviewer) => {
attempts.push(reviewer);
return true;
},
});
assert.deepEqual(attempts, ['wxianfeng']);
assert.deepEqual(result.requested, ['wxianfeng']);
assert.deepEqual(result.satisfiedReviewers, ['wxianfeng']);
}
Promise.all([
testSingleReviewerFallback(),
testTwoReviewerFallback(),
testLowerPriorityExistingRequestDoesNotReplaceOwner(),
])
.then(() => console.log('reviewer routing policy tests passed'))
.catch((error) => {
console.error(error);
process.exitCode = 1;
});
+131
View File
@@ -0,0 +1,131 @@
name: Code Admission — AI Behavior
on:
pull_request_target:
types: [opened, synchronize, reopened, labeled, unlabeled]
push:
branches:
- main
permissions:
contents: read
pull-requests: read
statuses: write
jobs:
ai-behavior-check:
name: AI Behavior
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
# Deliberately do not check out or execute pull-request code here.
# pull_request_target keeps this policy anchored to the base branch.
- name: Check AI-generated PR boundaries
uses: actions/github-script@v7
with:
script: |
const pullRequest = context.payload.pull_request;
const sha = context.eventName === 'push' ? context.sha : pullRequest.head.sha;
const setStatus = (state, description) =>
github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha,
state,
context: 'AI Behavior',
description,
});
await setStatus('pending', 'Evaluating AI-generated PR boundaries');
if (context.eventName === 'push') {
await setStatus('success', 'Not applicable to the protected main push');
core.notice('AI Behavior is a PR policy; the main push context is sealed.');
return;
}
try {
const expectedHead = pullRequest.head.sha;
const expectedBase = pullRequest.base.sha;
const currentPull = async (phase) => {
const { data: pull } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
if (pull.head.sha !== expectedHead || pull.base.sha !== expectedBase) {
throw new Error(
`Pull request revision changed during ${phase}: ` +
`expected base/head ${expectedBase}/${expectedHead}, ` +
`got ${pull.base.sha}/${pull.head.sha}`
);
}
return pull;
};
const before = await currentPull('pre-policy check');
const labels = before.labels.map(({ name }) => name);
if (!labels.includes('ai-generated')) {
await setStatus('success', 'Not labeled ai-generated');
core.notice('Not an ai-generated PR; no AI-only policy applied.');
return;
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
per_page: 100,
});
await currentPull('post-policy check');
const maxChangedFiles = 30;
if (files.length > maxChangedFiles) {
await setStatus(
'failure',
`Changes ${files.length} files; limit is ${maxChangedFiles}`
);
core.setFailed(
`AI-generated PR changes ${files.length} files; limit is ${maxChangedFiles}.`
);
return;
}
const isProtectedPath = (filename) =>
typeof filename === 'string' &&
(
filename.startsWith('.github/workflows/') ||
filename.startsWith('scripts/ci/') ||
filename.startsWith('scripts/policy/') ||
filename.startsWith('scripts/release/') ||
filename === 'test/fixtures/cli-interface-baseline.txt' ||
filename === '.goreleaser.yaml' ||
filename === 'Makefile'
);
const protectedPaths = [...new Set(
files
.flatMap(({ filename, previous_filename }) => [filename, previous_filename])
.filter(isProtectedPath)
)];
if (protectedPaths.length > 0) {
await setStatus('failure', 'Modifies protected release/CI infrastructure');
core.setFailed(
'AI-generated PR modifies protected release/CI infrastructure:\n' +
protectedPaths.map((filename) => ` - ${filename}`).join('\n') +
'\nSplit these changes into a human-owned PR with explicit review.'
);
return;
}
await setStatus(
'success',
`Passed with ${files.length} changed files (limit ${maxChangedFiles})`
);
core.notice(
`AI behavior check passed (${files.length} changed files; limit ${maxChangedFiles}).`
);
} catch (error) {
await setStatus('error', 'Could not evaluate the exact pull request revision');
throw error;
}
+1755 -78
View File
File diff suppressed because it is too large Load Diff
+296
View File
@@ -0,0 +1,296 @@
name: PR Eval Dispatch
# `/eval <products> [sha=<full-head-sha>] [cases=<ref>]` PR 评论 → 生成可验证的评测请求,报告由 bot 回贴。
# 本 workflow 只在默认分支上下文运行,不 checkout、不执行 PR 代码。
# 审核 SHA 规则:评测他人 PR 必须显式携带 sha=(审阅背书凭据,验证
# 其恰为当前 open head);评测自己创建的 PR 可省略,自动钉住派发时刻
# 的当前 head(作者自背书,无第三方偷换窗口);受控评测执行端另以
# FETCH_HEAD 校验兜底派发后的变更。
# 授权两级:仓库 write/maintain/admin 可派发任意 PR;默认分支
# .github/eval-allowlist.txt 名单内的用户仅可派发自己创建的 PR。
# 触发通道:workflow 先创建占位评论,再上传与本次 run/comment 绑定的
# 不可变 manifest artifact,最后把 artifact 指针写回同一评论。评论仅是
# 不可信通知;受控评测服务必须验证成功 run、artifact 与 manifest,并在
# 触发评测前原子占用 manifest.idempotency_key,重复占用只能 no-op。
on:
issue_comment:
types:
- created
permissions: {}
concurrency:
group: eval-dispatch-${{ github.event.issue.number }}
cancel-in-progress: false
jobs:
dispatch:
name: Dispatch internal evaluation
if: >-
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/eval')
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
# 该 job 仅处理 PR;评论写入也限定在 PR Conversation 这一权限域。
pull-requests: write
steps:
- name: Check out default branch tooling
uses: actions/checkout@v4
- name: Verify commenter dispatch authorization
env:
GH_TOKEN: ${{ github.token }}
COMMENTER: ${{ github.event.comment.user.login }}
PR_AUTHOR: ${{ github.event.issue.user.login }}
EVAL_ALLOWLIST_PATH: .github/eval-allowlist.txt
run: |
# 不用 --fail:非协作者查权限返回 404 错误体,交由 guard 走名单分支;硬网络错误降级为空对象同样 fail-closed
permission_json="$(curl --silent --show-error \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/collaborators/${COMMENTER}/permission")" || permission_json='{}'
printf '%s' "$permission_json" | python3 scripts/ci/eval_dispatch_guard.py permission
- name: Parse /eval command
id: parse
continue-on-error: true
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: python3 scripts/ci/eval_comment_parse.py
- name: Reply usage on parse failure
if: steps.parse.outcome == 'failure'
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.issue.number }}
PARSE_ERROR: ${{ steps.parse.outputs.error }}
run: |
body="❌ /eval 命令解析失败:${PARSE_ERROR}"
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
--raw-field body="$body" \
> /dev/null
exit 1
- name: Verify reviewed PR head
id: pr
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.issue.number }}
EXPECTED_PR_NUMBER: ${{ github.event.issue.number }}
REVIEWED_SHA: ${{ steps.parse.outputs.reviewed_sha }}
COMMENTER: ${{ github.event.comment.user.login }}
run: |
pr_json="$(curl --fail --silent --show-error \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}")"
printf '%s' "$pr_json" \
| python3 scripts/ci/eval_dispatch_guard.py head \
>> "$GITHUB_OUTPUT"
- name: Create dispatch placeholder
id: placeholder
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.issue.number }}
run: |
set -euo pipefail
placeholder_body="🛰️ /eval 请求已通过权限与版本校验,正在生成可验证的评测请求。"
response="$(
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \
--raw-field body="$placeholder_body"
)"
comment_id="$(
printf '%s' "$response" \
| jq -er \
--arg issue_url "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}" \
'select(.issue_url == $issue_url) | .id | tostring | select(test("^[1-9][0-9]*$"))'
)"
printf 'comment_id=%s\n' "$comment_id" >> "$GITHUB_OUTPUT"
- name: Build dispatch request manifest
env:
REPOSITORY_ID: '1187709537'
REPOSITORY: ${{ github.repository }}
WORKFLOW_ID: '331725458'
WORKFLOW_PATH: .github/workflows/eval-dispatch.yml
RUN_ID: ${{ github.run_id }}
RUN_ATTEMPT: ${{ github.run_attempt }}
SOURCE_COMMENT_ID: ${{ github.event.comment.id }}
DISPATCH_COMMENT_ID: ${{ steps.placeholder.outputs.comment_id }}
ACTOR_ID: ${{ github.event.comment.user.id }}
ACTOR_LOGIN: ${{ github.event.comment.user.login }}
PR_NUMBER: ${{ github.event.issue.number }}
PR_HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
PRODUCTS: ${{ steps.parse.outputs.products }}
CASES_REF: ${{ steps.parse.outputs.cases_ref }}
SOURCE_BODY: ${{ github.event.comment.body }}
MANIFEST_PATH: ${{ runner.temp }}/eval-dispatch-request.json
run: |
set -euo pipefail
if [ "$REPOSITORY" != "DingTalk-Real-AI/dingtalk-workspace-cli" ]; then
echo "unexpected repository: ${REPOSITORY}" >&2
exit 1
fi
for value in \
"$REPOSITORY_ID" \
"$WORKFLOW_ID" \
"$RUN_ID" \
"$RUN_ATTEMPT" \
"$SOURCE_COMMENT_ID" \
"$DISPATCH_COMMENT_ID" \
"$ACTOR_ID" \
"$PR_NUMBER"; do
if [[ ! "$value" =~ ^[1-9][0-9]*$ ]]; then
echo "dispatch manifest contains a non-canonical identifier" >&2
exit 1
fi
done
if [[ ! "$PR_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "dispatch manifest contains an invalid PR head SHA" >&2
exit 1
fi
hash_output="$(printf '%s' "$SOURCE_BODY" | sha256sum)"
source_body_sha256="${hash_output%% *}"
if [[ ! "$source_body_sha256" =~ ^[0-9a-f]{64}$ ]]; then
echo "failed to hash source comment" >&2
exit 1
fi
idempotency_key="${REPOSITORY_ID}:${SOURCE_COMMENT_ID}"
umask 077
jq -n \
--arg repository_id "$REPOSITORY_ID" \
--arg repository "$REPOSITORY" \
--arg workflow_id "$WORKFLOW_ID" \
--arg workflow_path "$WORKFLOW_PATH" \
--arg run_id "$RUN_ID" \
--arg run_attempt "$RUN_ATTEMPT" \
--arg source_comment_id "$SOURCE_COMMENT_ID" \
--arg dispatch_comment_id "$DISPATCH_COMMENT_ID" \
--arg actor_id "$ACTOR_ID" \
--arg actor_login "$ACTOR_LOGIN" \
--arg pr_number "$PR_NUMBER" \
--arg pr_head_sha "$PR_HEAD_SHA" \
--arg products "$PRODUCTS" \
--arg cases_ref "$CASES_REF" \
--arg source_body_sha256 "$source_body_sha256" \
--arg idempotency_key "$idempotency_key" \
'{
schema_version: 1,
repository_id: $repository_id,
repository: $repository,
workflow_id: $workflow_id,
workflow_path: $workflow_path,
run_id: $run_id,
run_attempt: $run_attempt,
source_comment_id: $source_comment_id,
dispatch_comment_id: $dispatch_comment_id,
actor_id: $actor_id,
actor_login: $actor_login,
pr_number: $pr_number,
pr_head_sha: $pr_head_sha,
products: $products,
cases_ref: $cases_ref,
source_body_sha256: $source_body_sha256,
idempotency_key: $idempotency_key
}' > "$MANIFEST_PATH"
- name: Upload dispatch request manifest
id: artifact
uses: actions/upload-artifact@v4
with:
name: eval-dispatch-request-${{ github.run_id }}-${{ github.run_attempt }}-${{ steps.placeholder.outputs.comment_id }}
path: ${{ runner.temp }}/eval-dispatch-request.json
if-no-files-found: error
retention-days: 1
overwrite: false
- name: Finalize dispatch marker
env:
GH_TOKEN: ${{ github.token }}
DISPATCH_COMMENT_ID: ${{ steps.placeholder.outputs.comment_id }}
REPOSITORY_ID: '1187709537'
WORKFLOW_ID: '331725458'
WORKFLOW_PATH: .github/workflows/eval-dispatch.yml
RUN_ID: ${{ github.run_id }}
RUN_ATTEMPT: ${{ github.run_attempt }}
ARTIFACT_ID: ${{ steps.artifact.outputs.artifact-id }}
ARTIFACT_DIGEST: ${{ steps.artifact.outputs.artifact-digest }}
PR_HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
PRODUCTS: ${{ steps.parse.outputs.products }}
CASES_REF: ${{ steps.parse.outputs.cases_ref }}
run: |
set -euo pipefail
if [[ ! "$DISPATCH_COMMENT_ID" =~ ^[1-9][0-9]*$ ]] || \
[[ ! "$ARTIFACT_ID" =~ ^[1-9][0-9]*$ ]]; then
echo "artifact marker contains a non-canonical identifier" >&2
exit 1
fi
artifact_digest="${ARTIFACT_DIGEST,,}"
if [[ "$artifact_digest" != sha256:* ]]; then
artifact_digest="sha256:${artifact_digest}"
fi
if [[ ! "$artifact_digest" =~ ^sha256:[0-9a-f]{64}$ ]]; then
echo "artifact marker contains an invalid digest" >&2
exit 1
fi
marker_json="$(
jq -nc \
--arg repository_id "$REPOSITORY_ID" \
--arg workflow_id "$WORKFLOW_ID" \
--arg workflow_path "$WORKFLOW_PATH" \
--arg run_id "$RUN_ID" \
--arg run_attempt "$RUN_ATTEMPT" \
--arg dispatch_comment_id "$DISPATCH_COMMENT_ID" \
--arg artifact_id "$ARTIFACT_ID" \
--arg artifact_digest "$artifact_digest" \
'{
schema_version: 1,
repository_id: $repository_id,
workflow_id: $workflow_id,
workflow_path: $workflow_path,
run_id: $run_id,
run_attempt: $run_attempt,
dispatch_comment_id: $dispatch_comment_id,
artifact_id: $artifact_id,
artifact_digest: $artifact_digest
}'
)"
cases_note=""
if [ -n "$CASES_REF" ]; then
cases_note=",用例版本 \`${CASES_REF}\`"
fi
body="<!-- eval-dispatch: ${marker_json} -->"$'\n'"🛰️ /eval 已受理:产品集 \`${PRODUCTS}\`${cases_note},评测对象 \`${PR_HEAD_SHA}\`。"$'\n'"受控评测服务将在数分钟内处理,完成后由 bot 回贴报告。"
response="$(
gh api --method PATCH \
"repos/${GITHUB_REPOSITORY}/issues/comments/${DISPATCH_COMMENT_ID}" \
--raw-field body="$body"
)"
printf '%s' "$response" \
| jq -e \
--arg comment_id "$DISPATCH_COMMENT_ID" \
--arg body "$body" \
'((.id | tostring) == $comment_id) and (.body == $body)' \
> /dev/null
- name: Mark dispatch preparation failure
if: ${{ failure() && steps.placeholder.outputs.comment_id != '' }}
env:
GH_TOKEN: ${{ github.token }}
DISPATCH_COMMENT_ID: ${{ steps.placeholder.outputs.comment_id }}
run: |
failure_body="❌ /eval 请求准备失败,未生成可消费的评测请求。请稍后重试。"
gh api --method PATCH \
"repos/${GITHUB_REPOSITORY}/issues/comments/${DISPATCH_COMMENT_ID}" \
--raw-field body="$failure_body" \
> /dev/null \
|| true
+51 -17
View File
@@ -1,4 +1,5 @@
# 把本仓库代码自动镜像到 Gitee,供国内用户访问(raw 脚本入口 + tags)。
# 把本仓库 main 代码自动镜像到 Gitee,供国内用户访问 raw 脚本入口。
# Release tag 与附件只由 release.yml 的受控 publication queue 发布。
# 用 HTTPS + 令牌直接 git push(无需 SSH key),复用已配置的 secret:
# GITEE_TOKEN —— Gitee 私人令牌(勾 projects)
# GITEE_USER —— 令牌所属 Gitee 用户名(用于 https 推送鉴权)
@@ -10,11 +11,14 @@ on:
push:
branches:
- main
tags:
- 'v*'
schedule:
- cron: '0 18 * * *'
workflow_dispatch:
inputs:
sync_release_version:
description: "Sync a specific release version's assets to Gitee (e.g. v1.0.55-beta.3)"
required: false
type: string
concurrency:
group: gitee-code-mirror
@@ -23,13 +27,13 @@ concurrency:
jobs:
mirror:
runs-on: ubuntu-latest
# GitHub Actions 不允许在 job-level if 直接引用 secrets,故先用 env 暴露再在 step 守卫。
if: ${{ github.ref_name == github.event.repository.default_branch && github.repository_owner == 'DingTalk-Real-AI' }}
env:
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
GITEE_USER: ${{ secrets.GITEE_USER }}
GITEE_REPO: ${{ secrets.GITEE_REPO }}
steps:
- name: Checkout (full history + tags)
- name: Checkout main history
if: env.GITEE_TOKEN != ''
uses: actions/checkout@v4
with:
@@ -41,15 +45,7 @@ jobs:
set -eu
REMOTE="https://${GITEE_USER}:${GITEE_TOKEN}@gitee.com/${GITEE_REPO}.git"
if [ "${GITHUB_REF_TYPE:-}" = "tag" ]; then
git fetch --force --tags origin "refs/tags/${GITHUB_REF_NAME}:refs/tags/${GITHUB_REF_NAME}"
git push --force "$REMOTE" "refs/tags/${GITHUB_REF_NAME}:refs/tags/${GITHUB_REF_NAME}"
echo "✅ 已镜像 tag ${GITHUB_REF_NAME} 到 Gitee ${GITEE_REPO}"
exit 0
fi
# 取到 main 与所有 tag(落到 origin/* 与本地 tags,避免推当前分支引用冲突)
git fetch --force --tags origin 'refs/heads/main:refs/remotes/origin/main'
git fetch --force origin 'refs/heads/main:refs/remotes/origin/main'
# Gitee 专属分支:在 origin/main 之上叠加一个 README 本地化 commit。
# GitHub 那份 README 不变;只有推往 Gitee 的副本被改写。
@@ -81,7 +77,45 @@ jobs:
git add README.md README_zh.md 2>/dev/null || true
git commit -m "docs(gitee): localize install commands + coverage badge for Gitee mirror" || true
# 镜像对齐(force:Gitee 始终跟随 GitHub + Gitee 专属 README 本地化)
# main 镜像对齐;release tag 由 release.yml 单独校验后创建,禁止在这里 force。
git push --force "$REMOTE" 'gitee-main:refs/heads/main'
git push --force --tags "$REMOTE"
echo "✅ 已镜像 main(+Gitee README 本地化) + tags 到 Gitee ${GITEE_REPO}"
echo "✅ 已镜像 main(含 Gitee README 本地化)到 Gitee ${GITEE_REPO}"
- name: Download GitHub Release assets
if: ${{ inputs.sync_release_version != '' }}
env:
VERSION: ${{ inputs.sync_release_version }}
GH_TOKEN: ${{ github.token }}
run: |
set -eu
echo "📥 Downloading release assets for ${VERSION}"
mkdir -p dist
gh release download "$VERSION" \
--repo "$GITHUB_REPOSITORY" \
--dir dist \
--pattern 'dws-*' \
--pattern 'checksums.txt' \
--clobber
ls -la dist/
- name: Verify release artifacts
if: ${{ inputs.sync_release_version != '' }}
env:
VERSION: ${{ inputs.sync_release_version }}
run: |
set -eu
DWS_PACKAGE_DIST_DIR="$GITHUB_WORKSPACE/dist" \
./scripts/release/verify-release-artifacts.sh "$VERSION"
- name: Sync release assets to Gitee
if: ${{ inputs.sync_release_version != '' }}
env:
VERSION: ${{ inputs.sync_release_version }}
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
GITEE_USER: ${{ secrets.GITEE_USER }}
GITEE_REPO: ${{ secrets.GITEE_REPO }}
DIST_DIR: ${{ github.workspace }}/dist
run: |
set -eu
echo "📦 Syncing release assets for ${VERSION} to Gitee ${GITEE_REPO}"
./scripts/release/sync-to-gitee.sh
+6 -4
View File
@@ -1,8 +1,9 @@
name: Multi Profile E2E
name: Main Integration — 主干集成
on:
pull_request:
push:
branches:
- main
workflow_dispatch:
permissions:
@@ -14,7 +15,7 @@ concurrency:
jobs:
multi-profile-e2e:
name: Multi Profile E2E
name: Multi-profile E2E
runs-on: ubuntu-latest
timeout-minutes: 15
env:
@@ -36,7 +37,7 @@ jobs:
mkdir -p .tmp-bin
bash scripts/dev/test-multi-profile-e2e.sh --keep-workdir | tee "$MULTI_PROFILE_E2E_LOG"
{
echo "### Multi Profile E2E"
echo "### Multi-profile E2E"
echo "- Command: \`bash scripts/dev/test-multi-profile-e2e.sh --keep-workdir\`"
echo "- Scope: isolated auth/profile storage, profile switch/use, one-shot profile override, CSV multi-profile aggregation, legacy migration"
echo "- Result: passed"
@@ -50,5 +51,6 @@ jobs:
path: |
.tmp-bin/multi-profile-e2e.*/out
.tmp-bin/multi-profile-e2e.log
include-hidden-files: true
if-no-files-found: ignore
retention-days: 3
+38
View File
@@ -0,0 +1,38 @@
name: Main Integration — Wukong Overlay
on:
workflow_run:
workflows:
- CI
types:
- completed
permissions: {}
jobs:
notify-downstream:
name: Notify Wukong Overlay
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Trigger downstream CI
env:
UPSTREAM_SHA: ${{ github.event.workflow_run.head_sha }}
WUKONG_TRIGGER_TOKEN: ${{ secrets.WUKONG_TRIGGER_TOKEN }}
WUKONG_TRIGGER_URL: ${{ secrets.WUKONG_TRIGGER_URL }}
run: |
if [ -n "$WUKONG_TRIGGER_TOKEN" ]; then
curl --fail --silent --show-error \
-X POST \
-F "token=$WUKONG_TRIGGER_TOKEN" \
-F "ref=main" \
-F "variables[UPSTREAM_SHA]=$UPSTREAM_SHA" \
"$WUKONG_TRIGGER_URL"
echo "Downstream CI triggered."
else
echo "No WUKONG_TRIGGER_TOKEN configured, skipping downstream notification."
fi
-71
View File
@@ -1,71 +0,0 @@
name: Publish npm release
on:
workflow_dispatch:
inputs:
version:
description: "Release tag to publish to npm (e.g. v1.0.48)"
required: true
type: string
permissions:
contents: read
jobs:
publish-npm:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Download GitHub release assets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eu
mkdir -p dist
gh release download "${{ inputs.version }}" \
--repo "${{ github.repository }}" \
--dir dist \
--pattern 'dws-*' \
--pattern 'checksums.txt' \
--clobber
ls -la dist
- name: Stage npm package
run: |
set -eu
version="${{ inputs.version }}"
semver="${version#v}"
pkg_root="dist/npm/dingtalk-workspace-cli"
rm -rf "$pkg_root"
mkdir -p "$pkg_root/assets" "$pkg_root/bin"
cp build/npm/install.js "$pkg_root/install.js"
cp build/npm/bin/dws.js "$pkg_root/bin/dws.js"
cp build/npm/README.md "$pkg_root/README.md"
sed "s|__VERSION__|${semver}|g" build/npm/package.json.tmpl > "$pkg_root/package.json"
cp dist/dws-* "$pkg_root/assets/"
cp dist/checksums.txt "$pkg_root/assets/"
test -f "$pkg_root/assets/dws-skills.zip"
cat "$pkg_root/package.json"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Publish stable to npm
if: ${{ github.repository_owner == 'DingTalk-Real-AI' && !contains(inputs.version, '-') }}
working-directory: dist/npm/dingtalk-workspace-cli
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish prerelease to npm beta
if: ${{ github.repository_owner == 'DingTalk-Real-AI' && contains(inputs.version, '-') }}
working-directory: dist/npm/dingtalk-workspace-cli
run: npm publish --access public --tag beta
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
File diff suppressed because it is too large Load Diff
+301
View File
@@ -0,0 +1,301 @@
name: Reviewer routing
on:
pull_request_target:
branches: [main]
types: [opened, synchronize, reopened, ready_for_review]
# pull_request_target deliberately runs only this workflow from the protected
# base branch. Never check out or execute pull-request code here.
permissions:
contents: write
pull-requests: write
concurrency:
group: reviewer-router-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
route:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out trusted routing policy
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.base.sha }}
persist-credentials: false
- name: Route review and enable auto-merge
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pullNumber = context.payload.pull_request.number;
const eventHeadSha = context.payload.pull_request.head.sha;
const {
REVIEWER_POOL,
requestReviewersWithFallback,
resolveReviewRouting,
reviewerCandidates,
} = require('./.github/reviewer-routing.js');
const reviewerPool = REVIEWER_POOL;
async function getReadyEventPull(phase) {
const {data: currentPull} = await github.rest.pulls.get({
owner,
repo,
pull_number: pullNumber,
});
if (
currentPull.head.sha !== eventHeadSha ||
currentPull.state !== 'open' ||
currentPull.draft ||
currentPull.base.ref !== 'main'
) {
core.info(
`PR #${pullNumber} state or revision no longer matches this ready-main event during ${phase}; routing stopped.`,
);
return null;
}
return currentPull;
}
const pullRequest = await getReadyEventPull('initial read');
if (!pullRequest) {
return;
}
const author = pullRequest.user.login.toLowerCase();
const headSha = pullRequest.head.sha;
const latestPusher =
context.payload.action === 'synchronize'
? context.payload.sender?.login?.toLowerCase()
: author;
async function routeReview() {
let changedFiles;
try {
changedFiles = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: pullNumber,
per_page: 100,
});
} catch (error) {
core.warning(
`Could not inspect changed files for PR #${pullNumber}; using load-balanced fallback (${error.status || 'unknown status'}).`,
);
changedFiles = [];
}
const eligible = reviewerPool.filter(
reviewer =>
reviewer.toLowerCase() !== author &&
reviewer.toLowerCase() !== latestPusher,
);
if (eligible.length === 0) {
core.warning(`No eligible reviewer remains for PR #${pullNumber}.`);
return;
}
const existingRequestedReviewers = new Set(
(pullRequest.requested_reviewers || []).map(({login}) => login.toLowerCase()),
);
let reviews;
try {
reviews = await github.paginate(github.rest.pulls.listReviews, {
owner,
repo,
pull_number: pullNumber,
per_page: 100,
});
} catch (error) {
core.warning(
`Could not inspect existing reviews for PR #${pullNumber}; skipping reviewer routing to avoid a duplicate request (${error.status || 'unknown status'}).`,
);
return;
}
const latestDecisionByLogin = new Map();
for (const review of reviews) {
const login = review.user?.login?.toLowerCase();
if (
!login ||
!['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(
review.state,
)
) {
continue;
}
const previous = latestDecisionByLogin.get(login);
if (!previous || review.id > previous.id) {
latestDecisionByLogin.set(login, review);
}
}
const currentHeadReviewers = new Set(
[...latestDecisionByLogin.values()]
.filter(
review =>
review.commit_id === headSha &&
eligible.some(
reviewer => reviewer.toLowerCase() === review.user.login.toLowerCase(),
) &&
['APPROVED', 'CHANGES_REQUESTED'].includes(review.state),
)
.map(review => review.user.login.toLowerCase()),
);
const loads = new Map(eligible.map(reviewer => [reviewer, 0]));
try {
const openPullRequests = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
});
for (const openPullRequest of openPullRequests) {
for (const reviewer of openPullRequest.requested_reviewers || []) {
const candidate = eligible.find(
login => login.toLowerCase() === reviewer.login.toLowerCase(),
);
if (candidate) {
loads.set(candidate, loads.get(candidate) + 1);
}
}
}
} catch (error) {
core.warning(
`Could not read current reviewer load; using deterministic rotation (${error.status || 'unknown status'}).`,
);
}
const offset = pullNumber % eligible.length;
const rotated = eligible.slice(offset).concat(eligible.slice(0, offset));
const tieOrder = new Map(rotated.map((reviewer, index) => [reviewer, index]));
const staleChangeRequester = [...latestDecisionByLogin.values()]
.filter(review => review.state === 'CHANGES_REQUESTED')
.sort((left, right) => right.id - left.id)
.map(review =>
eligible.find(
reviewer =>
reviewer.toLowerCase() === review.user.login.toLowerCase(),
),
)
.find(Boolean);
const ranked = [...eligible].sort(
(left, right) =>
Number(right === staleChangeRequester) -
Number(left === staleChangeRequester) ||
loads.get(left) - loads.get(right) ||
tieOrder.get(left) - tieOrder.get(right),
);
const routing = resolveReviewRouting({
files: changedFiles,
author,
latestPusher,
fallbackReviewers: ranked,
});
const candidates = reviewerCandidates({
preferredReviewers: routing.reviewers,
fallbackReviewers: ranked,
eligibleReviewers: eligible,
});
const desiredReviewers = candidates.slice(0, routing.requiredReviewers);
if (routing.reason === 'unknown_paths' && currentHeadReviewers.size > 0) {
core.info(
`PR #${pullNumber} has a current-head review for unknown paths; leaving manual ownership unchanged.`,
);
return;
}
core.info(
`PR #${pullNumber} routing: ${routing.reason}; modules=${routing.modules.map(module => module.id).join(',') || 'unknown'}; reviewers=${desiredReviewers.join(',') || 'load-balanced fallback'}.`,
);
const satisfiedReviewers = new Set([
...currentHeadReviewers,
...[...existingRequestedReviewers].filter((reviewer) =>
candidates.some((candidate) => candidate.toLowerCase() === reviewer),
),
]);
const requestResult = await requestReviewersWithFallback({
candidates,
requiredReviewers: routing.requiredReviewers,
satisfiedReviewers: [...satisfiedReviewers],
requestReviewer: async (reviewer) => {
const currentPull = await getReadyEventPull('review request');
if (!currentPull) {
return false;
}
await github.rest.pulls.requestReviewers({
owner,
repo,
pull_number: pullNumber,
reviewers: [reviewer],
});
core.info(
`Requested @${reviewer} for PR #${pullNumber} (open request load: ${loads.get(reviewer)}).`,
);
return true;
},
onFailure: (reviewer, error) => {
core.warning(
`Could not request @${reviewer} for PR #${pullNumber}; trying the next candidate (${error.status || 'unknown status'}).`,
);
},
});
if (requestResult.aborted) {
return;
}
if (requestResult.satisfiedReviewers.length < routing.requiredReviewers) {
core.warning(
`Only ${requestResult.satisfiedReviewers.length} of ${routing.requiredReviewers} required reviewers could be satisfied for PR #${pullNumber}.`,
);
}
}
async function enableAutoMerge() {
try {
const currentPull = await getReadyEventPull('auto-merge enable');
if (!currentPull) {
return;
}
if (currentPull.auto_merge) {
core.info(`Auto-merge is already enabled for PR #${pullNumber}.`);
return;
}
await github.graphql(
`mutation EnableAutoMerge($pullRequestId: ID!) {
enablePullRequestAutoMerge(
input: {
pullRequestId: $pullRequestId
mergeMethod: MERGE
}
) {
pullRequest {
autoMergeRequest {
enabledAt
}
}
}
}`,
{pullRequestId: currentPull.node_id},
);
core.info(`Enabled native auto-merge for PR #${pullNumber}.`);
} catch (error) {
core.warning(
`Could not enable auto-merge for PR #${pullNumber}; checks and review can continue normally (${error.message}).`,
);
}
}
try {
await routeReview();
} catch (error) {
core.warning(
`Reviewer routing hit an unexpected error for PR #${pullNumber}; review can still proceed manually (${error.message}).`,
);
}
await enableAutoMerge();
@@ -1,50 +0,0 @@
name: Sync release to Gitee
# Manually mirror a published GitHub release's assets to the matching Gitee
# release. Use this to repair a release whose Gitee mirror is incomplete (e.g.
# the Release job timed out mid-upload). It runs ONLY the idempotent Gitee sync
# step — it does not run GoReleaser and does not touch the GitHub release, so
# there is no release outage. The sync script skips assets already on Gitee, so
# this only uploads what is missing.
on:
workflow_dispatch:
inputs:
version:
description: "Release tag to mirror to Gitee (e.g. v1.0.42)"
required: true
type: string
permissions:
contents: read
jobs:
sync-gitee:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Download GitHub release assets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eu
mkdir -p dist
gh release download "${{ inputs.version }}" \
--repo "${{ github.repository }}" \
--dir dist \
--pattern 'dws-*' \
--pattern 'checksums.txt' \
--clobber
ls -la dist
- name: Mirror release to Gitee (China)
# Idempotent: uploads only assets not already present on the Gitee release.
run: ./scripts/release/sync-to-gitee.sh
env:
VERSION: ${{ inputs.version }}
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
GITEE_USER: ${{ secrets.GITEE_USER }}
GITEE_REPO: ${{ secrets.GITEE_REPO }}
+148
View File
@@ -0,0 +1,148 @@
name: Withdraw release
run-name: Withdraw ${{ inputs.version }}
on:
workflow_dispatch:
inputs:
version:
description: "Exact published version to withdraw (vX.Y.Z or vX.Y.Z-beta.N)"
required: true
type: string
reason:
description: "Public, single-line withdrawal reason (8-300 characters)"
required: true
type: string
confirmation:
description: "Type WITHDRAW followed by a space and the exact version"
required: true
type: string
permissions:
contents: read
# Share the publication lock with release.yml. A withdrawal and a publication
# must never mutate channel pointers concurrently.
concurrency:
group: dws-release-publication
cancel-in-progress: false
jobs:
withdraw:
name: Withdraw release from every distribution channel
environment: release-withdrawal
runs-on: ubuntu-latest
timeout-minutes: 180
permissions:
actions: read
contents: write
steps:
- name: Verify withdrawal environment protection
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const response = await github.request(
"GET /repos/{owner}/{repo}/environments/{environment_name}",
{ owner, repo, environment_name: "release-withdrawal" },
);
const reviewerRule = response.data.protection_rules.find(
(rule) => rule.type === "required_reviewers",
);
if (
!reviewerRule ||
reviewerRule.prevent_self_review !== true ||
!Array.isArray(reviewerRule.reviewers) ||
reviewerRule.reviewers.length === 0
) {
core.setFailed("release-withdrawal must require a reviewer and prevent self-review");
return;
}
if (response.data.deployment_branch_policy?.protected_branches !== true) {
core.setFailed("release-withdrawal must allow only protected branches");
}
if (response.data.can_admins_bypass !== false) {
core.setFailed("release-withdrawal must not allow administrator bypass");
}
- name: Require the exact current official default-branch commit
uses: actions/github-script@v7
with:
script: |
const expectedRepository = "DingTalk-Real-AI/dingtalk-workspace-cli";
const defaultBranch = context.payload.repository.default_branch;
if (context.eventName !== "workflow_dispatch") {
core.setFailed("release withdrawal accepts workflow_dispatch only");
return;
}
if (`${context.repo.owner}/${context.repo.repo}` !== expectedRepository) {
core.setFailed(`release withdrawal is restricted to ${expectedRepository}`);
return;
}
if (context.ref !== `refs/heads/${defaultBranch}`) {
core.setFailed(`release withdrawal must be dispatched from ${defaultBranch}`);
return;
}
const branch = await github.rest.git.getRef({
...context.repo,
ref: `heads/${defaultBranch}`,
});
if (branch.data.object.sha !== context.sha) {
core.setFailed(
`default branch advanced to ${branch.data.object.sha}; re-dispatch from the new head`,
);
}
- name: Check out trusted withdrawal tooling
uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js for npm channel withdrawal
uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Withdraw immutable release and roll back channels
id: withdrawal
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_EVENT_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
WITHDRAW_VERSION: ${{ inputs.version }}
WITHDRAW_REASON: ${{ inputs.reason }}
WITHDRAW_CONFIRMATION: ${{ inputs.confirmation }}
OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
OSS_ENDPOINT: ${{ secrets.OSS_ENDPOINT }}
OSS_BUCKET: ${{ secrets.OSS_BUCKET }}
OSS_PREFIX: ${{ secrets.OSS_PREFIX }}
GITEE_TOKEN: ${{ secrets.GITEE_TOKEN }}
GITEE_USER: ${{ secrets.GITEE_USER }}
GITEE_REPO: ${{ secrets.GITEE_REPO }}
DWS_GITEE_ENABLED: ${{ vars.ENABLE_GITEE_UPLOAD_FALLBACK == 'true' && 'true' || 'false' }}
HOMEBREW_PR_TOKEN: ${{ secrets.HOMEBREW_PR_TOKEN }}
run: |
./scripts/release/withdraw-release.sh \
"$WITHDRAW_VERSION" \
"$WITHDRAW_REASON" \
"$WITHDRAW_CONFIRMATION"
- name: Report withdrawal boundary
if: ${{ always() }}
env:
VERSION: ${{ inputs.version }}
RESULT: ${{ steps.withdrawal.outcome }}
run: |
{
echo "### Release withdrawal: ${VERSION}"
echo
echo "- Workflow result: ${RESULT}"
echo "- Success means every configured channel was verified and the permanent withdrawn/${VERSION} tombstone remains as the version-reuse barrier."
echo "- Failure may occur before or after the tombstone/channel mutations; inspect the failed step and rerun the exact same inputs after fixing the cause."
echo "- The problem GitHub Release and original tag are removed after npm and every tag-enabled/configured mirror are rolled back, so GitHub installers stop resolving the bad version while the Homebrew rollback PR is reviewed."
echo "- npm is deprecated rather than unpublished; already-installed clients cannot be remotely downgraded."
echo "- If a Homebrew rollback PR was opened, this run remains failed until that PR is independently reviewed, merged, and the workflow is rerun."
} >> "$GITHUB_STEP_SUMMARY"
+29
View File
@@ -19,6 +19,11 @@ test/cli_compat/testdata/
/internal/compat/testdata/*
.gitignore
.worktrees/
.qoder/
_logs/
_docs/
_output/
vendor/
# Secrets & credentials
.env
@@ -42,5 +47,29 @@ dws.zip
# 功能测试运行产物
results.jsonl
test/dev_functional/results.jsonl
/auto-test/
/eval-runs/
/.qoder/
.vercel
.env*
# Local Go coverage output
/coverage.txt
/coverage-base.txt
/coverage-policy.txt
/coverage.html
dwsbin
# Local shortcut eval / real-backend capture artifacts — may contain real PII
# (employee names/emails, userIds, conversation & message IDs). Never commit.
/docs/shortcut-real-read-results.json
/docs/shortcut-real-write-results.json
/docs/shortcut-comparison.html
/docs/shortcut-gsb-eval.*
/scripts/run_shortcut_real_read_matrix.py
# Local coverage artifacts
coverage-shortcut.txt
coverage-*.txt
# stray compiled generator binary (source lives in internal/generator/cmd_param_aliases/)
/cmd_param_aliases
+5 -8
View File
@@ -1,19 +1,14 @@
# GoReleaser configuration for dws
# Docs: https://goreleaser.com
#
# To release:
# git tag -a v0.1.0 -m "Release v0.1.0"
# git push origin v0.1.0
# To release, use scripts/release/release.sh. It seals main, validates the
# CHANGELOG and packages, then pushes the annotated tag for CI/CD to publish.
#
# To test locally (no publish):
# goreleaser release --snapshot --clean
version: 2
before:
hooks:
- go mod tidy
builds:
- main: ./cmd
binary: dws
@@ -67,7 +62,9 @@ release:
# 用当前运行 CI 的仓库 owner: fork CI 发到 fork, 官方 CI 发到官方, 两边都对
owner: "{{ .Env.GITHUB_REPOSITORY_OWNER }}"
name: dingtalk-workspace-cli
draft: false
# Keep the release private until post-processing has replaced the Darwin
# archives and verified every finalized asset digest.
draft: true
prerelease: auto
name_template: "v{{.Version}}"
mode: replace
+615
View File
@@ -0,0 +1,615 @@
# Repository Agent Guide
This file applies to the entire repository. Keep changes scoped, preserve
unrelated work, and use `gofmt` for every modified Go file.
## Build and test
- Build: `make build` (wraps `scripts/dev/build.sh` → `go build -o dws ./cmd`; bare `go build ./cmd` fails because output name `cmd` collides with the directory)
- Full test suite: `DWS_PACKAGE_VERSION=0.0.0-test go test ./...`
- Param aliases generate: `go generate ./internal/cli` (entry point: `internal/cli/gen.go`; Catalog is not generated)
- Optional diagnostic MCP dump (not a Schema pin): `make fetch-mcp-metadata` (requires `dws auth login`; writes under `artifacts/`)
- Check generated drift + assembly determinism: `./scripts/policy/check-generated-drift.sh`
- Check the Schema contract: `./scripts/policy/check-schema-catalog.sh`
- Coverage-gate test naming: tests that carry coverage for the macOS platform
gate must be named `TestCrossPlatformCoverage*` (or `TestAllShortcuts*`);
`scripts/policy/run-platform-coverage-gate.sh` only selects those prefixes,
so a covering test with any other name silently leaves its target uncovered.
- Package-var injection seams (e.g. `pipelineBuildEffectiveRegistry`): swap
them in tests only via `testseam.Swap(t, &seam, stub)` from
`internal/testseam` — it restores the previous value through `t.Cleanup`
structurally. Like the manual pattern it replaces, Swap mutates global state
and is **not** safe for `t.Parallel` tests.
- Cross-package test helpers (e.g. `StoreProductDeclRawForTest`) live in
per-package `fortest.go` files, never scattered through production files;
the `ForTest` suffix is the boundary and production code must not call them.
Schema Catalog delivery is **声明即 Catalog**: production assembles via
`RegisterSchemaSourceRoot` → `ResolveSchemaBuild` (factory registered in
`internal/app`). There is no
`cmd_schema_catalog` `//go:generate` delivery step. `dws schema -f json` remains
the wire projection. `cmd_schema_catalog` produces CI/local dumps only;
`internal/cli/schema_catalog/`, `internal/cli/schema_meta_index.gob`, and
`internal/cli/schema_meta_index.json` must not be committed. `schema_agent_metadata/` is retired: if that directory
(or `schema_agent_metadata_audit.json`) is present, policy fails.
Command identity is no longer a file input: it is collected from
`ContractFinal.Identity` on the live Cobra leaves
(`internal/cli/schema_identity_collect.go` → `BuildEffectiveCommandRegistry`).
The reviewed `schema_command_registry/` was retired together with that
switchover and must not reappear; identity changes happen by editing the leaf
declaration. The remaining **reviewed inputs** under `internal/cli` (see Agent
Schema contract) keep separate authorities — do not merge them with
`param_concepts.json` or promote any of them into Catalog declaration.
## Command framework declaration
- Framework definition: `docs/rfc-command-framework-convergence.md` **§5.0**
- Today: `helpers.LeafSpec` / `shortcut.Shortcut` → `corecmd.Spec` (+ optional `Contract`) → `corecmd.New`
- **Declare = final Schema source**: `Flags` / `Constraints` / `Safety` / `ConstParams` / `Contract` (`corecmd.ContractDecl`; nested fields are `contract.*`)
- Naming: `ContractDecl` is the authoring leaf declaration. "Schema" means Catalog / `ToolSpec` delivery — do not reintroduce `SchemaDecl`.
- `Safety` uses `contract.SafetySpec` (`internal/corecmd/contract` only — no `cli.*` type alias). Its `confirmation` drives the runtime gate; `effect` / `risk` / `idempotency` are published unchanged. When `Contract` is set, convert once via `contractfinal.RegisterRuntimeContractFinal` (all callers — `corecmd.New` registers internally); assembly **pass-throughs** Final.
- Package seam:
- types / ProductDecl → `corecmd/contract` (DTO only; **no** Cobra-keyed ContractFinal store)
- AnnotateRuntime* writers → `internal/corecmd/runtimeannotate` (framework-owned)
- ContractFinal cobra store + Register → `internal/corecmd/contractfinal` (framework-owned)
- homology gates → `internal/cli/homology`
- Catalog assembly / `ResolveMeta` (`RegisterSchemaSourceRoot` → `ResolveSchemaBuild`); go:embed only for reviewed inputs → `internal/cli` root (package-local aliases for annotate/store APIs live in `runtime_schema_seam.go`; the former `cli/runtimeannotate` / `cli/contractfinal` shim packages are removed — import `corecmd/*` directly)
- **Hard rule**: `internal/corecmd` (and its subpackages) must **not** import any `internal/cli` package
- Authoring tiers (current, not aspirational):
- **Tier1** — `corecmd.New` / `helpers.NewLeafCommand` (fully managed declare + execute)
- **Tier2** — `DeclareLeafMetadata` (helpers migration; **Shortcut may also use this path — acceptable**)
- **Tier3** — bare Cobra (should shrink over time; reviewed exclusions where needed)
- Long-term outlook only: broader mcpbind / fewer hand-written `Execute` bodies. **Not** a current hard requirement to delete `Shortcut.Execute` or force mcpbind.
- Description declare vs delivery: construction requires `ContractDecl.Description` (evidence). Catalog delivery prefers Cobra Long → provenance `cobra_help`; without Long, declared text → `contract_final`. Title: declared first, then Short, then MCP. Do **not** read this as "declare = wire final" or dual authority.
- **Execute** = hooks (`Validate` / `Call` / `RunE` / `PostMount`) — not a second surface authority
- Declaration path has **no reviewed parallel fields**; migration-only `runtime_gate` annotate until `Safety` is declared
- **Do not add** new production `AnnotateRuntimeRisk` / `AnnotateRuntimeGate`
(`runtime_gate`) call sites; migrate leaves to declared `Safety` /
`ContractDecl` instead. Existing annotate sites may remain until migrated.
## flag / help / schema homology
- Decision (path A — Contract/LeafSpec is CLI-surface authority **and must embed into Schema**): `docs/flag-help-schema-homology.md`
- Hard rule: every help/Schema fact is **declared** **or** **annotated**; never inference-only (§1.1–§1.3; framework §5.0).
- Embed path: `corecmd.New` → `dws.schema.*` annotations → Schema catalog assembly
- MCP metadata must not create CLI flags; optional 1:1 passthrough is a gated subset only.
- Gate IDs: `HOM-P*`, `HOM-S*`, `HOM-I1`, `HOM-D1` (see that doc §3–§4). `HOM-P1`/`HOM-D1`/`HOM-S1`/`HOM-S2` are on the `check-schema-catalog.sh` policy whitelist; remaining IDs land incrementally.
## Agent Schema contract
The Schema data flow is one way:
```text
1. app.NewRootCommand()
└─ builds the real Cobra command tree and flags
└─ leaf Safety / Contract / contract.ParamDecl declare ContractFinal (declare-or-annotate)
2. CollectIdentitySpecs (ContractFinal.Identity on live Cobra leaves)
└─ forms EffectiveCommandRegistry
└─ binds exactly to real Cobra leaves and aliases
3. Parameter resolution
Cobra flags
+ contract.ParamDecl.Property / native annotations (primary property authority)
+ schema_parameter_mapping_ledger.go (mapping_exclusions / removals only;
active bindings JSON retired after Track 1 Phase 2)
└─ produces ParameterSpec and constraints
4. Agent and interface semantics
ProductDecl + leaf ContractFinal Selection / Safety / Interface
+ contract.ParamDecl (interface_type / property)
└─ resolves Agent metadata by source precedence
Markdown is evidence only; it is not concatenated into final prose
└─ schema_hints/ and schema_mcp_metadata.json are fully retired
5. One typed hub
BoundCommandRegistry
+ ParameterSpec
+ Agent metadata
+ Interface metadata
└─ resolves every command exactly once into ToolSpec
└─ aggregates SchemaRegistry + SchemaIndex
└─ ResolveSchemaBuild assembles at runtime; deliverySchemaCatalog wraps it (lazy, sync.Once)
6. Runtime delivery (no generate-written Catalog authority)
SchemaRegistry
└─ dws schema list/product/group/leaf/--all (-f json wire)
└─ ResolveMeta projects Identity/Safety/Selection from the same registry
└─ CI may dump Catalog via cmd_schema_catalog for jq gates / determinism
```
**Reviewed inputs / 评审输入** (organizational family under `internal/cli`;
parallel peers, not one merged authority). These are assembly inputs only —
never Catalog declaration authority, never leaf `Contract` / `ProductDecl`
substitutes. Keep them side-by-side; do **not** fold one into another:
| Input | Path | Owns |
|---|---|---|
| Command identity | collected from `ContractFinal.Identity` on live Cobra leaves (`schema_identity_collect.go`; not a file input) | stable identity, primary CLI path, aliases, navigation |
| Param concepts | `param_concepts.json` (+ `.schema.json`) | argv synonym / concept dictionary (reduced to `param_aliases_generated.go`) |
| Exclusions | `schema_command_exclusions.go` | exact reviewed CLI paths excluded from Schema (non-empty reason) |
| Mapping ledger | `schema_parameter_mapping_ledger.go` | `mapping_exclusions` / removals (CLI flags with no direct RPC property); active bindings JSON retired |
`schema_mcp_metadata.json` is retired and must not reappear. Interface facts
(`interface_ref`, `interface_type`, …) declare on leaf `Contract` /
`contract.ParamDecl`. Retiring the pin cleared MCP-sourced `interface_type`
values from the wire; schema-compat deliberately accepts clearing (missing =
unknown for consumers) while still rejecting any change to a different
non-empty value. Re-populating a value requires an explicit `ParamDecl`
declaration, not a new pin.
**Aliases are three distinct layers** (do not conflate):
| Layer | Owns |
|---|---|
| `FlagSpec.Aliases` / Cobra flag aliases | executable flag synonyms on a leaf |
| `ContractFinal.Identity` `aliases` | reviewed CLI-path aliases for the same command identity |
| `param_concepts.json` | argv synonym / concept dictionary (central preparse normalization) |
**Visibility vs exclusions:** collected identity `visibility` is dormant (all
entries default `public`); “runnable but not Agent-visible” belongs in
`schema_command_exclusions.go`, not new `visibility` values. Native identity
annotations are consistency assertions only — they must agree with the
collected identity and never materialize or override it.
Leaf declare (`Contract` / `ParamDecl` / `Safety` / `ProductDecl`) and the live
Cobra tree remain separate from this table: declare owns semantics; Cobra owns
executability and flags.
After binding there is no second identity source and no identity precedence
winner. The binder must reject a missing/non-runnable Cobra path, an alias
collision, and any native identity annotation that disagrees with the effective
registry. A missing native identity annotation is allowed because annotations
are implementation-side assertions, not identity fallbacks.
The assembler resolves every bound command exactly once into one `ToolSpec`.
CI determinism (`check-schema-assembly.sh`) and policy jq gates consume a
fresh assembly dump; runtime consumes the same `ResolveSchemaBuild` path via
`RegisterSchemaSourceRoot`. Neither path may reopen annotations, merge source
records, or use a previous Catalog JSON as a source.
### Assembly vs consumption
**Assembly** (declare → typed registry; CI + runtime):
- Runtime entry: `RegisterSchemaSourceRoot` (`internal/app`) →
`ResolveSchemaBuild` / `deliverySchemaCatalog` (lazy, sync.Once).
- CI tool: `cmd_schema_catalog` dumps an assembled Catalog for jq/determinism;
it is **not** a `//go:generate` or committed delivery step.
- `gen.go` only generates `param_aliases_generated.go`.
- Inputs: **reviewed inputs** (param_concepts / exclusions / mapping ledger —
see table above) + ProductDecl/ContractFinal (identity is collected from
`ContractFinal.Identity`) + live Cobra tree.
`schema_hints/`, `schema_agent_metadata/`, `schema_command_registry/`, and
`schema_mcp_metadata.json` must not reappear.
- Gates: `make generate-schema` (param aliases + assembly determinism),
`check-generated-drift.sh`, `check-schema-catalog.sh`.
**Consumption** (runtime, unified API):
- Entry point: `ResolveMeta(cliPath) → CommandMeta{Identity, Safety, Selection}`
in `internal/cli/command_meta.go` — projected from the assembled registry
when the app factory is registered.
- Consumers: `--help` (Safety annotation via `RenderSafetyAnnotation`),
agent selection, future skill generation; `dws schema` uses the same
assembled Catalog (`-f json` wire unchanged).
- `SafetyForCLIPath` delegates to `ResolveMeta` (backward compatible).
This split is architecturally isomorphic to Lark's typed metadata registry,
navigation catalog, and schema renderer. DWS intentionally preserves its
existing flat JSON wire contract for compatibility; do not treat architectural
alignment as permission to make an unversioned wire-format change.
The identity collected from `ContractFinal.Identity` (via
`CollectIdentitySpecs`) is the sole source of stable command identity and
navigation. The executable Cobra tree remains the source of truth for whether
a CLI path exists, is runnable, and which flags it accepts. Schema coverage is
bidirectional:
1. Every final `SchemaRegistry` tool, including its serialized Catalog
projection, must resolve to an executable Cobra command.
2. Every public runnable Cobra leaf must either resolve to Schema or appear as
an exact, reviewed exclusion with a non-empty reason in
`internal/cli/schema_command_exclusions.go` (central Go groups; not JSON).
Do not use prefix or wildcard exclusions: they can silently hide future
commands. Remove an exclusion when its command enters Schema; stale, invalid,
or duplicate exclusions must fail generation and CI.
When adding or changing an Agent-visible command, review all relevant inputs:
- Leaf `ContractFinal.Identity` for canonical identity, primary CLI path,
aliases, and stable navigation. Identity is collected from the live Cobra
leaves (`CollectIdentitySpecs`); there is no separate identity file. Invalid
canonical paths, alias collisions, stale paths, and drift fail collection,
binding, and policy.
- Leaf `Safety` / `Contract` (`corecmd.ContractDecl`) / `contract.ParamDecl`
(helpers `LeafSpec` or shortcut `Contract`) for parameter facts, interface
disposition, safety, and Agent selection prose. Delivered provenance is
`contract_final` from `corecmd.contract` (description may stamp `cobra_help`
when Cobra Long wins). Product routing uses `ProductDecl`
(`internal/corecmd/contract`; provenance label remains `cli.product_decl`).
- `internal/cli/schema_hints/` is fully retired. Do not reintroduce HintFiles,
audit JSON, or `imported/` baselines; declare on ProductDecl / the owning
leaf instead.
- Native Runtime Schema identity annotations, when present, as consistency
assertions against `EffectiveCommandRegistry`. They must agree exactly and
must never materialize, infer, or override registry identity.
- Flag-to-interface property mappings and required/default semantics.
- Do not expect generate-written Catalog delivery. Run
`make generate-schema` only to refresh param aliases and prove assembly
determinism. Do not expect or commit `schema_agent_metadata/`.
Run the reverse-completeness tests whenever the Cobra tree changes. A command
that works through `dws <path>` but cannot be found through the matching
`dws schema` lookup is a contract failure unless it has a reviewed exact
exclusion.
`RegisterSchemaHints` / `ToolSchemaHint` overlays are fully removed. Parameter
and selection facts must be declared on the owning leaf (`contract.ParamDecl` /
`Contract`) or via `ProductDecl`; do not reintroduce overlay registries.
For Agent-authored selection edits:
1. Confirm the exact command and flag names in the current Cobra tree.
2. Declare selection prose on the owning leaf (`Contract.Selection` /
`DeclareLeafMetadata`) and product routing via `ProductDecl`; declare
safety / parameters / interface on the same leaf.
3. Do not copy generated Catalog fields into source inputs.
4. Run generation, drift, Schema policy, and the focused CLI tests before
proposing the change.
## Agent curation workflow
Use this workflow when refreshing Agent selection prose and confirmation
alignment. Prefer **agent-authored review** over bulk merge scripts that dump
Skill Markdown into Catalog fields.
Human-authored inputs:
| Block | Path | Owns |
|---|---|---|
| **declaration** | helpers / shortcut `Safety` + `Contract` / `contract.ParamDecl` + `ProductDecl` | `effect` / `risk` / `confirmation` / `idempotency` / `interface_*` / parameter facts / selection prose (`contract_final`) |
`schema_hints/` is fully retired. Do not reintroduce HintFiles or audit JSON.
### Goals
1. **Selection prose** is decision-oriented (Feishu/Lark style): trigger intent,
sibling-command routing, and outcome shape — not a restatement of the
summary. Delivered Catalog provenance is `contract_final` from leaf
`Contract.Selection` / `ProductDecl`.
2. **Safety** follows Runtime: `confirmation=user_required` when the leaf
Contract/Safety (or remaining `runtime_gate` annotate) requires a user gate
(for example `confirm_delete`, `typed_yes`, `confirm_dangerous`).
3. **Parameter facts** are declared on the leaf (`contract.ParamDecl` /
`Contract.Parameters` / FlagSpec). Do not reintroduce HintFile or
`RegisterSchemaHints` overlays.
### Authoring
For every curated tool:
1. Declare safety/interface/parameters/selection on the owning leaf
(`DeclareLeafMetadata` / `Shortcut.Contract` / `contract.ParamDecl`) and product routing
via `ProductDecl` when needed.
2. Run `make generate-schema` (param aliases + assembly determinism). Do not
create or commit `schema_catalog/` or Schema meta-index fixtures.
### Pull live MCP descriptions (personal token)
Schema delivery no longer embeds a pinned MCP JSON. Prefer live Schema from a
logged-in personal session when reviewing interface facts before declaring them
on the leaf:
```bash
dws auth status # token_valid should be true
dws schema <mcp-canonical> --jq '{canonical_path,interface_ref,parameters}' -f json
# or CLI path: dws schema --cli-path "drive copy" --jq '{canonical_path,interface_ref,parameters}' -f json
```
Resolve MCP identity via declared `interface_ref` when CLI canonical ≠ MCP path
(example: CLI `drive.copy_document` → live `doc.copy_document`). On pull
failure, fall back to Skill + Cobra Help, and record evidence
(for example `live-dws-schema:<path>#FAILED`). Never print or commit tokens.
`make fetch-mcp-metadata` writes an optional diagnostic dump under `artifacts/`
only — do not commit it as a Schema pin.
Precedence when sources disagree: **Runtime/Cobra / leaf Contract > live MCP >
Skill (evidence only)**.
### Parallel product agents
Split work by product groups. Each agent must:
- Read Skill, Cobra/`--help`, Runtime confirmation sites, and live
`dws schema <leaf> --compact` for its tools. Mapping/interface/provenance
audits may query the full leaf only through a narrow `--jq` / `--fields`
projection; do not load an entire full leaf into Agent context.
- Hand-write selection prose and leaf Contract / ProductDecl declarations;
forbid wholesale JSON merges from review dumps.
- Edit only its product’s leaf declarations (and `ProductDecl` when needed).
- **Never** `git checkout` unrelated product files to “clean scope”.
### Regenerate and gates
```bash
make generate-schema
./scripts/policy/check-runtime-confirmation-truth.sh
go test ./internal/app -run '^TestSheetFinalSchemaConfirmationMatchesRuntimeGuards$' -count=1
```
`check-runtime-confirmation-truth.sh` compares live ContractFinal.Safety with the assembled ToolSpec `confirmation=user_required` and probes the runtime gate.
`schema_hints/` must stay absent.
Example rules (fail generation otherwise):
- At most two examples per tool; no `--yes` in stored examples.
- Examples must match live Cobra argv (path, flags, required groups).
- No shell comments in examples.
After generation, spot-check Catalog: selection and safety/interface
provenance are `contract_final` from ProductDecl / leaf declarations
(`user_required` must match Runtime confirmation gates).
`make generate-schema` refreshes `param_aliases_generated.go` and runs
assembly determinism (`check-schema-assembly.sh`). It does not rewrite a
committed Catalog as delivery authority — runtime reassembles from
declarations. Byte guards fail if generation mutates parameter-concept
inputs; policy fails if the retired `schema_command_registry/` reappears.
Selection prose may choose a more or less restrictive recommendation. It cannot
create a Cobra command or flag, change parameter facts, invent an
RPC/interface, alter safety metadata, or bypass command completeness. Examples
must use an executable primary/alias path and flags accepted by the live Cobra
command; never add `--yes` to stored examples.
Every example is always checked against its real `BoundCommand`: exact path,
accepted flags, Cobra required flags/positionals, and the effective
`require_one_of`, `require_together`, and `mutually_exclusive` constraints must
all pass before execution eligibility is considered. A missing required value,
constraint failure, runtime error, or MCP resolution error is a contract bug;
none is a valid reason to skip an example.
Example execution defaults to contract validation only. Runtime execution is
opt-in: an example enters `dry_run` only when its final `ToolSpec` publishes an
explicit reviewed dry-run capability. The test never injects `--yes`, and
`risk`/`confirmation` values do not manufacture preview support. A narrow
runtime precondition that cannot be derived from the typed contract may use an
exact zero-based `example_dispositions` entry with `mode=contract_only`,
`reviewed=true`, one of the schema-enumerated reason codes, and a concrete
non-empty reason. Such a disposition may only narrow an explicit dry-run
capability; it cannot turn an ordinary contract-only example into a skip.
Duplicate, missing, and out-of-range indexes fail validation. Never catch a
dry-run failure and dynamically downgrade it to `contract_only`.
Normal Go tests run the exhaustive contract gate. Run
`make test-schema-agent-examples` to additionally execute the eligible subset
through the real Cobra `--dry-run` path with isolated HOME and blocked proxies.
The test reports stable `total`, `contract`, `dry_run`, `contract_only`,
`reviewed_manual`, and per-reason counts; changing those counts requires a
review of the corresponding typed dry-run capability or manual disposition.
This target is also part of `make policy`.
Treat every tool `use_when` entry as a reviewed positive selection scenario
whose expected result is that tool's canonical path, and every `avoid_when`
entry as a reviewed negative scenario that must not choose that tool. The
deterministic gate derives a typed evaluation fixture from these same fields;
it requires exact tool coverage, a real runnable `BoundCommandRegistry`
primary command, at least one positive and negative assertion per tool, and no
literal contradictory expectations. It does not claim that string matching
proves natural-language understanding.
Semantic selection is an explicit opt-in live-model check. Run the smoke set
(one positive and one negative scenario per product) with
`DWS_AGENT_SELECTION_LIVE=1 ARK_API_KEY=... ARK_BASE_URL=... ARK_MODEL=... go test ./internal/app -run TestAgentSelectionArkLive -count=1`.
Add `DWS_AGENT_SELECTION_FULL=1` to evaluate every committed tool scenario, or
set `DWS_AGENT_SELECTION_CASES` to comma-separated fixture case IDs. Normal CI
never calls a model; its blockers remain the reproducible fixture, binding,
example, provenance, and final-delivery facts.
The live evaluator sends only case IDs/scenarios plus one same-product
candidate table; expected/forbidden assertions stay local and must never be
included in the model prompt. Built-in Ark HTTPS bases are allowlisted. A
different HTTPS provider requires its exact base in
`DWS_AGENT_SELECTION_ALLOWED_BASE_URLS`; plaintext HTTP is accepted only for a
loopback test server so API credentials are never sent to an arbitrary clear
text endpoint.
## Safety metadata
Parameter and safety resolution is mostly source-precedence based and
value-neutral: do not choose a winner because one value looks stricter. A
higher-priority reviewed metadata/explicit source may intentionally raise or
lower description, mapping, `effect`, `risk`, `confirmation`, or `idempotency`.
Preserve all candidates and the selected source in provenance, and fail
same-precedence conflicts rather than silently merging them.
`required` is the exception. Cobra `MarkFlagRequired` is a hard floor: the
final Agent projection must keep `required=true` and cannot be lowered by a
lower-precedence source. A higher-precedence declaration may still raise an
optional flag to required. `cli_required` continues to mirror the executable
Cobra marker.
For command-level description: **declare required, delivery Long may win**.
`ContractDecl.Description` is mandatory at construction (declaration evidence).
Catalog delivery prefers Cobra Long when present (provenance `cobra_help`,
resolution `cobra_help_preferred`); without Long, the declared Description is
delivered as `contract_final`. Title keeps declared ContractDecl /
ContractFinal first, then Cobra Short, then MCP metadata. This is one authority
chain with an explicit delivery preference — not two competing sources.
Generic RPC prose may remain an unselected provenance candidate (and
parameter-level `interface_description`); it must not overwrite a specialized
leaf's title or description.
For every delivered `ToolSpec` and `ParameterSpec` field, the provenance
winner value must exactly equal the delivered value. Checking only source,
count, presence, or hash is not a sufficient final-delivery invariant.
The same resolved `ToolSpec` must drive every projection. The full leaf payload
must equal the corresponding tool in `schema --all` and the full Catalog tool.
Overview/product/group summaries and Catalog summaries must equal
`ToolSpec.ToSummaryPayload()`. An alias lookup may change only the view fields
`cli_path` and `is_alias`; it must not re-resolve or mutate the command
contract.
This build-time rule is distinct from runtime drift handling. If shipped Help
and leaf Schema disagree, pass only flags accepted by Cobra. For conflicting
safety information, do not silently take the less restrictive behavior: use
the safer interpretation or stop and report the contract drift.
Do not infer one safety field from another. In particular, `effect=destructive`
or `risk=high` does not mechanically rewrite `confirmation`; the final
precedence winner for each field is authoritative. When
`confirmation=user_required`, obtain confirmation before adding `--yes`.
Keep CLI confirmation behavior and Schema metadata consistent, and add a
semantic regression test through the final embedded loader/query delivery
path; a generator unit test or JSON count alone is insufficient.
## Unified result Schema and performance
The unified runtime envelope and the per-command Schema result declaration are
related but distinct contracts:
- Runtime owns the outer machine envelope (`ok`, `outcome`, `data`, `error`,
`meta`) and derives it through `internal/output`. Business commands return a
`CommandResult`; they must not hand-author the outer JSON shape.
- A leaf `Contract.Result` / `contract.ResultSpec` describes the reviewed
business value inside `data`. It may declare `outcomes`, `data_schema`, and
`sensitive_paths`. `Contract.Pagination` is a separate command capability
because pagination is emitted under envelope `meta`, not inside `data`.
- `outcomes` is the set of results a command may produce; it is not the outcome
of the current invocation. `data_schema` is a JSON Schema object for business
data and must not duplicate the framework envelope.
- Result declarations are delivered in the full leaf and in the reviewed
`--compact` Agent projection. Compact retains the normalized `result` object
verbatim but still omits provenance, interface bindings, and other audit-only
fields. Product/group summaries remain navigation views and need not repeat
every leaf Result. When an Agent needs return-shape facts, query the compact
leaf directly; do not load the whole full Catalog.
- A missing `result` means “no reviewed return-value declaration is published
for this leaf.” It does **not** prove that the runtime is legacy, and it must
not be filled by inference from examples, MCP samples, or previous command
output. Runtime rollout remains an internal per-command fact.
- The public contract has no `contract_version`, no `--output-contract`, and no
Agent-selectable protocol alias. Agents continue to request machine output
with `--format json`; migrated commands use the unified result directly and
unmigrated commands retain their current legacy output.
- Existing `dev` / `devapp` pilot coverage is gradual. Active reviewed
`devapp` shortcuts are gated on a non-empty Result declaration, while `dev`
currently has representative Result coverage. Do not describe that as
repository-wide coverage. Any newly activated Agent-visible command should
add and test its Result declaration; the remaining pilot gaps should shrink,
not expand.
The compact/full leaf `result` object has one stable shape:
```json
{
"result": {
"outcomes": ["success", "pending", "partial_failure", "failure"],
"data_schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string", "description": "Stable resource ID"},
"name": {"type": "string", "description": "Display name"}
}
}
}
}
},
"sensitive_paths": ["credential.secret"]
},
"pagination": {
"kind": "cursor",
"cursor_parameter": "cursor",
"meta_path": "meta.pagination",
"endpoint_exhausted_path": "meta.pagination.endpoint_exhausted",
"next_token_path": "meta.pagination.next_token"
}
}
```
Field rules:
| Field | Required | Contract |
|---|---|---|
| `outcomes` | yes | Non-empty unique subset of `success`, `pending`, `partial_failure`, `failure`; normalization publishes canonical order. |
| `data_schema` | yes | One recursive JSON Schema **object** describing only the runtime envelope's `data` value. Every named `properties` child must have a non-empty `description`. It must not duplicate `ok`, `outcome`, `error`, or `meta`. |
| `sensitive_paths` | no | Unique safe dot paths relative to `data`; renderers/redaction consumers must not treat them as shell/JQ expressions. |
Optional members are omitted, never emitted as `null`. A leaf without a
reviewed Result omits the entire `result` key. Compact must preserve the same
normalized Result value as the full leaf; it must not summarize, infer, rename,
or independently rebuild any Result field. Product/group summaries do not
aggregate child Result objects.
`pagination` is a sibling of `result`, not a child. It declares the canonical
CLI cursor parameter and the fixed framework paths under `meta.pagination`.
Product response fields used to derive that metadata remain mapper internals;
they are not part of `result.data_schema`. Do not execute a second request to
derive pagination metadata.
Invalid result declarations fail closed during normalization: unknown or
duplicate outcomes, a non-object/multiple `data_schema`, unsafe or duplicate
sensitive paths, unsupported pagination kinds, attempts to override framework
meta paths, and an invalid cursor parameter must be rejected rather than
silently removed.
Full-leaf wire round trips must
preserve the normalized Result exactly. Do not commit generated Schema JSON as
evidence; tests construct contracts in Go and runtime/CI assemble the Catalog
from declarations.
### Performance model and rules
- Catalog construction is declaration-driven and cached through the existing
lazy `sync.Once` delivery path. Do not reassemble or reopen annotations per
command invocation, per leaf lookup, or per renderer.
- Normalizing one Result declaration is linear in the size of that declaration.
Full `schema --all` is linear in tools + parameters + Result schema bytes and
is an audit/compatibility export, not the normal Agent discovery path.
Overview → compact product/group → compact leaf remains the normal route;
only the final leaf carries its Result declaration.
- Constructing a `CommandResult` defensively clones result data and validates
invariants; rendering is buffer-first and then writes once. Both CPU cost and
transient memory are O(payload size), with roughly one additional in-memory
rendered copy. This buys immutability and prevents partial JSON leakage, but
it is not free.
- Large list/search commands must use bounded pages and publish continuation
facts. The current emitter buffers one command result/page before publishing;
pagination is the memory bound. Continuous event streams are a separate,
command-specific protocol and are not described by `ResultSpec`.
- A `dual_validate` command must execute the business request exactly once,
validate a shadow unified result, and preserve legacy bytes. Never obtain
validation by issuing a second network or write request.
- Filters and alternate formats are render-time work over the same in-memory
result. They must not rerun the business operation or rebuild Schema.
- Performance changes must preserve the one-result, buffer-first, fail-closed,
and atomic `--output` guarantees. Do not trade correctness for a microbenchmark
improvement. For a material hot-path change, benchmark representative small
and page-sized payloads and report allocations/bytes as well as latency.
## Current Schema boundaries
- `schema list` remains a progressive overview. `schema --all` is the stable
full-export contract: every final `SchemaIndex` tool must contain its
complete leaf parameters, constraints, and safety semantics, including an empty
`parameters` object for commands without flags. Keep it suitable for the #602
compatibility baseline and fail rather than silently emitting a partial
export.
- `schema --all` is not normal command discovery. Use overview -> compact
product/group -> compact leaf for routine Agent work. `--compact` is the
reviewed positive-field allowlist for Agent context: new full/audit fields
must not appear there until explicitly reviewed. A compact full export is not
a complete compatibility baseline.
- `dws <path> --help` defines whether Cobra exposes a path and which flags the
executable accepts. A compact leaf defines Agent selection, CLI parameters,
constraints, safety/confirmation semantics, and any reviewed `result`
contract. Full leaf fields such as `property`, `interface_ref`, and
provenance are audit facts. A conflict is contract drift, not permission to
guess.
- Schema and Help describe commands; neither returns DingTalk business data.
After discovery, execute the real read/search/list command to obtain data.
+1002 -1
View File
File diff suppressed because one or more lines are too long
+58 -9
View File
@@ -27,7 +27,9 @@ notes that are intentionally kept out of the repository root.
## Local Checks
Run the verification commands that match the surface you changed before you hand work back.
Run the verification commands that match the surface you changed before you
hand work back. The goal is useful, change-specific evidence, not a second
local execution of every CI job.
Common repository checks already used here include:
@@ -36,27 +38,74 @@ Common repository checks already used here include:
./scripts/policy/check-open-source-assets.sh
go test ./...
make test
make test-plan
make lint
bash test/scripts/run_all_tests.sh --jobs 8
./scripts/policy/check-generated-drift.sh
./scripts/policy/check-command-surface.sh --strict
./scripts/release/verify-package-managers.sh
git diff --check
```
Select the PR risk tier before choosing checks:
| Tier | Typical scope | Developer evidence | CI expansion |
|---|---|---|---|
| Documentation-only | Prose and documentation assets with no executable, generated, workflow, packaging, or interface change | Links/content/rendering plus repository asset checks | Lightweight documentation validation; all nine named contexts still report |
| Standard | Ordinary implementation work with a stable package graph | Focused unit/integration tests and observable behavior for the changed path | Race tests for changed packages and their reverse dependencies, scope-matched HEAD/base coverage, and representative Darwin/Windows compilation |
| High-risk | Workflow/policy, package graph, generated Schema/registry, platform, auth/keychain, installer, packaging, release, transport, recovery, or an unprovable infrastructure change | Relevant full or domain suite plus focused behavior evidence | Complete race suite, native platform tests, and all affected domain gates; protected `main` uses this tier |
Classification fails closed: an incomplete diff, package add/remove/rename, or
uncertain dependency graph selects the high-risk suite. Native changed-code
coverage is additionally selected for platform-sensitive code.
## Pull Request Checklist
1. Keep implementation and tests in sync.
2. Run `./scripts/dev/ci-local.sh`.
3. Run `./scripts/policy/check-command-surface.sh --strict` when command paths/flags change.
4. Run `./scripts/policy/check-generated-drift.sh` when generated artifacts may change.
5. Run `./scripts/release/verify-package-managers.sh` when packaging or installer surfaces change (run `make package` first).
6. Update docs and `CHANGELOG.md` for behavior/interface changes.
7. Include verification evidence in your PR description.
2. Select the documentation-only, standard, or high-risk tier and run the
smallest checks that prove the change. Use `./scripts/dev/ci-local.sh` when
a complete local pass is warranted; it is not required for every ordinary
PR.
3. Include both the commands/results and user-visible or contract-level
behavior evidence in the PR description.
4. Run `./scripts/policy/check-command-surface.sh --strict` when command
paths/flags change. CI resolves the exact merge-base, latest reachable
non-withdrawn stable GA tag, and committed candidate SHA, then enters the single compatibility
decision seam through
`make authoritative-interface-integrity BASE_REF=<merge-base> STABLE_REF=<latest-GA-tag> CANDIDATE_REF=<candidate-sha>`.
The Make target delegates to the authoritative wrapper; CI does not invoke a
second comparator or the legacy fixture checker. See
[CLI Help / Schema compatibility migration governance](docs/cli-interface-flag-migrations.md)
for the reviewed two-stage `pending` → `consumed` lifecycle.
Agent-visible flag or command-path migrations must also run
`make schema-compatibility BASE_REF=<merge-base> STABLE_REF=<latest-GA-tag> CANDIDATE_REF=<candidate-sha>`;
it consumes the same base-owned ledger rather than a second exception list.
5. Run `./scripts/policy/check-generated-drift.sh` when generated artifacts may
change.
6. Run `./scripts/release/verify-package-managers.sh` when packaging or
installer surfaces change (run `make package` first).
7. Update docs and add one `.changes/<unique-name>.md` release fragment for
behavior/interface changes. Do not edit `CHANGELOG.md` in an ordinary PR;
the release-seal workflow renders and archives fragments into the versioned
changelog section.
## Submission Flow
1. Make the smallest atomic change that satisfies the task.
2. Keep doc edits factual and limited to implemented behavior.
3. Run the relevant verification commands.
4. Report the validation results with the handoff.
4. Report the validation results and risk tier with the handoff.
5. Open a ready PR against `main`. Base-owned automation assigns one eligible
peer reviewer, balancing the current open-review load and excluding the
author. A new head push re-enters the same routing flow when the latest
revision still needs review.
6. After the latest push has one peer approval and the exact nine required
contexts are current and green, auto-merge completes the PR. If `main`
advances first, strict status checks revalidate the branch; no separate
routine merge request is needed.
Contributors without repository write access stop at the PR flow. Explicitly
authorized collaborators with `write`, `maintain`, or `admin` access can use
[Actions → Release](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/actions/workflows/release.yml)
to publish beta releases without manual approval. The same internal roles may
start a stable release, but a different repository administrator must approve
the `release-stable` Environment deployment before publication continues.
+63
View File
@@ -0,0 +1,63 @@
class DingtalkWorkspaceCliBeta < Formula
desc "Automate DingTalk workspace tasks from the terminal (beta channel)"
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
version "1.0.59-beta.3"
license "Apache-2.0"
keg_only "it is the beta channel and conflicts with dingtalk-workspace-cli"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59-beta.3/dws-darwin-arm64.tar.gz"
sha256 "9c99adcefd9104368eb443f0a1b4af8e7aceaa1ffdd4462e486854c1692bb6ce"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59-beta.3/dws-darwin-amd64.tar.gz"
sha256 "f5cc8efb1f982d68ae549190fd683292359c2ab542b532fa52bb35e6b5c049af"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59-beta.3/dws-linux-arm64.tar.gz"
sha256 "7a4efd04b417ce8013b1e431274b396179958da244164f59974358ba327ff093"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59-beta.3/dws-linux-amd64.tar.gz"
sha256 "90181e8f2e9010c1943a5773c3d45d7d3ac85d6bc93e18a9aaa7c69909e553d7"
end
end
resource "skills" do
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59-beta.3/dws-skills.zip"
sha256 "e7028914a4a826af9b18ed4922d68fa8f279473817fed4f305465bc8a7aad363"
end
def install
root = Dir["dws-*"].find { |entry| File.directory?(entry) } || "."
binary = File.join(root, "dws")
raise "binary not found: #{binary}" unless File.exist?(binary)
bin.install binary => "dws"
%w[LICENSE NOTICE README.md CHANGELOG.md].each do |name|
source = File.join(root, name)
pkgshare.install source if File.exist?(source)
end
skill_dest = pkgshare/"skills/dws"
skill_dest.mkpath
resource("skills").stage do
cp_r(Dir["*"], skill_dest)
end
end
def caveats
<<~EOS
Agent Skills are bundled in #{pkgshare}/skills/dws.
Run `dws skill setup` to install them into your Agent directories.
This beta is keg-only. Add #{opt_bin} to PATH to use its `dws` binary.
EOS
end
test do
assert_match version.to_s, shell_output("#{bin}/dws version")
end
end
+63
View File
@@ -0,0 +1,63 @@
class DingtalkWorkspaceCli < Formula
desc "Automate DingTalk workspace tasks from the terminal"
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
version "1.0.58"
license "Apache-2.0"
on_macos do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58/dws-darwin-arm64.tar.gz"
sha256 "7d98599f90cae9d42b51ff2863efc87dbfb4a3176ff3c84fc2216110c0157a70"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58/dws-darwin-amd64.tar.gz"
sha256 "4c12e35e5bf7e0905812cd42dc94a5345068a2c16e306bb50b13c5c78b5cb95d"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58/dws-linux-arm64.tar.gz"
sha256 "5ef6bde24bc3db6a11a0f1d0b3343a048956b2cbcf6cd3409a037fb6ba425489"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58/dws-linux-amd64.tar.gz"
sha256 "3ccadcc6f070a39d2b2ba20429a4fcdc2f21639bf79f34361dc7d16f501bfda6"
end
end
resource "skills" do
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.58/dws-skills.zip"
sha256 "2626debc21c3daadfd155b4c167b2219b97e801398fe4441a8b48138960ab264"
end
def install
root = Dir["dws-*"].find { |entry| File.directory?(entry) } || "."
binary = File.join(root, "dws")
raise "binary not found: #{binary}" unless File.exist?(binary)
bin.install binary => "dws"
%w[LICENSE NOTICE README.md CHANGELOG.md].each do |name|
source = File.join(root, name)
pkgshare.install source if File.exist?(source)
end
skill_dest = pkgshare/"skills/dws"
skill_dest.mkpath
resource("skills").stage do
cp_r(Dir["*"], skill_dest)
end
end
def caveats
<<~EOS
Agent Skills are bundled in #{pkgshare}/skills/dws.
Run `dws skill setup` to install them into your Agent directories.
EOS
end
test do
assert_match version.to_s, shell_output("#{bin}/dws version")
end
end
+227 -16
View File
@@ -1,6 +1,16 @@
GO ?= go
DWS_PACKAGE_VERSION ?= 0.0.0-test
REMOTE ?=
PUBLISH ?= 0
YES ?= 0
DWS_POLICY_TMPDIR ?= $(CURDIR)/.worktrees/policy-tmp
POLICY_GOTMPDIR ?= $(DWS_POLICY_TMPDIR)/go
SCHEMA_CATALOG_OUTPUT ?= artifacts/schema_catalog
SCHEMA_META_INDEX_OUTPUT ?= artifacts/schema_meta_index.gob
POLICY_ENV = DWS_POLICY_TMPDIR="$(DWS_POLICY_TMPDIR)" GOTMPDIR="$(POLICY_GOTMPDIR)"
GO_SOURCE_LIST = git ls-files -z --cached --others --exclude-standard -- '*.go'
.PHONY: all help build rebuild test lint fmt policy edition-test package release publish-homebrew-formula setup-hooks
.PHONY: all help build rebuild test test-plan test-auth-legacy-compat lint format-check fmt policy edition-test interface-integrity authoritative-interface-integrity coverage-gate coverage-gate-platform update-interface-baseline reset-interface-baseline schema-compatibility skill-command-integrity skill-context-budget multi-im-skill-chain-integrity cli-smoke mock-mcp-smoke test-schema-agent-examples generate-schema fetch-mcp-metadata generate-schema-catalog package release release-pre release-stable changelog-pre changelog-stable publish-homebrew-formula setup-hooks
all: setup-hooks fmt lint build test rebuild
@@ -8,11 +18,32 @@ help:
@printf "Available targets:\n"
@printf " make build - Build the dws CLI binary\n"
@printf " make test - Run the Go test suite\n"
@printf " make lint - Run formatting checks and golangci-lint when available\n"
@printf " make fmt - Format Go source files\n"
@printf " make policy - Run open-source asset and command-surface checks\n"
@printf " make package - Build all release artifacts locally (goreleaser snapshot)\n"
@printf " make release - Build and publish a release via goreleaser\n"
@printf " make test-plan - Verify CI test and full-suite coverage package plans cover their scopes exactly once\n"
@printf " make test-auth-legacy-compat - Run stable legacy authentication compatibility regressions\n"
@printf " make lint - Run formatting checks, go vet, and staticcheck\n"
@printf " make format-check - Check all repository Go source files with gofmt\n"
@printf " make fmt - Format all repository Go source files\n"
@printf " make policy - Check the built dws plus open-source and Schema policies\n"
@printf " make interface-integrity [BASE_REF=<ref>] [STABLE_REF=<tag>] [CANDIDATE_REF=<ref>] - Check authoritative CLI history\n"
@printf " make authoritative-interface-integrity BASE_REF=<ref> [STABLE_REF=<tag>] [CANDIDATE_REF=<ref>] - Check Git-owned CLI history\n"
@printf " make coverage-gate BASE_REF=<ref> - Enforce overall non-regression and 100%% changed-code coverage\n"
@printf " make coverage-gate-platform BASE_REF=<ref> PROFILE=<file> - Enforce 100%% native changed-code coverage\n"
@printf " make update-interface-baseline - Update the non-authoritative CLI smoke fixture\n"
@printf " make reset-interface-baseline - DANGEROUS: replace the non-authoritative CLI smoke fixture\n"
@printf " make schema-compatibility BASE_REF=<ref> [STABLE_REF=<tag>] [CANDIDATE_REF=<ref>] - Check the authoritative Schema history\n"
@printf " make skill-command-integrity - Check dws commands referenced by skills exist\n"
@printf " make skill-context-budget - Check generated Skill drift and common-path context budgets\n"
@printf " make multi-im-skill-chain-integrity - Check reviewed IM intents keep one default Skill route\n"
@printf " make cli-smoke - Verify help for every public top-level command\n"
@printf " make mock-mcp-smoke - Verify HTTP and stdio MCP request/response transport\n"
@printf " make test-schema-agent-examples - Contract-check all Agent examples and dry-run the eligible subset\n"
@printf " make generate-schema - Refresh param_aliases + verify Schema assembly determinism\n"
@printf " make generate-schema-catalog - Optional assembled Catalog dump under artifacts/ (not a delivery step)\n"
@printf " make package - Build all release artifacts locally\n"
@printf " make changelog-pre VERSION=vX.Y.Z-beta.N - Prepare prerelease notes\n"
@printf " make changelog-stable VERSION=vX.Y.Z FROM_BETA=vX.Y.Z-beta.N - Prepare stable notes\n"
@printf " make release-pre VERSION=vX.Y.Z-beta.N - Validate prerelease; publish official releases from Actions\n"
@printf " make release-stable VERSION=vX.Y.Z FROM_BETA=vX.Y.Z-beta.N - Validate stable; publish official releases from Actions\n"
@printf " make publish-homebrew-formula - Push dist/homebrew/dingtalk-workspace-cli.rb to a tap repo\n"
build:
@@ -22,24 +53,178 @@ rebuild:
@./scripts/dev/build.sh
test:
@./test/scripts/run_all_tests.sh
@DWS_PACKAGE_VERSION="$(DWS_PACKAGE_VERSION)" $(GO) test -count=1 -timeout=10m ./...
test-plan:
@./scripts/ci/test-packages.sh verify
test-auth-legacy-compat:
@mkdir -p "$(POLICY_GOTMPDIR)"
@GO="$(GO)" $(POLICY_ENV) ./scripts/policy/check-auth-legacy-compat.sh
lint:
@./scripts/dev/lint.sh
fmt:
@find cmd internal test -name '*.go' -print0 2>/dev/null | xargs -0r gofmt -w
format-check:
@set -eu; \
go_files="$$(mktemp "$${TMPDIR:-/tmp}/dws-go-files.XXXXXX")"; \
trap 'rm -f "$$go_files"' EXIT HUP INT TERM; \
$(GO_SOURCE_LIST) > "$$go_files"; \
unformatted="$$(xargs -0 sh -c 'if [ "$$#" -gt 0 ]; then exec gofmt -l -- "$$@"; fi' sh < "$$go_files")"; \
if [ -n "$$unformatted" ]; then \
printf '%s\n' "$$unformatted"; \
printf '%s\n' "Go files are not formatted. Run 'make fmt'." >&2; \
exit 1; \
fi
policy:
@./scripts/policy/check-open-source-assets.sh
@./scripts/policy/check-command-surface.sh --strict
fmt:
@set -eu; \
go_files="$$(mktemp "$${TMPDIR:-/tmp}/dws-go-files.XXXXXX")"; \
trap 'rm -f "$$go_files"' EXIT HUP INT TERM; \
$(GO_SOURCE_LIST) > "$$go_files"; \
xargs -0 sh -c 'if [ "$$#" -gt 0 ]; then exec gofmt -w -- "$$@"; fi' sh < "$$go_files"
policy: test-auth-legacy-compat
@mkdir -p "$(POLICY_GOTMPDIR)"
@$(POLICY_ENV) ./scripts/policy/check-open-source-assets.sh
@$(POLICY_ENV) ./scripts/policy/check-skill-context-budget.sh
@$(POLICY_ENV) ./scripts/policy/check-multi-im-skill-chain.sh
@python3 scripts/run_chat_shortcut_live_audit_test.py
@$(POLICY_ENV) ./scripts/policy/check-command-surface.sh --strict
@$(POLICY_ENV) ./scripts/policy/check-generated-drift.sh
@$(POLICY_ENV) ./scripts/policy/check-param-concepts.sh
@$(POLICY_ENV) ./scripts/policy/check-param-alias-cooccurrence.sh
@$(POLICY_ENV) $(GO) test -count=1 ./internal/app -run '^(TestParamAlias(FixtureThroughEmbeddedDeliveryPath|ReadCommandFinalPayload|WriteCommandFinalPayload|CanonicalConflictFailsBeforeRunE|BlockedFlagReachesReviewedFinalError)|TestFlagConflictErrorFormattingIsDeterministic)$$'
@$(POLICY_ENV) ./scripts/policy/check-schema-catalog.sh
@$(POLICY_ENV) ./scripts/policy/check-schema-binary.sh
@$(POLICY_ENV) $(MAKE) test-schema-agent-examples
edition-test:
$(GO) test -v -count=1 ./pkg/editiontest/...
interface-integrity:
@base_ref="$(BASE_REF)"; \
candidate_ref="$(CANDIDATE_REF)"; \
if [ -z "$$base_ref" ]; then base_ref="origin/main"; fi; \
if [ -z "$$candidate_ref" ]; then candidate_ref="HEAD"; fi; \
./scripts/policy/check-authoritative-interface-baselines.sh \
--base-ref "$$base_ref" \
--stable-ref "$(STABLE_REF)" \
--candidate-ref "$$candidate_ref"
authoritative-interface-integrity:
@candidate_ref="$(CANDIDATE_REF)"; \
if [ -z "$$candidate_ref" ]; then candidate_ref="HEAD"; fi; \
./scripts/policy/check-authoritative-interface-baselines.sh \
--base-ref "$(BASE_REF)" \
--stable-ref "$(STABLE_REF)" \
--candidate-ref "$$candidate_ref"
coverage-gate:
@./scripts/policy/check-coverage-gate.sh --base-ref "$(BASE_REF)" --scope-buildable
coverage-gate-platform:
@./scripts/policy/run-platform-coverage-gate.sh --base-ref "$(BASE_REF)" --profile "$(PROFILE)"
update-interface-baseline:
@./scripts/policy/check-interface-baseline.sh --update
reset-interface-baseline:
@./scripts/policy/check-interface-baseline.sh --reset
schema-compatibility:
@candidate_ref="$(CANDIDATE_REF)"; \
if [ -z "$$candidate_ref" ]; then candidate_ref="HEAD"; fi; \
./scripts/policy/check-authoritative-schema-compatibility.sh \
--base-ref "$(BASE_REF)" \
--stable-ref "$(STABLE_REF)" \
--candidate-ref "$$candidate_ref"
skill-command-integrity:
@./scripts/policy/check-skill-commands.sh
skill-context-budget:
@./scripts/policy/check-skill-context-budget.sh
multi-im-skill-chain-integrity:
@./scripts/policy/check-multi-im-skill-chain.sh
skill-mono-multi-content:
@./scripts/policy/check-mono-multi-skill-content.sh
cli-smoke:
@./scripts/policy/check-cli-smoke.sh
mock-mcp-smoke:
$(GO) test -v -count=1 -run '^(TestHTTPClientEndToEnd|TestStdioClientEndToEnd)$$' ./internal/transport
test-schema-agent-examples:
DWS_AGENT_EXAMPLES_DRY_RUN=1 $(GO) test -v -count=1 ./internal/app -run '^TestAgentExamplesDryRun$$'
# generate-schema refreshes param_aliases_generated.go and verifies that
# ResolveSchemaBuild assembly is deterministic. Catalog is runtime-assembled
# (声明即 Catalog); cmd_schema_catalog is not a committed delivery step.
# schema_agent_metadata/ and schema_hints/ must stay absent.
generate-schema:
@set -e; \
concepts_guard=$$(mktemp); \
concepts_schema_guard=$$(mktemp); \
command_fallbacks_guard=$$(mktemp); \
command_fallbacks_schema_guard=$$(mktemp); \
trap 'rm -f "$$concepts_guard" "$$concepts_schema_guard" "$$command_fallbacks_guard" "$$command_fallbacks_schema_guard"' EXIT HUP INT TERM; \
cp internal/cli/param_concepts.json "$$concepts_guard"; \
cp internal/cli/param_concepts.schema.json "$$concepts_schema_guard"; \
cp internal/cli/command_path_fallbacks.json "$$command_fallbacks_guard"; \
cp internal/cli/command_path_fallbacks.schema.json "$$command_fallbacks_schema_guard"; \
$(GO) generate ./internal/cli; \
rm -rf internal/cli/schema_agent_metadata internal/cli/schema_agent_metadata_audit.json; \
rm -f internal/cli/schema_meta_index.json; \
if [ -e internal/cli/schema_command_registry ]; then \
printf '%s\n' 'retired schema_command_registry/ must not reappear after generation' >&2; \
exit 1; \
fi; \
cmp -s internal/cli/param_concepts.json "$$concepts_guard" || { \
printf '%s\n' 'generation modified reviewed input internal/cli/param_concepts.json' >&2; \
exit 1; \
}; \
cmp -s internal/cli/param_concepts.schema.json "$$concepts_schema_guard" || { \
printf '%s\n' 'generation modified reviewed input internal/cli/param_concepts.schema.json' >&2; \
exit 1; \
}; \
cmp -s internal/cli/command_path_fallbacks.json "$$command_fallbacks_guard" || { \
printf '%s\n' 'generation modified reviewed input internal/cli/command_path_fallbacks.json' >&2; \
exit 1; \
}; \
cmp -s internal/cli/command_path_fallbacks.schema.json "$$command_fallbacks_schema_guard" || { \
printf '%s\n' 'generation modified reviewed input internal/cli/command_path_fallbacks.schema.json' >&2; \
exit 1; \
}; \
if [ -e internal/cli/schema_hints ]; then \
printf '%s\n' 'retired schema_hints/ must not reappear after generation' >&2; \
exit 1; \
fi; \
if [ -e internal/cli/schema_meta_index.json ]; then \
printf '%s\n' 'retired schema_meta_index.json must not remain after generation' >&2; \
exit 1; \
fi; \
./scripts/policy/check-schema-assembly.sh
# Optional local/CI dump of an assembled Catalog under artifacts/ by default.
# Override SCHEMA_CATALOG_OUTPUT and SCHEMA_META_INDEX_OUTPUT as needed. This
# is not a go:generate or production delivery step.
generate-schema-catalog:
$(GO) run -a ./internal/generator/cmd_schema_catalog \
-root . \
-output "$(SCHEMA_CATALOG_OUTPUT)" \
-meta-index "$(SCHEMA_META_INDEX_OUTPUT)"
fetch-mcp-metadata:
@printf ' %sFetching diagnostic MCP dump (not a Schema pin)%s\n' "$(COLOR_RUN)" "$(COLOR_RESET)"
@./scripts/dev/fetch_mcp_metadata.sh
package:
@./scripts/dev/build-all.sh
@./scripts/release/post-goreleaser.sh
@version="$(if $(VERSION),$(VERSION),v0.0.0-SNAPSHOT)"; VERSION="$${version#v}" ./scripts/dev/build-all.sh
@version="$(if $(VERSION),$(VERSION),v0.0.0-SNAPSHOT)"; DWS_PACKAGE_VERSION="$$version" ./scripts/release/post-goreleaser.sh
publish-homebrew-formula:
@./scripts/release/publish-homebrew-formula.sh
@@ -47,6 +232,32 @@ publish-homebrew-formula:
setup-hooks:
@git config core.hooksPath scripts/hooks 2>/dev/null || true
changelog-pre:
@test -n "$(VERSION)" || (printf 'VERSION is required, e.g. v1.2.3-beta.1\n' >&2; exit 2)
@./scripts/release/prepare-changelog.sh prerelease "$(VERSION)"
changelog-stable:
@test -n "$(VERSION)" || (printf 'VERSION is required, e.g. v1.2.3\n' >&2; exit 2)
@test -n "$(FROM_BETA)" || (printf 'FROM_BETA is required, e.g. v1.2.3-beta.2\n' >&2; exit 2)
@./scripts/release/prepare-changelog.sh stable "$(VERSION)" --from-beta "$(FROM_BETA)"
release-pre:
@test -n "$(VERSION)" || (printf 'VERSION is required, e.g. v1.2.3-beta.1\n' >&2; exit 2)
@test -n "$(REMOTE)" || (printf 'REMOTE is required, e.g. origin\n' >&2; exit 2)
@args=""; \
if [ "$(PUBLISH)" = "1" ]; then args="$$args --publish"; fi; \
if [ "$(YES)" = "1" ]; then args="$$args --yes"; fi; \
./scripts/release/release.sh prerelease "$(VERSION)" --remote "$(REMOTE)" $$args
release-stable:
@test -n "$(VERSION)" || (printf 'VERSION is required, e.g. v1.2.3\n' >&2; exit 2)
@test -n "$(FROM_BETA)" || (printf 'FROM_BETA is required, e.g. v1.2.3-beta.2\n' >&2; exit 2)
@test -n "$(REMOTE)" || (printf 'REMOTE is required, e.g. origin\n' >&2; exit 2)
@args=""; \
if [ "$(PUBLISH)" = "1" ]; then args="$$args --publish"; fi; \
if [ "$(YES)" = "1" ]; then args="$$args --yes"; fi; \
./scripts/release/release.sh stable "$(VERSION)" --from-beta "$(FROM_BETA)" --remote "$(REMOTE)" $$args
release:
goreleaser release --clean
@./scripts/release/post-goreleaser.sh
@printf 'Use make release-pre or make release-stable; direct goreleaser publishing is disabled.\n' >&2
@exit 2
+197 -39
View File
@@ -70,17 +70,17 @@ The installer ships skills in one of two layouts. CLI commands (`dws aitable ...
| Mode | What gets installed | Best for |
|------|----------------------|----------|
| **mono** (stable, default) | One `dws` skill covering all products | Cross-product workflows; single entry point |
| **multi** 🧪 **EXPERIMENTAL** | 22 per-product skills (`dingtalk-aitable`, `dingtalk-calendar`, `dingtalk-chat`, ...) | Single-product tasks; smaller context per call |
| **multi** (default) | Per-product skills (`dingtalk-aitable`, `dingtalk-calendar`, `dingtalk-chat`, ...) | Single-product tasks; smaller context per call |
| **mono** (legacy) | One `dws` skill covering all products | Cross-product workflows; single entry point |
> 🧪 **`multi` is currently EXPERIMENTAL / preview.** 22 product-scoped skills all pass the dispatch verifier, but interface, naming and cross-skill references may change in future releases. For production / shared environments, prefer `mono`. File issues if you hit problems.
> Installs and upgrades default to `multi`. `mono` remains available via `DWS_SKILL_MODE=mono` or `dws skill setup --mode mono`. File issues if you hit problems.
How to pick:
- **Quick install** (one-liner above): non-interactive, installs `mono`.
- **TTY install** (download then run): `curl -O .../install.sh && bash install.sh` — prompts `1) mono 2) multi` (default 1).
- **Override via env**: `DWS_SKILL_MODE=multi curl -fsSL ... | sh`.
- **Switch later**: `dws skill setup --mode multi` (or `--mode mono`) — re-run any time.
- **Quick install** (one-liner above): non-interactive, installs `multi`.
- **TTY install** (download then run): `curl -O .../install.sh && bash install.sh` — prompts `1) multi 2) mono` (default 1).
- **Override via env**: `DWS_SKILL_MODE=mono curl -fsSL ... | sh`.
- **Switch later**: `dws skill setup --mode mono` (or `--mode multi`) — review the listed paths and confirm interactively.
</details>
@@ -93,6 +93,30 @@ How to pick:
npm install -g dingtalk-workspace-cli
```
Install the latest beta:
```bash
npm install -g dingtalk-workspace-cli@beta
```
**Homebrew** (macOS / Linux):
```bash
brew tap DingTalk-Real-AI/dingtalk-workspace-cli https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git
brew install dingtalk-workspace-cli
```
> The Formula lives in this repository, so the first `tap` command must include the explicit repository URL. Afterwards, use `brew upgrade dingtalk-workspace-cli` normally.
Install the keg-only Homebrew beta without replacing the stable Formula:
```bash
brew install dingtalk-workspace-cli-beta
$(brew --prefix dingtalk-workspace-cli-beta)/bin/dws version
```
To make the beta `dws` the default for the current shell, prepend `$(brew --prefix dingtalk-workspace-cli-beta)/bin` to PATH.
**Pre-built binary**: download from [GitHub Releases](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases).
> **macOS users**: If you see "cannot be opened because Apple cannot check it for malicious software", run:
@@ -168,13 +192,25 @@ dws upgrade -y # skip confirmation prompt
By default, `dws upgrade` follows the stable release track. Use `--beta` only when you explicitly want the newest GitHub pre-release build.
### Six-channel post-release verification
Maintainers and release validators can run the release-quality smoke checks for curl, PowerShell, npm stable, npm beta, Homebrew, and `dws upgrade`:
```bash
git clone https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git /tmp/dws-verify
cd /tmp/dws-verify/verify
bash verify-all-channels.sh
```
The verifier uses isolated directories and does not replace the `dws` on the current PATH. It reports `PASS`, `FAIL`, and `SKIP`; a platform skip is not a pass and must be covered on the matching host. See [`verify/README.md`](verify/README.md) for the platform matrix.
<details>
<summary><strong>How it works</strong></summary>
The upgrade process follows a two-phase atomic flow to ensure consistency:
1. **Prepare** — downloads the platform-specific binary and skill packages to a temporary directory, verifies SHA256 checksums, and extracts/validates all files. If any step fails, the upgrade aborts without modifying the existing installation.
2. **Apply** — only after all preparations succeed, the binary is replaced and skill packages are installed to all detected agent directories (`~/.agents/skills/dws`, `~/.claude/skills/dws`, `~/.cursor/skills/dws`, etc.).
2. **Apply** — only after all preparations succeed, the binary is replaced and skills are flattened into the canonical `~/.agents/skills` root. Agents classified by the pinned compatibility registry as supporting the universal root read it directly; other detected Agents receive links to the canonical copy, with a direct-copy fallback when links are unavailable. Older DWS-managed agent-specific copies are backed up and retired so the same Skill is not discovered twice.
A backup of the current version is automatically created before each upgrade. Use `dws upgrade --rollback` to restore the previous version if needed.
@@ -247,16 +283,32 @@ Credentials are securely persisted after first login (Keychain). Subsequent runs
<details>
<summary><strong>Multiple organizations (profiles)</strong></summary>
`dws` can stay logged in to several DingTalk organizations at once. Each organization is one **profile**; the current profile decides which org a command runs against (credentials are stored per organization).
`dws` can stay logged in to several DingTalk accounts at once, including multiple accounts in the same organization. A profile is uniquely identified by `corpId:userId`; the current profile decides which identity a command runs as.
```bash
dws auth login # log in to another org → adds a profile (first login becomes the primary)
dws profile list # list logged-in orgs (primary / current marker, status)
dws profile switch <name|corpId> # switch the default org (use - to toggle back to the previous one)
dws --profile <name|corpId> contact user search --query "..." # run one command against a specific org, without changing the default
dws auth login # add or refresh one account
dws profile list # list every logged-in account
dws profile switch <corpId:userId> # persistently switch; use - to toggle back
dws profile switch "<corpName>:<userName>" # friendly input; names must be unique
dws --profile <corpId> contact user search --query "..." # use that org's explicitly recorded current account
dws --profile <corpId:userId> contact user search --query "..." # use one exact account without changing the default
```
Cross-org reads are orchestrated by the agent rather than a built-in `--all-orgs`: list the profiles, run the query per org with `--profile`, then merge. Writes default to the current org only — confirm the target org before writing across orgs.
Selectors support `corpId:userId`, `corpId:userName`, `corpName:userId`, and `corpName:userName`. Friendly names are input aliases only; use the stable `profile` value returned by `profile list` for automation. Duplicate organization or account names fail with explicit `corpId:userId` candidates. If an organization has multiple accounts but no recorded current account, `--profile <corpId>` fails instead of choosing the first or most recently used account.
`currentProfile`, `previousProfile`, and per-organization defaults are stored as exact identities. `primaryProfile` remains in JSON only for compatibility and is not used for selection. `profile list` reads status and expiry from each real identity Token without refreshing it. `auth logout --profile <corpId>` removes all local accounts in that organization; an exact selector or local profile name removes one account.
Cross-org reads are orchestrated by the agent rather than a built-in `--all-orgs`: list profiles, group by `corpId`, and use the unique `isOrgCurrent=true` account for each organization. If a multi-account organization has no default, ask the user to choose an account first. Writes default to the current account — confirm both organization and account before cross-org writes.
On macOS, an unreadable registered token slot blocks a new OAuth login rather than risking a mixed Keychain/file-DEK state. If normal terminal commands can still read the login while a sandbox using `DWS_DISABLE_KEYCHAIN=1` cannot, migrate the legacy and profile auth entries without exposing tokens:
```bash
env -u DWS_DISABLE_KEYCHAIN dws auth migrate-keychain --to file-dek --dry-run --format json
env -u DWS_DISABLE_KEYCHAIN dws auth migrate-keychain --to file-dek --yes --format json
DWS_DISABLE_KEYCHAIN=1 dws auth status --format json
```
The migration validates every selected auth ciphertext before writing, ignores unrelated application secrets, and can be rerun after an interrupted commit. If validation identifies genuinely damaged ciphertext, remove only the affected account with `dws auth logout --profile <corpId:userId>`, or all accounts in one organization with `--profile <corpId>`, then log in again. Use `dws auth reset` only when you intend to discard every local profile.
</details>
@@ -277,6 +329,9 @@ dws auth status # confirm "Refresh Token: valid"
```
The bundle includes the encrypted keychain under `~/.local/share/dws-cli` (with `auth-token.enc` and `dek`) plus required `~/.dws` config files.
Windows export and import are intentionally rejected before credentials or
bundles are read: Windows stores credentials as DPAPI-protected HKCU Registry
values, and the current file-DEK bundle has no safe DPAPI-to-portable conversion.
</details>
@@ -313,34 +368,44 @@ dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserNam
### Command Help and Schema
Product commands are compiled into the binary in static endpoint mode. Use `--help` and the bundled Agent Skills as the source of truth; `dws schema` is retained for helper-only schemas such as `dev.*`.
Use Cobra help and Schema for different parts of the command contract:
- `dws <path> --help` is the source of truth for whether a command exists and which flags the binary accepts.
- `dws schema "<path>" --compact` is the normative Agent view for command selection, CLI parameters and constraints, risk, and confirmation; use a full leaf with a narrow `--jq` projection for mapping or provenance audits.
- If Help and Schema disagree, treat it as contract drift: pass only flags accepted by Cobra and use the more conservative safety semantics.
- Schema describes commands; it does not read or search DingTalk business data. Execute the real product command after discovery.
```bash
# Inspect the current compiled command surface
# Confirm that the command exists and inspect accepted flags
dws aitable record query --help
# Helper-only schema introspection
dws schema "dev app create"
# Discover within a product, then inspect the selected leaf contract
dws schema aitable --compact
dws schema "aitable record query" --compact
# Construct the call
# Execute the real business query
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --limit 10
```
`dws schema --all` exports the complete contract for tooling, CI, audits, and compatibility baselines. Agents should query progressively with `--compact`; its positive field allowlist prevents new full/audit fields from silently expanding Agent context.
### Agent Skills
The repo ships a complete Agent Skill system under `skills/`, now organized into two layouts:
The repo ships a complete Agent Skill system under `skills/`, organized into two layouts:
- `skills/mono/` — single-skill layout (one `SKILL.md` + `references/products/`), recommended default.
- `skills/multi/` — per-product skills (`dingtalk-aitable/`, `dingtalk-calendar/`, `dingtalk-chat/`, ... 22 products in total), each with its own `SKILL.md`. 🧪 **EXPERIMENTAL / preview — see banner in each multi `SKILL.md` for caveats.**
- `skills/mono/` — single-skill layout (one `SKILL.md` + `references/products/`), legacy.
- `skills/multi/` — per-product skills (`dingtalk-aitable/`, `dingtalk-calendar/`, `dingtalk-chat/`, ...), each with its own `SKILL.md`. Default layout.
Leaf safety/parameters/selection prose for Schema generation come from ProductDecl / ContractFinal declarations in Go. The former `internal/cli/schema_hints/` HintFile tree is fully retired and must not reappear.
After installing, AI tools like Claude Code / Cursor can operate DingTalk directly through natural language:
```bash
# Install skills into current project (defaults to mono)
# Install skills into current project (defaults to multi; DWS_SKILL_MODE=mono switches back)
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install-skills.sh | sh
```
> `install.sh` installs to `$HOME/.agents/skills/dws` (global); `install-skills.sh` installs to `./.agents/skills/dws` (current project).
> Installers use `$HOME/.agents/skills/` as the canonical global store, following the universal `.agents/skills` convention. Agents classified by the pinned compatibility registry as universal read that root directly; detected non-universal Agents receive links to it (or copies when links are unavailable). Multi layout is per-product siblings, while mono uses the `dws/` subdirectory.
>
> China users: prefix `DWS_GITEE_REPO` to use the Gitee mirror — see [China mirror](#china-mirror).
@@ -350,22 +415,31 @@ curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace
# Interactive: prompts for mode + target agents
dws skill setup
# Install mono skill to every detected agent home (claude / cursor / codex / opencode / qoder)
dws skill setup --mode mono --target all --yes
# Preview the exact directories that mono setup would back up and replace
dws skill setup --mode mono --target all --dry-run
# Install multi skills to a single agent home
dws skill setup --mode multi --target cursor --yes
# Run interactively and confirm the listed directories
dws skill setup --mode mono --target all
# Point at a local source tree (e.g. a fork or work-in-progress)
# Preview, then install multi skills to a single agent home with interactive confirmation
dws skill setup --mode multi --target cursor --dry-run
dws skill setup --mode multi --target cursor
# Point at a local source tree (e.g. a fork or work-in-progress), preview first
DWS_SKILL_SOURCE=/path/to/skills dws skill setup --mode multi --dry-run
DWS_SKILL_SOURCE=/path/to/skills dws skill setup --mode multi
```
| Flag | Values | Description |
|------|--------|-------------|
| `--mode` | `mono` \| `multi` | Skill layout; defaults to interactive prompt |
| `--target` | `all` \| `claude` \| `cursor` \| `codex` \| `opencode` \| `qoder` | Where to install; `all` covers every detected agent home |
| `--target` | `all` \| `claude` \| `cursor` \| `codex` \| `zcode` \| `opencode` \| `qoder` | Where to install; `all` covers every detected agent home, including ZCode at `~/.zcode/skills` |
| `--source` | path | Local source directory (overrides bundled skills) |
| `--yes` | — | Skip confirmation prompts |
| `--yes` | — | Scripting-only: skip the confirmation prompt. Removals are still backed up to `~/.dws/skill-backups/` first |
> The setup command can remove the opposite-mode layout (`dws/` for multi, DWS-managed multi Skills for mono) and stale managed Skills not in the bundle. DWS records ownership, installer version, source, and content digest centrally in `~/.dws/skills-state.json` (or `$DWS_CONFIG_DIR/skills-state.json`). Exact official names shipped before the centralized state remain a frozen migration list. A `dingtalk-*` prefix alone never authorizes cleanup, so other same-prefix market/user Skills are preserved. Every removal is previewed before confirmation and preserved under `~/.dws/skill-backups/<timestamp>/`; a directory that cannot be backed up is never removed. In a non-interactive shell, first run `--dry-run` and inspect its output; only then may the caller explicitly choose the scripting-only confirmation bypass.
After a multi setup or upgrade, DWS stores the official bundle snapshot and centralized ownership metadata in `~/.dws/skills-state.json` (or `$DWS_CONFIG_DIR/skills-state.json`). Every upgrade installs and overwrites the complete bundled Skill set from that release. Deleting or excluding a bundled Skill is not sticky: the next upgrade restores it. `dws upgrade --force` additionally allows reinstalling the current CLI version when no newer version is available.
Env vars: `DWS_SKILL_MODE=mono|multi` (also honored by `install.sh` / `install.ps1`), `DWS_SKILL_SOURCE=<path>`.
@@ -378,7 +452,6 @@ Env vars: `DWS_SKILL_MODE=mono|multi` (also honored by `install.sh` / `install.p
| Intent guide | `skills/mono/references/intent-guide.md` | Disambiguation for confusing scenarios (e.g. report vs todo) |
| Global reference | `skills/mono/references/global-reference.md` | Auth, output formats, global flags |
| Error codes | `skills/mono/references/error-codes.md` | Error codes + debugging workflows |
| Recovery guide | `skills/mono/references/recovery-guide.md` | `RECOVERY_EVENT_ID` handling |
| Ready-made scripts | `skills/mono/scripts/*.py` | 13 batch operation scripts (see below) |
<details>
@@ -406,6 +479,89 @@ Env vars: `DWS_SKILL_MODE=mono|multi` (also honored by `install.sh` / `install.p
## Features
<details>
<summary><strong>Personal Event Subscription</strong> — real-time DingTalk messages for event-driven agents</summary>
`dws event consume` subscribes as the currently logged-in user over a managed Stream WebSocket and emits each event as one NDJSON line on stdout. The public catalog covers scoped and all one-to-one/group messages, specified senders, read/recall/reaction events, group lifecycle events, and seven OA approval task/instance events.
The default `ndjson`, `json`, and `pretty` output preserves the transport envelope (`type`, `event_type`, string `data`, and `headers`) for existing scripts; `compact` retains its existing processor. Add `--flatten` to emit the stable top-level business fields used by Agent workflows. `--format` controls JSON serialization; `--flatten` controls the data structure and cannot be combined with `-f raw` or `--debug-raw-events`.
> **Prerequisite**: run `dws auth login`. Personal identity is resolved from the OAuth token and cannot be supplied through command-line identity flags.
For an event-focused installation, use the official convenience installer:
```bash
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install-event.sh | sh
# Or install the standalone multi skill from an existing dws installation
dws skill setup --mode multi -s event
```
```bash
# Inspect the public personal event catalog and schema
dws event list
dws event schema user_im_message_receive_o2o --flatten
dws event list --category oa
dws event schema user_oa_approval_task_created --flatten
# Listen for messages that mention the current user
dws event +listen-im --kind at-me -f ndjson
# Listen for messages from a specified sender
dws event +listen-im --kind sender --user <userId> -f ndjson
# Listen by openDingtalkId (external contact, bot, or cross-organization identity)
dws event +listen-im --kind sender --open-dingtalk-id <openDingtalkId> -f ndjson
# Listen for messages in a specified group
dws event +listen-im --kind group --chat-id <openConversationId> -f ndjson
# Listen for all one-to-one or all group messages
dws event +listen-im --kind all-direct -f ndjson
dws event +listen-im --kind all-group -f ndjson
# Listen for a specified group's title changes, member changes, or disband event
dws event consume user_im_group_updated --group <openConversationId> --flatten -f ndjson
dws event consume user_im_group_member_added --group <openConversationId> --flatten -f ndjson
dws event consume user_im_group_member_exited --group <openConversationId> --flatten -f ndjson
dws event consume user_im_group_disbanded --group <openConversationId> --flatten -f ndjson
# Listen for messages, reads, and recalls from the same sender in one process
dws event +listen-im --kind sender --user <userId> \
--events message,read,recall -f ndjson
# Listen for all seven public OA approval events in one process
dws event consume \
user_oa_approval_task_created \
user_oa_approval_task_finished \
user_oa_approval_task_redirected \
user_oa_approval_instance_started \
user_oa_approval_instance_cc \
user_oa_approval_instance_terminated \
user_oa_approval_instance_finished \
--flatten -f ndjson
# Inspect local consumers and cancel a subscription
dws event status
dws event stop <subscribe_id>
```
For one-to-one and specified-sender events, use exactly one target identity: `--user` for an internal `userId`, or `--open-dingtalk-id` for an `openDingtalkId`. The CLI does not infer or convert between these identity types.
| Feature | Details |
|---------|---------|
| Managed lifecycle | `consume` creates or reuses the personal subscription; `stop` cancels it and cleans local state |
| Shared connection | Consumers for the same user share one local bus and cloud connection |
| Multi-event process | One consume process can listen for compatible events for the same target while retaining one subscription per event |
| Subscription isolation | Normal consumers match both event type and `subscribe_id` |
| Agent-friendly output | Stream events are written to stdout as NDJSON; status and diagnostics use stderr |
| Observability | `status` shows remote subscriptions, the personal bus, and local consumers |
| Cross-platform | Unix Socket on macOS/Linux, Windows Named Pipe on Windows |
See `skills/multi/dingtalk-event/SKILL.md` for the Agent workflow and supported event parameters.
</details>
<details>
<summary><strong>Raw API Access</strong> — call any DingTalk OpenAPI directly</summary>
@@ -487,7 +643,7 @@ dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-i
```bash
# Built-in jq expressions
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --jq '.invocation.params'
dws schema "dev app create" --jq '.tool.required'
dws schema "dev app create" --jq '.parameters'
# Return only specific fields
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocation,response
@@ -496,12 +652,13 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocati
</details>
<details>
<summary><strong>Schema Introspection</strong> — helper-only schemas in static endpoint mode</summary>
<summary><strong>Schema Introspection</strong> — Agent command discovery and execution contracts</summary>
```bash
dws schema # static endpoint mode note
dws schema "dev app create" # view helper-only schema
dws schema "dev app create" --jq '.tool.required' # view required fields
dws schema aitable --compact # discover product commands
dws schema "aitable record query" --compact # view the selected Agent leaf contract
dws schema "aitable record query" --jq '[.parameters | to_entries[] | select(.value.required)]' # view required fields
dws schema --all # full export for CI/audit/baselines
```
</details>
@@ -553,7 +710,7 @@ See [`docs/robot-quickstart.md`](./docs/robot-quickstart.md) for the full 4-step
| Service | Command | Capabilities |
|---------|---------|--------------|
| Contact | `contact` | Look up users by name / mobile / job-number, departments, labels & roles, roster profiles & dismissals |
| Contact | `contact` | Look up users, departments, labels, roster profiles and dismissals; create enterprises and enterprise accounts; invite employees |
| Chat / IM | `chat` (`im`) | Send / reply / search messages, group & member management, bot & webhook messaging, reactions, recall |
| Calendar | `calendar` | Events CRUD, attendees, meeting rooms, free/busy & time suggestions |
| Todo | `todo` | Create / list / update / complete tasks and comments |
@@ -581,7 +738,7 @@ See [`docs/robot-quickstart.md`](./docs/robot-quickstart.md) for the full 4-step
<summary>Coming soon</summary>
- `conference` (video meetings)
- Multi-skill mode (experimental) — per-product skills under `skills/multi/`; opt in via `dws skill setup --mode multi`
- Multi-skill mode (default) — per-product skills under `skills/multi/`; installs and upgrades default to it, `dws skill setup --mode mono` switches back after interactive confirmation
</details>
@@ -630,6 +787,7 @@ See [`docs/robot-quickstart.md`](./docs/robot-quickstart.md) for the full 4-step
## Reference & Docs
- [International DingTalk (`.io`) guide](./docs/international-region-guide.md) — international login, domestic/international profile switching, isolated testing, and troubleshooting
- [Command Index](./docs/command-index.md) — every runtime command with description and when-to-use guidance
- [Reference](./docs/reference.md) — environment variables, exit codes, output formats, shell completion
- [Architecture](./docs/architecture.md) — static endpoint pipeline, command surface, transport layer
+194 -39
View File
@@ -70,17 +70,17 @@ irm https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/ma
| 模式 | 安装内容 | 适合场景 |
|------|----------|----------|
| **mono**(稳定,默认) | 一个 `dws` skill,覆盖全部产品 | 跨产品组合操作;单一入口召唤 |
| **multi** 🧪 **试验版 / Preview** | 22 个独立产品 skill(`dingtalk-aitable` / `dingtalk-calendar` / `dingtalk-chat` ...) | 单产品任务;每次召唤上下文更小 |
| **multi**(默认) | 按产品拆分的独立 skill(`dingtalk-aitable` / `dingtalk-calendar` / `dingtalk-chat` ...) | 单产品任务;每次召唤上下文更小 |
| **mono**(legacy) | 一个 `dws` skill,覆盖全部产品 | 跨产品组合操作;单一入口召唤 |
> 🧪 **multi 模式当前为 EXPERIMENTAL(试验版 / Preview)**。22 个独立 skill 全部通过 dispatch verifier,但接口、命名、跨 skill 引用后续可能调整。生产 / 共享环境建议优先用 `mono`。问题请提 issue 反馈。
> 安装与升级默认均为 multi。mono 仍可通过 `DWS_SKILL_MODE=mono` 或 `dws skill setup --mode mono` 使用。问题请提 issue 反馈。
怎么选:
- **快速安装**(上方一行 curl):非交互,默认装 `mono`。
- **TTY 安装**(先下载再执行):`curl -O .../install.sh && bash install.sh`,会弹出 `1) mono 2) multi` 选项(默认 1)。
- **环境变量覆盖**:`DWS_SKILL_MODE=multi curl -fsSL ... | sh`。
- **装完之后再切换**:`dws skill setup --mode multi`(或 `--mode mono`),随时重跑都行。
- **快速安装**(上方一行 curl):非交互,默认装 `multi`。
- **TTY 安装**(先下载再执行):`curl -O .../install.sh && bash install.sh`,会弹出 `1) multi 2) mono` 选项(默认 1)。
- **环境变量覆盖**:`DWS_SKILL_MODE=mono curl -fsSL ... | sh`。
- **装完之后再切换**:`dws skill setup --mode mono`(或 `--mode multi`),核对列出的路径后交互确认。
</details>
@@ -93,6 +93,30 @@ irm https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/ma
npm install -g dingtalk-workspace-cli
```
安装最新 beta:
```bash
npm install -g dingtalk-workspace-cli@beta
```
**Homebrew**(macOS / Linux):
```bash
brew tap DingTalk-Real-AI/dingtalk-workspace-cli https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git
brew install dingtalk-workspace-cli
```
> Formula 与代码位于同一个仓库,因此首次 `tap` 需要显式指定仓库 URL。后续可直接使用 `brew upgrade dingtalk-workspace-cli`。
安装 Homebrew beta(keg-only,不覆盖稳定版):
```bash
brew install dingtalk-workspace-cli-beta
$(brew --prefix dingtalk-workspace-cli-beta)/bin/dws version
```
如需让 beta 的 `dws` 成为当前 shell 默认版本,将 `$(brew --prefix dingtalk-workspace-cli-beta)/bin` 放到 PATH 最前面。
**预编译二进制文件**:从 [GitHub Releases](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases) 下载。
> **macOS 用户注意**:如果提示“无法打开,因为 Apple 无法检查其是否包含恶意软件”,请执行:
@@ -165,13 +189,25 @@ dws upgrade -y # 跳过确认直接升级
默认情况下,`dws upgrade` 只跟随正式 release 轨道。只有显式传入 `--beta` 时,才会选择 GitHub pre-release 里的 beta 构建。
### 六渠道发布后验证
维护者和验证同学可按发版质量保障 SOP,对 curl、PowerShell、npm stable、npm beta、Homebrew、`dws upgrade` 执行安装与冒烟验证:
```bash
git clone https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli.git /tmp/dws-verify
cd /tmp/dws-verify/verify
bash verify-all-channels.sh
```
脚本使用隔离目录,不会替换当前 PATH 中的 `dws`;输出 `PASS`、`FAIL`、`SKIP` 汇总。跨平台渠道必须由对应平台补测,`SKIP` 不计为通过。验证范围和平台矩阵见 [`verify/README.md`](verify/README.md)。
<details>
<summary><strong>工作原理</strong></summary>
升级过程采用两阶段原子流程,确保一致性:
1. **准备阶段** — 将平台对应的二进制文件和技能包下载到临时目录,校验 SHA256 校验和,解压并验证所有文件。任何步骤失败则立即中止,不会修改现有安装。
2. **执行阶段** — 仅在所有准备工作成功后,替换二进制文件并将技能包安装到所有已检测到的 Agent 目录(`~/.agents/skills/dws`、`~/.claude/skills/dws`、`~/.cursor/skills/dws` 等)。
2. **执行阶段** — 仅在所有准备工作成功后,替换二进制文件并将技能包平铺到已检测到的具体 Agent 目录(例如 `~/.codex/skills/dingtalk-chat`、`~/.claude/skills/dingtalk-chat`)。只有未检测到具体 Agent 时才使用 `~/.agents/skills`;检测到具体 Agent 后会备份迁走旧的 DWS 通用副本,避免同一 Skill 被重复发现。
每次升级前自动备份当前版本,可通过 `dws upgrade --rollback` 随时回滚。
@@ -244,16 +280,32 @@ dws auth login --client-id <your-app-key> --client-secret <your-app-secret>
<details>
<summary><strong>多组织(profile)</strong></summary>
`dws` 可以同时登录多个钉钉组织。一个组织就是一个 **profile**,当前 profile 决定本次命令操作哪个组织(凭证按组织分别存储)。
`dws` 可以同时登录多个钉钉账号,同一组织也能保留多个账号。一个 profile 由 `corpId + userId` 唯一确定。
```bash
dws auth login # 再登录一个组织 → 新增一个 profile(首次登录的为主组织)
dws profile list # 列出已登录组织(主 / 当前标记、状态)
dws profile switch <名称|corpId> # 切换默认组织(用 - 切回上一个)
dws --profile <名称|corpId> contact user search --query "..." # 单次对指定组织执行,不改默认组织
dws auth login # 新增或刷新一个账号
dws profile list # 列出全部账号,profile 字段是稳定的 corpId:userId
dws profile switch <corpId:userId> # 持久切换账号;用 - 切回上一个
dws profile switch "组织名:用户名" # 名称输入要求唯一
dws --profile <corpId> contact user search --query "..." # 使用该组织明确记录的当前账号
dws --profile <corpId:userId> contact user search --query "..." # 单次精确指定账号,不改默认账号
```
跨组织读取由 agent 编排,而非内置 `--all-orgs`:先 `dws profile list` 拿到组织,再对每个组织带 `--profile` 各查一遍,然后合并。写操作默认只在当前组织进行——跨组织写之前先确认目标组织。
支持 `corpId:userId`、`corpId:userName`、`corpName:userId`、`corpName:userName`。名称只用于输入,自动化应使用 `profile list` 返回的稳定 `profile`。组织名或用户名重名时会列出候选并报错;同组织多账号但没有明确当前账号时,只传组织也会报错,不会选择第一项或最近使用账号。
`currentProfile`、`previousProfile` 和组织默认账号都保存精确身份。`primaryProfile` 只为 JSON 兼容保留,不再参与选择。`profile list` 直接读取各身份 Token 计算状态和到期时间,不触发刷新。`auth logout --profile <corpId>` 退出该组织全部账号;精确选择器或本地 profile 名只退出一个账号。
跨组织读取由 agent 编排,而非内置 `--all-orgs`:先 `dws profile list`,每个组织使用唯一的 `isOrgCurrent=true` 账号;若多账号组织没有默认账号,先让用户指定账号。写操作默认只在当前账号执行——跨组织写之前先确认目标组织和账号。
macOS 下,如果已登记的 token slot 无法解密,为避免把系统 Keychain 和 file-DEK 写成混合状态,新的 OAuth 登录会直接拒绝。如果普通终端仍能读取登录态、只有设置 `DWS_DISABLE_KEYCHAIN=1` 的沙箱读不到,可在不暴露 token 的情况下迁移 legacy 与各 profile 的认证条目:
```bash
env -u DWS_DISABLE_KEYCHAIN dws auth migrate-keychain --to file-dek --dry-run --format json
env -u DWS_DISABLE_KEYCHAIN dws auth migrate-keychain --to file-dek --yes --format json
DWS_DISABLE_KEYCHAIN=1 dws auth status --format json
```
迁移会先验证全部认证密文再写入、忽略无关的应用密钥;提交中断后可安全重跑。如果预检确认是密文本身损坏,优先使用 `dws auth logout --profile <corpId:userId>` 只清理受影响账号;只有确认要丢弃全部本地 profile 时才用 `dws auth reset`。
</details>
@@ -310,34 +362,44 @@ dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserNam
### 命令帮助与 Schema
产品命令在静态端点模式下已经编译进二进制。Agent 以 `--help` 和内置 Skill 为事实源;`dws schema` 仅保留给 `dev.*` 等 helper-only schema 查询。
命令帮助和 Schema 分别负责命令契约的不同部分:
- `dws <path> --help` 是命令是否存在、当前二进制接受哪些 flags 的事实源。
- `dws schema "<path>" --compact` 是 Agent 选命令、CLI 参数与约束、风险和确认语义的规范视图;映射或 provenance 审计使用 full leaf 配合 `--jq` 精确投影。
- Help 与 Schema 冲突时视为契约漂移:执行只传 Cobra 接受的参数,安全语义取更保守值。
- Schema 只描述命令,不读取或搜索钉钉业务数据;发现命令后仍需执行真实产品命令。
```bash
# 查看当前编译出的命令面
# 确认命令存在并查看当前接受的 flags
dws aitable record query --help
# helper-only schema 自省
dws schema "dev app create"
# 先在产品内发现命令,再查看选中 leaf 的契约
dws schema aitable --compact
dws schema "aitable record query" --compact
# 构造正确的调用
# 执行真实业务查询
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --limit 10
```
`dws schema --all` 会完整导出命令契约,供工具、CI、审计和兼容性基线使用。Agent 应使用 `--compact` 渐进查询;该视图采用正向字段白名单,full 新增的审计字段不会自动进入 Agent 上下文。
### Agent Skills
仓库内置完整的 Agent Skill 体系(`skills/` 目录),目前重组为两套布局:
仓库内置完整的 Agent Skill 体系(`skills/` 目录),分为两套布局:
- `skills/mono/` — 单 skill 布局(一个 `SKILL.md` + `references/products/`),默认推荐。
- `skills/multi/` — 每个产品一个独立 skill(`dingtalk-aitable/` / `dingtalk-calendar/` / `dingtalk-chat/` ... 共 22 个),每个 skill 自带 `SKILL.md`。🧪 **试验版 / Preview — 各 multi `SKILL.md` 头部有详细注意事项。**
- `skills/mono/` — 单 skill 布局(一个 `SKILL.md` + `references/products/`),legacy。
- `skills/multi/` — 每个产品一个独立 skill(`dingtalk-aitable/` / `dingtalk-calendar/` / `dingtalk-chat/` ...),每个 skill 自带 `SKILL.md`。默认布局。
Schema 生成的叶子 safety/参数/选型文案由 Go 中的 ProductDecl / ContractFinal 声明驱动。原 `internal/cli/schema_hints/` HintFile 目录已完全退役,不得重新引入。
安装之后,Claude Code / Cursor 等 AI 工具就能通过自然语言直接操作钉钉:
```bash
# 安装 skills 到当前项目(默认 mono)
# 安装 skills 到当前项目(默认 multi;DWS_SKILL_MODE=mono 可切回)
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install-skills.sh | sh
```
> `install.sh` 安装到 `$HOME/.agents/skills/dws`(全局);`install-skills.sh` 安装到 `./.agents/skills/dws`(当前项目)。
> 安装器优先使用检测到的具体 Agent 根目录(如 `$HOME/.codex/skills/`);仅在未检测到具体 Agent 时回退到 `.agents/skills/`。multi 为按产品平铺,mono 为 `dws/` 子目录。
>
> 国内用户加 `DWS_GITEE_REPO` 走 Gitee 镜像,见 [国内加速安装](#国内加速安装)。
@@ -347,22 +409,31 @@ curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace
# 交互式:提示选模式 + 目标 Agent
dws skill setup
# 把 mono skill 铺到所有检测到的 Agent home(claude / cursor / codex / opencode / qoder)
dws skill setup --mode mono --target all --yes
# 先预览 mono setup 将备份和替换的精确目录
dws skill setup --mode mono --target all --dry-run
# 只装到某一个 Agent home
dws skill setup --mode multi --target cursor --yes
# 交互执行并确认列出的目录
dws skill setup --mode mono --target all
# 指定本地源目录(比如 fork 或正在改的版本)
# 先预览,再交互确认装到某一个 Agent home
dws skill setup --mode multi --target cursor --dry-run
dws skill setup --mode multi --target cursor
# 指定本地源目录(比如 fork 或正在改的版本),先预览
DWS_SKILL_SOURCE=/path/to/skills dws skill setup --mode multi --dry-run
DWS_SKILL_SOURCE=/path/to/skills dws skill setup --mode multi
```
| 参数 | 取值 | 说明 |
|------|------|------|
| `--mode` | `mono` \| `multi` | skill 布局,不指定则交互式询问 |
| `--target` | `all` \| `claude` \| `cursor` \| `codex` \| `opencode` \| `qoder` | 安装目标,`all` 表示铺到所有检测到的 Agent home |
| `--target` | `all` \| `claude` \| `cursor` \| `codex` \| `zcode` \| `opencode` \| `qoder` | 安装目标;`all` 表示铺到检测到的具体 Agent home(ZCode 为 `~/.zcode/skills`),仅在未检测到具体 Agent 时回退到 `~/.agents/skills` |
| `--source` | 路径 | 本地源目录(覆盖内置 skills) |
| `--yes` | — | 跳过确认提示 |
| `--yes` | — | 仅供脚本使用:跳过确认提示。删除操作仍会先备份到 `~/.dws/skill-backups/` |
> setup 命令可能移除对面模式残留(装 multi 删 `dws/`,装 mono 清理统一状态中登记或属于状态上线前精确官方名称集合的 multi Skill)以及不在 bundle 内的过期受管 Skill。DWS 在 `~/.dws/skills-state.json`(或 `$DWS_CONFIG_DIR/skills-state.json`)集中记录所有权、安装版本、来源和内容摘要。仅有 `dingtalk-*` 前缀不能触发清理,因此其他同前缀市场/用户 Skill 会保留。所有删除都会先列入确认预览,并备份到 `~/.dws/skill-backups/<时间戳>/`;备份失败的目录会保留原样、绝不删除。非交互环境应先用 `--dry-run` 核对输出,再由调用方显式决定是否使用仅供脚本的确认跳过参数。
multi setup 或 upgrade 后,DWS 会把官方 bundle 快照和统一所有权元数据写入 `~/.dws/skills-state.json`(或 `$DWS_CONFIG_DIR/skills-state.json`)。每次 upgrade 都会安装并覆盖该版本的全部预制 Skill;手工删除或通过 setup 排除预制 Skill 不会永久保留,下次 upgrade 会恢复。`dws upgrade --force` 还允许在没有新版本时重装当前 CLI 版本。
环境变量:`DWS_SKILL_MODE=mono|multi`(`install.sh` / `install.ps1` 也认)、`DWS_SKILL_SOURCE=<路径>`。
@@ -375,7 +446,6 @@ DWS_SKILL_SOURCE=/path/to/skills dws skill setup --mode multi
| 意图指南 | `skills/mono/references/intent-guide.md` | 易混淆场景消歧(如 report vs todo) |
| 全局参考 | `skills/mono/references/global-reference.md` | 认证、输出格式、全局 flag |
| 错误码 | `skills/mono/references/error-codes.md` | 错误码 + 调试流程 |
| Recovery 指南 | `skills/mono/references/recovery-guide.md` | `RECOVERY_EVENT_ID` 处理 |
| 现成脚本 | `skills/mono/scripts/*.py` | 13 个批量操作脚本(见下方) |
<details>
@@ -403,6 +473,89 @@ DWS_SKILL_SOURCE=/path/to/skills dws skill setup --mode multi
## 功能特性
<details>
<summary><strong>个人事件订阅</strong> — 实时接收钉钉消息,驱动事件触发的 Agent</summary>
`dws event consume` 使用当前 OAuth 登录用户建立托管的 Stream WebSocket 长连接,并把每条事件以 NDJSON 一行输出到 stdout。当前公开目录覆盖指定范围和全量单聊/群消息、指定发送人、已读/撤回/表情回应、群生命周期,以及七个 OA 审批任务/实例事件。
默认 `ndjson`、`json`、`pretty` 输出保留兼容 transport envelope(`type`、`event_type`、字符串 `data`、`headers`),`compact` 继续沿用原 processor。Agent 或新脚本显式加 `--flatten` 后,输出稳定的顶层业务字段。`--format` 控制 JSON 序列化,`--flatten` 控制数据结构,且不能与 `-f raw` 或 `--debug-raw-events` 同时使用。
> **前置条件**:先运行 `dws auth login`。个人身份从 OAuth token 解析,不允许通过命令行伪造。
只需要 event 能力时,可以使用官方便捷安装脚本:
```bash
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install-event.sh | sh
# 或在已有 dws 环境中安装独立的 multi skill
dws skill setup --mode multi -s event
```
```bash
# 查看公开个人事件目录和 schema
dws event list
dws event schema user_im_message_receive_o2o --flatten
dws event list --category oa
dws event schema user_oa_approval_task_created --flatten
# 监听当前用户被 @ 的消息
dws event +listen-im --kind at-me -f ndjson
# 监听指定发送人的消息
dws event +listen-im --kind sender --user <userId> -f ndjson
# 使用 openDingtalkId 监听外部联系人、机器人或跨组织身份
dws event +listen-im --kind sender --open-dingtalk-id <openDingtalkId> -f ndjson
# 监听指定群的消息
dws event +listen-im --kind group --chat-id <openConversationId> -f ndjson
# 监听所有单聊或所有群消息
dws event +listen-im --kind all-direct -f ndjson
dws event +listen-im --kind all-group -f ndjson
# 监听指定群标题变更、成员进退群或群解散
dws event consume user_im_group_updated --group <openConversationId> --flatten -f ndjson
dws event consume user_im_group_member_added --group <openConversationId> --flatten -f ndjson
dws event consume user_im_group_member_exited --group <openConversationId> --flatten -f ndjson
dws event consume user_im_group_disbanded --group <openConversationId> --flatten -f ndjson
# 一个进程监听同一发送人的消息、已读和撤回
dws event +listen-im --kind sender --user <userId> \
--events message,read,recall -f ndjson
# 一个进程监听全部七个公开 OA 审批事件
dws event consume \
user_oa_approval_task_created \
user_oa_approval_task_finished \
user_oa_approval_task_redirected \
user_oa_approval_instance_started \
user_oa_approval_instance_cc \
user_oa_approval_instance_terminated \
user_oa_approval_instance_finished \
--flatten -f ndjson
# 查看本地 consume,并取消指定订阅
dws event status
dws event stop <subscribe_id>
```
单聊和指定发送人事件必须且只能选择一种目标身份:企业内部 `userId` 使用 `--user`,`openDingtalkId` 使用 `--open-dingtalk-id`。CLI 不会自动猜测或转换身份类型。
| 特性 | 说明 |
|------|------|
| 自动编排 | `consume` 创建或复用个人订阅,`stop` 取消订阅并清理本地状态 |
| 共享连接 | 同一用户的多个 consumer 共享本地 bus 和云端长连接 |
| 多事件进程 | 同一目标的兼容事件可由一个 consume 进程监听,每个事件仍有独立订阅 |
| 订阅隔离 | 正常 consumer 同时按事件类型和 `subscribe_id` 匹配 |
| Agent 友好输出 | Stream 事件写入 stdout,连接状态和诊断信息写入 stderr |
| 状态可观测 | `status` 同时显示服务端订阅、personal bus 和本地 consumers |
| 跨平台 | macOS/Linux 使用 Unix Socket,Windows 使用 Named Pipe |
Agent 工作流和事件参数详见 `skills/multi/dingtalk-event/SKILL.md`。
</details>
<details>
<summary><strong>Raw API 调用</strong> — 直接调用钉钉 OpenAPI</summary>
@@ -484,7 +637,7 @@ dws aitable record query --base-id BASE_ID --tabel-id TABLE_ID # --tabel-i
```bash
# 内置 jq 表达式
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --jq '.invocation.params'
dws schema "dev app create" --jq '.tool.required'
dws schema "dev app create" --jq '.parameters'
# 只返回指定字段
dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocation,response
@@ -493,12 +646,13 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocati
</details>
<details>
<summary><strong>Schema 自省</strong> — 静态端点模式下的 helper-only schema</summary>
<summary><strong>Schema 自省</strong> — Agent 命令发现与执行契约</summary>
```bash
dws schema # 静态端点模式提示
dws schema "dev app create" # 查看 helper-only schema
dws schema "dev app create" --jq '.tool.required' # 查看必填字段
dws schema aitable --compact # 发现产品命令
dws schema "aitable record query" --compact # 查看 Agent leaf 契约
dws schema "aitable record query" --jq '[.parameters | to_entries[] | select(.value.required)]' # 定向查看必填字段
dws schema --all # CI/审计/基线的全量导出
```
</details>
@@ -545,7 +699,7 @@ dws dev connect --channel auto --robot-client-id <id> --robot-client-secret <sec
| 服务 | 命令 | 能力 |
|------|------|------|
| 通讯录 | `contact` | 按姓名 / 手机号 / 工号查人,部门、角色标签、花名册与离职 |
| 通讯录 | `contact` | 按姓名 / 手机号 / 工号查人,部门、角色标签、花名册与离职;创建企业、企业账号及邀请员工 |
| 群聊 | `chat`(`im`)| 发送 / 回复 / 搜索消息,群与成员管理,机器人与 Webhook 发消息,表情反应,撤回 |
| 日历 | `calendar` | 日程 CRUD、参与者、会议室、闲忙与时间建议 |
| 待办 | `todo` | 创建 / 列表 / 修改 / 完成待办及评论 |
@@ -573,7 +727,7 @@ dws dev connect --channel auto --robot-client-id <id> --robot-client-secret <sec
<summary>即将推出</summary>
- `conference`(视频会议)
- 多 skill 模式(实验中)— 每产品一个独立 skill,位于 `skills/multi/`,通过 `dws skill setup --mode multi` 启用
- 多 skill 模式(默认)— 每产品一个独立 skill,位于 `skills/multi/`,安装与升级默认启用;`dws skill setup --mode mono` 交互确认后可切回单 skill
</details>
@@ -624,6 +778,7 @@ dws dev connect --channel auto --robot-client-id <id> --robot-client-secret <sec
## 参考与文档
- [国际版(`.io`)使用手册](./docs/international-region-guide.zh-CN.md) — 国际版登录、国内/国际 profile 切换、隔离验证与排障
- [命令索引](./docs/command-index.md) — 全部运行时命令,带描述与使用场景
- [参考手册](./docs/reference.md) — 环境变量、退出码、输出格式、Shell 补全
- [架构设计](./docs/architecture.md) — 静态端点管道、命令面、Transport 层
-16
View File
@@ -1,16 +0,0 @@
# cli_to_mcp smoke tests
This directory contains lightweight command-to-tool contract tests for hardcoded
DWS commands synced from `dws-wukong`.
The tests do not call live DingTalk APIs. They exercise command help, validation,
and `--dry-run` output so command paths and MCP argument mappings stay stable.
Run with an already built binary:
```bash
DWS_BIN=/path/to/dws pytest auto-test/cli_to_mcp/testcases
```
If `DWS_BIN` is not set, the runner falls back to `go run ./cmd` from the repo
root.
@@ -1,193 +0,0 @@
from test_utils import combined_output, dry_run_args
def assert_ok(result):
output = combined_output(result)
assert result.returncode == 0, output
return output
def test_agoal_strategy_and_contract_cli_to_mcp(dws):
output = assert_ok(
dws.run_raw(
"agoal",
"strategy",
"list",
"--scope-type",
"PERSONAL",
"--scope-id",
"user123",
"--request-id",
"req-1",
"--dry-run",
)
)
assert "list_strategy_decodings" in output
assert dry_run_args(output) == {
"scopeType": "PERSONAL",
"openId": "user123",
"requestId": "req-1",
}
output = assert_ok(
dws.run_raw(
"agoal",
"strategy",
"update",
"--profile-id",
"profile123",
"--content",
'[{"id":"e1","title":{"title":"new"}}]',
"--dry-run",
)
)
assert "update_strategy_decoding" in output
assert dry_run_args(output) == {
"profileId": "profile123",
"content": [{"id": "e1", "title": {"title": "new"}}],
}
output = assert_ok(dws.run_raw("agoal", "contract", "fields", "--dry-run"))
assert "list_op_contract_fields" in output
assert dry_run_args(output) == {}
output = assert_ok(
dws.run_raw(
"agoal",
"contract",
"update",
"--contract-id",
"contract123",
"--dimensions",
'[{"id":"dim1","title":"metric"}]',
"--audit-config",
'{"needAudit":true}',
"--objective-template",
'{"id":"tpl1"}',
"--dry-run",
)
)
assert "update_op_contract" in output
assert dry_run_args(output) == {
"contractId": "contract123",
"dimensions": [{"id": "dim1", "title": "metric"}],
"auditConfig": '{"needAudit":true}',
"objectiveTemplate": '{"id":"tpl1"}',
}
def test_agoal_scorecard_user_report_template_cli_to_mcp(dws):
output = assert_ok(
dws.run_raw(
"agoal",
"scorecard",
"detail",
"--selected-time",
"2026-01-01T00:00:00+08:00",
"--dept-id",
"dept123",
"--dry-run",
)
)
assert "get_score_card_detail" in output
args = dry_run_args(output)
assert args["deptId"] == "dept123"
assert args["selectedTime"] == 1767196800000
output = assert_ok(
dws.run_raw(
"agoal",
"scorecard",
"update",
"--dept-id",
"dept123",
"--selected-time",
"2026-01-01",
"--id",
"sc123",
"--tracking-period-type",
"MONTHLY",
"--content",
'[{"id":"dim1","items":[]}]',
"--dry-run",
)
)
assert "update_score_card" in output
args = dry_run_args(output)
assert args["selectedTime"] == 1767196800000
assert args["content"] == [{"id": "dim1", "items": []}]
output = assert_ok(
dws.run_raw(
"agoal",
"user",
"objectives",
"--user-id",
"user123",
"--rule-id",
"rule123",
"--period-ids",
"p1,p2",
"--dry-run",
)
)
assert "list_user_objectives" in output
assert dry_run_args(output) == {
"dingUserId": "user123",
"objectiveRuleId": "rule123",
"periodIds": ["p1", "p2"],
}
output = assert_ok(
dws.run_raw(
"agoal",
"report",
"submit-detail",
"--template-id",
"tpl123",
"--submit-state",
"LATE",
"--query-date",
"2026-06-18T00:00:00+08:00",
"--page",
"1",
"--page-size",
"20",
"--keyword",
"alice",
"--dry-run",
)
)
assert "get_submit_detail" in output
assert dry_run_args(output) == {
"templateId": "tpl123",
"submitState": "LATE",
"queryDate": "2026-06-18",
"page": 1,
"pageSize": 20,
"keyword": "alice",
}
output = assert_ok(
dws.run_raw(
"agoal",
"obj-template",
"create-or-update",
"--title",
"tpl",
"--dimensions",
'[{"title":"dim"}]',
"--objective-weight",
"--dimension-weight",
"--compute-by-weight",
"--dry-run",
)
)
assert "create_or_update_obj_template" in output
assert dry_run_args(output) == {
"title": "tpl",
"dimensions": '[{"title":"dim"}]',
"objectiveWeight": True,
"dimensionWeight": True,
"computeByWeight": True,
}
@@ -1,163 +0,0 @@
from test_utils import combined_output, dry_run_args
def assert_ok(result):
output = combined_output(result)
assert result.returncode == 0, output
return output
def test_group_notice_cli_to_mcp(dws):
output = assert_ok(
dws.run_raw(
"chat",
"group",
"notice",
"create",
"--group",
"cid123",
"--content",
"maintenance tonight",
"--sticky",
"--send-ding",
"--dry-run",
)
)
assert "create_group_notice" in output
assert dry_run_args(output) == {
"openConversationId": "cid123",
"content": "maintenance tonight",
"sticky": True,
"sendDing": True,
}
output = assert_ok(
dws.run_raw(
"chat",
"group",
"notice",
"edit",
"--group",
"cid123",
"--notice-id",
"notice123",
"--content",
"updated",
"--dry-run",
)
)
assert "edit_group_notice" in output
assert dry_run_args(output) == {
"openConversationId": "cid123",
"dataId": "notice123",
"content": "updated",
}
output = assert_ok(
dws.run_raw(
"chat",
"group",
"notice",
"get",
"--group",
"cid123",
"--notice-id",
"notice123",
"--dry-run",
)
)
assert "get_group_notice" in output
assert dry_run_args(output) == {
"openConversationId": "cid123",
"dataId": "notice123",
}
output = assert_ok(
dws.run_raw(
"chat",
"group",
"notice",
"list",
"--group",
"cid123",
"--limit",
"20",
"--cursor",
"next",
"--scheduled",
"--dry-run",
)
)
assert "list_group_notices" in output
assert dry_run_args(output) == {
"openConversationId": "cid123",
"limit": 20,
"cursor": "next",
"scheduled": True,
}
def test_chat_misc_new_commands_cli_to_mcp(dws):
output = assert_ok(
dws.run_raw(
"chat",
"group",
"share-invite",
"--source",
"sourceCid",
"--target",
"targetCid",
"--expires-seconds",
"3600",
"--uuid",
"uuid-1",
"--dry-run",
)
)
assert "share_group_invite_url" in output
assert dry_run_args(output) == {
"sourceOpenConversationId": "sourceCid",
"targetOpenConversationId": "targetCid",
"expiresSeconds": 3600,
"uuid": "uuid-1",
}
output = assert_ok(
dws.run_raw("chat", "text", "translate", "--query", "hello", "--to", "zh_CN", "--dry-run")
)
assert "translate" in output
assert dry_run_args(output) == {"query": "hello", "to": "zh_CN"}
output = assert_ok(
dws.run_raw(
"chat",
"category",
"create-smart",
"--name",
"priority",
"--keywords",
"alpha,beta",
"--members",
"uid1,uid2",
"--dry-run",
)
)
assert "create_smart_conv_category" in output
assert dry_run_args(output) == {
"title": "priority",
"keywords": ["alpha", "beta"],
"memberOpenDingTalkIds": ["uid1", "uid2"],
}
output = assert_ok(
dws.run_raw(
"chat",
"message",
"list-emotion-replies",
"--msg-ids",
"msg1,msg2",
"--dry-run",
)
)
assert "list_message_emotion_replies" in output
assert dry_run_args(output) == {"openMessageIds": ["msg1", "msg2"]}
@@ -1,8 +0,0 @@
import pytest
from test_utils import DWSRunner
@pytest.fixture(scope="session")
def dws():
return DWSRunner()
@@ -1,54 +0,0 @@
from test_utils import combined_output
def assert_ok(result):
output = combined_output(result)
assert result.returncode == 0, output
return output
def test_doc_import_help_and_validation(dws, tmp_path):
output = assert_ok(dws.run_raw("doc", "import", "--help"))
assert "dws doc import" in output
assert "--file string" in output
assert "--workspace string" in output
assert "--name string" in output
result = dws.run_raw("doc", "import", "--file", str(tmp_path / "missing.md"), "--dry-run")
output = combined_output(result)
assert result.returncode != 0
assert "cannot read file" in output
bad = tmp_path / "bad.exe"
bad.write_text("bad", encoding="utf-8")
result = dws.run_raw("doc", "import", "--file", str(bad), "--dry-run")
output = combined_output(result)
assert result.returncode != 0
assert "unsupported file format" in output
def test_doc_import_dry_run(dws, tmp_path):
source = tmp_path / "sample.md"
source.write_text("# Sample\n\nhello\n", encoding="utf-8")
output = assert_ok(
dws.run_raw(
"doc",
"import",
"--file",
str(source),
"--name",
"Imported Sample",
"--workspace",
"workspace123",
"--dry-run",
)
)
assert "Imported Sample" in output
assert "sample.md" in output
assert "md" in output
def test_doc_import_get_dry_run(dws):
output = assert_ok(dws.run_raw("doc", "import", "get", "--task-id", "task123", "--dry-run"))
assert "task123" in output
@@ -1,143 +0,0 @@
import os
from test_utils import combined_output, dry_run_args
def mail_email() -> str:
return os.environ.get("DINGTALK_MAIL_EMAIL", "user@example.com")
def assert_ok(result):
output = combined_output(result)
assert result.returncode == 0, output
return output
def assert_fails(result, expected: str):
output = combined_output(result)
assert result.returncode != 0, output
assert expected in output
def test_mailbox_profile_cli_to_mcp(dws):
output = assert_ok(dws.run_raw("mail", "mailbox", "profile", "--help"))
assert "dws mail mailbox profile" in output
assert "--email string" in output
assert_fails(dws.run_raw("mail", "mailbox", "profile"), "email")
output = assert_ok(
dws.run_raw("mail", "mailbox", "profile", "--email", mail_email(), "--dry-run")
)
assert "get_mailbox_profile" in output
assert dry_run_args(output) == {"email": mail_email()}
def test_message_batch_get_cli_to_mcp(dws):
output = assert_ok(dws.run_raw("mail", "message", "batch-get", "--help"))
assert "dws mail message batch-get" in output
assert "--email string" in output
assert "--ids string" in output
assert_fails(
dws.run_raw("mail", "message", "batch-get", "--email", mail_email()),
"ids",
)
too_many_ids = ",".join(f"msg_{i:02d}" for i in range(21))
assert_fails(
dws.run_raw(
"mail",
"message",
"batch-get",
"--email",
mail_email(),
"--ids",
too_many_ids,
"--dry-run",
),
"20",
)
output = assert_ok(
dws.run_raw(
"mail",
"message",
"batch-get",
"--email",
mail_email(),
"--ids",
"msg_001,msg_002",
"--dry-run",
)
)
assert "get_email_by_message_id" in output
assert "msg_001" in output
assert "msg_002" in output
def test_sent_message_recall_cli_to_mcp(dws):
output = assert_ok(dws.run_raw("mail", "sent-message", "recall", "--help"))
assert "dws mail sent-message recall" in output
assert "--subject string" in output
assert "--yes" in output
assert_fails(
dws.run_raw(
"mail",
"sent-message",
"recall",
"--email",
mail_email(),
"--id",
"msg_001",
"--subject",
"subject",
),
"--yes",
)
output = assert_ok(
dws.run_raw(
"mail",
"sent-message",
"recall",
"--email",
mail_email(),
"--id",
"msg_001",
"--subject",
"subject",
"--yes",
"--dry-run",
)
)
assert "recall_sent_message" in output
assert dry_run_args(output) == {
"email": mail_email(),
"id": "msg_001",
"subject": "subject",
}
def test_sent_message_recall_detail_cli_to_mcp(dws):
output = assert_ok(dws.run_raw("mail", "sent-message", "recall-detail", "--help"))
assert "dws mail sent-message recall-detail" in output
assert "--email string" in output
assert "--id string" in output
assert "FINISHED" in output
output = assert_ok(
dws.run_raw(
"mail",
"sent-message",
"recall-detail",
"--email",
mail_email(),
"--id",
"task_001",
"--dry-run",
)
)
assert "get_recall_detail" in output
assert dry_run_args(output) == {"email": mail_email(), "id": "task_001"}
@@ -1,133 +0,0 @@
import os
from test_utils import combined_output, dry_run_args
def mail_email() -> str:
return os.environ.get("DINGTALK_MAIL_EMAIL", "user@example.com")
def assert_ok(result):
output = combined_output(result)
assert result.returncode == 0, output
return output
def test_auto_reply_update_cli_to_mcp(dws):
output = assert_ok(dws.run_raw("mail", "auto-reply", "update", "--help"))
for flag in ("--email string", "--enabled string", "--start string", "--end string", "--scope string", "--content string"):
assert flag in output
output = assert_ok(
dws.run_raw(
"mail",
"auto-reply",
"update",
"--email",
mail_email(),
"--enabled",
"true",
"--start",
"2026/07/01 09:00:00 +0800",
"--end",
"2026/07/07 18:00:00 +0800",
"--scope",
"all",
"--content",
"out of office",
"--dry-run",
)
)
assert "update_auto_reply" in output
assert dry_run_args(output) == {
"email": mail_email(),
"enabled": True,
"startTime": "2026/07/01 09:00:00 +0800",
"endTime": "2026/07/07 18:00:00 +0800",
"scope": "all",
"content": "out of office",
}
def test_allow_list_cli_to_mcp(dws):
output = assert_ok(dws.run_raw("mail", "allow-list", "list", "--email", mail_email(), "--dry-run"))
assert "list_mailbox_allowlist" in output
assert dry_run_args(output) == {"email": mail_email()}
output = assert_ok(
dws.run_raw(
"mail",
"allow-list",
"add",
"--email",
mail_email(),
"--entries",
"partner@example.com,@example.org",
"--dry-run",
)
)
assert "add_mailbox_allowlist" in output
assert dry_run_args(output) == {
"email": mail_email(),
"entries": ["partner@example.com", "@example.org"],
}
output = assert_ok(
dws.run_raw(
"mail",
"allow-list",
"remove",
"--email",
mail_email(),
"--entries",
"partner@example.com",
"--dry-run",
)
)
assert "remove_mailbox_allowlist" in output
assert dry_run_args(output) == {
"email": mail_email(),
"entries": ["partner@example.com"],
}
def test_block_list_cli_to_mcp(dws):
output = assert_ok(dws.run_raw("mail", "block-list", "list", "--email", mail_email(), "--dry-run"))
assert "list_mailbox_blocklist" in output
assert dry_run_args(output) == {"email": mail_email()}
output = assert_ok(
dws.run_raw(
"mail",
"block-list",
"add",
"--email",
mail_email(),
"--entries",
"spam@example.com,@junk.example",
"--dry-run",
)
)
assert "add_mailbox_blocklist" in output
assert dry_run_args(output) == {
"email": mail_email(),
"entries": ["spam@example.com", "@junk.example"],
}
output = assert_ok(
dws.run_raw(
"mail",
"block-list",
"remove",
"--email",
mail_email(),
"--entries",
"spam@example.com",
"--dry-run",
)
)
assert "remove_mailbox_blocklist" in output
assert dry_run_args(output) == {
"email": mail_email(),
"entries": ["spam@example.com"],
}
@@ -1,80 +0,0 @@
from test_utils import combined_output, dry_run_args
def assert_ok(result):
output = combined_output(result)
assert result.returncode == 0, output
return output
def test_group_dimension_cli_to_mcp(dws):
output = assert_ok(dws.run_raw("sheet", "group-dimension", "--help"))
assert "dws sheet group-dimension" in output
assert "--group-state string" in output
output = assert_ok(
dws.run_raw(
"sheet",
"group-dimension",
"--node",
"node123",
"--sheet-id",
"Sheet1",
"--range",
"3:7",
"--group-state",
"fold",
"--dry-run",
)
)
assert "group_dimension" in output
assert dry_run_args(output) == {
"nodeId": "node123",
"sheetId": "Sheet1",
"range": "3:7",
"groupState": "fold",
}
def test_ungroup_dimension_cli_to_mcp(dws):
output = assert_ok(dws.run_raw("sheet", "ungroup-dimension", "--help"))
assert "dws sheet ungroup-dimension" in output
output = assert_ok(
dws.run_raw(
"sheet",
"ungroup-dimension",
"--node",
"node123",
"--sheet-id",
"Sheet1",
"--range",
"C:F",
"--dry-run",
)
)
assert "ungroup_dimension" in output
assert dry_run_args(output) == {
"nodeId": "node123",
"sheetId": "Sheet1",
"range": "C:F",
}
def test_group_dimension_rejects_invalid_state(dws):
result = dws.run_raw(
"sheet",
"group-dimension",
"--node",
"node123",
"--sheet-id",
"Sheet1",
"--range",
"3:7",
"--group-state",
"invalid",
"--dry-run",
)
output = combined_output(result)
assert result.returncode != 0
assert "group-state" in output
@@ -1,58 +0,0 @@
import json
import os
import re
import shlex
import subprocess
from pathlib import Path
def repo_root(start_file: str) -> Path:
current = Path(start_file).resolve()
for parent in [current, *current.parents]:
if (parent / "go.mod").exists():
return parent
raise RuntimeError(f"cannot locate repo root from {start_file}")
def resolve_dws_cmd(start_file: str) -> list[str]:
root = repo_root(start_file)
if env_bin := os.environ.get("DWS_BIN"):
return shlex.split(env_bin)
for rel in ("dws", "build/dws", "bin/dws", "dingtalk-workspace-cli"):
candidate = root / rel
if candidate.exists() and os.access(candidate, os.X_OK):
return [str(candidate)]
return ["go", "run", "./cmd"]
def combined_output(result: subprocess.CompletedProcess) -> str:
return (result.stdout or "") + (result.stderr or "")
def dry_run_args(output: str) -> dict:
match = re.search(r"Arguments:\s*(\{.*\})", output, re.S)
assert match, f"dry-run output does not contain Arguments JSON: {output}"
return json.loads(match.group(1))
class DWSRunner:
def __init__(self):
self.root = repo_root(__file__)
self.cmd = resolve_dws_cmd(__file__)
def run_raw(self, *args: str, timeout: int = 45) -> subprocess.CompletedProcess:
return subprocess.run(
[*self.cmd, *args],
cwd=self.root,
text=True,
capture_output=True,
timeout=timeout,
)
def run(self, *args: str, timeout: int = 45):
result = self.run_raw(*args, timeout=timeout)
output = combined_output(result)
assert result.returncode == 0, output
return json.loads(result.stdout)
+63
View File
@@ -0,0 +1,63 @@
class __CLASS_NAME__ < Formula
desc "__DESCRIPTION__"
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
version "__VERSION__"
license "Apache-2.0"
__KEG_ONLY_LINE__
on_macos do
if Hardware::CPU.arm?
url "__DARWIN_ARM64_URL__"
sha256 "__DARWIN_ARM64_SHA256__"
else
url "__DARWIN_AMD64_URL__"
sha256 "__DARWIN_AMD64_SHA256__"
end
end
on_linux do
if Hardware::CPU.arm?
url "__LINUX_ARM64_URL__"
sha256 "__LINUX_ARM64_SHA256__"
else
url "__LINUX_AMD64_URL__"
sha256 "__LINUX_AMD64_SHA256__"
end
end
resource "skills" do
url "__SKILLS_URL__"
sha256 "__SKILLS_SHA256__"
end
def install
root = Dir["dws-*"].find { |entry| File.directory?(entry) } || "."
binary = File.join(root, "dws")
raise "binary not found: #{binary}" unless File.exist?(binary)
bin.install binary => "dws"
%w[LICENSE NOTICE README.md CHANGELOG.md].each do |name|
source = File.join(root, name)
pkgshare.install source if File.exist?(source)
end
skill_dest = pkgshare/"skills/dws"
skill_dest.mkpath
resource("skills").stage do
cp_r(Dir["*"], skill_dest)
end
end
def caveats
<<~EOS
Agent Skills are bundled in #{pkgshare}/skills/dws.
Run `dws skill setup` to install them into your Agent directories.
__CHANNEL_CAVEAT__
EOS
end
test do
assert_match version.to_s, shell_output("#{bin}/dws version")
end
end
+7 -38
View File
@@ -1,5 +1,5 @@
class __CLASS_NAME__ < Formula
desc "DingTalk Workspace CLI"
desc "Install locally built DingTalk workspace CLI artifacts for verification"
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
url "__ARCHIVE_URL__"
sha256 "__ARCHIVE_SHA256__"
@@ -12,8 +12,6 @@ __KEG_ONLY_LINE__
end
def install
require "fileutils"
root = Dir["dws-*"].find { |entry| File.directory?(entry) } || "."
binary = File.join(root, "dws")
raise "binary not found: #{binary}" unless File.exist?(binary)
@@ -28,44 +26,15 @@ __KEG_ONLY_LINE__
skill_dest = pkgshare/"skills/dws"
skill_dest.mkpath
resource("skills").stage do
FileUtils.cp_r(Dir["*"], skill_dest)
cp_r(Dir["*"], skill_dest)
end
end
def post_install
require "fileutils"
skill_root = pkgshare/"skills/dws"
entries = Dir["#{skill_root}/*"]
return if entries.empty?
targets = [
Pathname.new(File.join(Dir.home, ".agents/skills/dws")),
Pathname.new(File.join(Dir.home, ".claude/skills/dws")),
Pathname.new(File.join(Dir.home, ".cursor/skills/dws")),
Pathname.new(File.join(Dir.home, ".qoder/skills/dws")),
Pathname.new(File.join(Dir.home, ".qoderwork/skills/dws")),
Pathname.new(File.join(Dir.home, ".gemini/skills/dws")),
Pathname.new(File.join(Dir.home, ".codex/skills/dws")),
Pathname.new(File.join(Dir.home, ".github/skills/dws")),
Pathname.new(File.join(Dir.home, ".windsurf/skills/dws")),
Pathname.new(File.join(Dir.home, ".augment/skills/dws")),
Pathname.new(File.join(Dir.home, ".cline/skills/dws")),
Pathname.new(File.join(Dir.home, ".amp/skills/dws")),
Pathname.new(File.join(Dir.home, ".kiro/skills/dws")),
Pathname.new(File.join(Dir.home, ".trae/skills/dws")),
Pathname.new(File.join(Dir.home, ".openclaw/skills/dws")),
Pathname.new(File.join(Dir.home, ".hermes/skills/dws")),
]
targets.each_with_index do |dest, index|
parent_gate = dest.parent.parent
next if index > 0 && !parent_gate.directory?
FileUtils.rm_rf(dest)
FileUtils.mkdir_p(dest)
FileUtils.cp_r(entries, dest)
end
def caveats
<<~EOS
Agent Skills are bundled in #{pkgshare}/skills/dws.
Run `dws skill setup` to install them into your Agent directories.
EOS
end
test do
+1748 -40
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -32,6 +32,6 @@
"README.md"
],
"engines": {
"node": ">=16"
"node": ">=16.7.0"
}
}
+413
View File
@@ -0,0 +1,413 @@
// Command fetch_mcp_metadata pulls tools/list from ALL live MCP server endpoints
// and writes a local diagnostic dump. It is NOT a Schema delivery refresh:
// schema_mcp_metadata.json is retired; production Catalog assembles from
// Contract/ParamDecl/Interface + Cobra only.
//
// Usage:
//
// dws auth login # ensure valid auth
// make fetch-mcp-metadata # writes artifacts/mcp_metadata_diagnostic.json
//
// The tool loads auth from the DWS keychain, iterates static server endpoints
// (internal/syncdata.StaticServers), calls tools/list on each, merges results,
// and writes the requested -output path (refuses the retired pin path).
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/syncdata"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/transport"
)
// toolLister is the tools/list capability consumed by run; production code
// uses transport.Client, tests inject fakes.
type toolLister interface {
ListTools(ctx context.Context, endpoint string) (transport.ToolsListResult, error)
}
// Injection points so run() is fully testable without network/keychain/exit.
var (
osExit = os.Exit
getenv = os.Getenv
loadTokenData = auth.LoadTokenDataKeychain
staticServers = syncdata.StaticServers
registrySource = collectedIdentityInterfaceRefs
collectIdentitySpecs = cli.CollectIdentitySpecs
listToolsTimeout = 30 * time.Second
gitHeadPath = ".git/HEAD"
newToolLister = func(token string) toolLister {
return transport.NewClient(&http.Client{Timeout: 60 * time.Second}).WithAuth(token, nil)
}
)
func main() {
osExit(run(os.Args[1:], os.Stderr))
}
func run(args []string, stderr io.Writer) int {
flags := flag.NewFlagSet("fetch_mcp_metadata", flag.ContinueOnError)
flags.SetOutput(stderr)
output := flags.String("output", "artifacts/mcp_metadata_diagnostic.json", "diagnostic dump path (not a Schema pin)")
if err := flags.Parse(args); err != nil {
return 2
}
if retiredPinnedMCPMetadataPath(*output) {
fmt.Fprintln(stderr, "fetch_mcp_metadata: refusing to write retired Schema pin internal/cli/schema_mcp_metadata.json")
return 2
}
token := resolveToken(stderr)
if token == "" {
fmt.Fprintln(stderr, "fetch_mcp_metadata: no auth token. Run 'dws auth login' first.")
return 1
}
client := newToolLister(token)
// Iterate ALL static server endpoints (26 servers covering all products).
servers := staticServers()
fmt.Fprintf(stderr, "fetch_mcp_metadata: querying %d server endpoints\n", len(servers))
// Collect command identity to build tool_name → interface_ref mapping.
registryMap := loadRegistryInterfaceRefs(stderr)
fmt.Fprintf(stderr, "fetch_mcp_metadata: registry mapping: %d entries\n", len(registryMap))
// Load a previous diagnostic dump (if any) to preserve hand-curated
// cross-server interface_ref mappings that automated matching can't derive.
prevData, prevErr := os.ReadFile(*output)
prevTools := map[string]map[string]any{}
if prevErr == nil {
var prev struct {
Tools map[string]map[string]any `json:"tools"`
}
if json.Unmarshal(prevData, &prev) == nil {
prevTools = prev.Tools
}
}
// Start from previous data (preserves cross-server refs), then overwrite
// with fresh MCP data where available.
allTools := make(map[string]map[string]any)
for k, v := range prevTools {
allTools[k] = v
}
// Reviewed cross-server interface_refs live only in the previous snapshot
// (the registry stores canonical paths, not MCP identities). Build a
// live-key → canonicals index so those tools get refreshed instead of
// being skipped and frozen at the previous snapshot forever.
crossRefs := buildCrossServerRefs(prevTools, registryMap)
if len(crossRefs) > 0 {
fmt.Fprintf(stderr, "fetch_mcp_metadata: cross-server ref index: %d live keys\n", len(crossRefs))
}
// Canonicals with a reviewed cross-server identity must only be fed by
// that identity; a same-named tool on another server is a coincidence,
// not a data source.
crossOwned := map[string]bool{}
for _, canonicals := range crossRefs {
for _, canonical := range canonicals {
crossOwned[canonical] = true
}
}
totalRaw := 0
failedServices := []string{}
for _, srv := range servers {
endpoint := strings.TrimSpace(srv.Endpoint)
if endpoint == "" {
continue
}
ctx, cancel := context.WithTimeout(context.Background(), listToolsTimeout)
result, err := client.ListTools(ctx, endpoint)
cancel()
if err != nil {
fmt.Fprintf(stderr, " [skip] %s: %v\n", srv.ID, err)
failedServices = append(failedServices, srv.ID)
continue
}
fmt.Fprintf(stderr, " [ok] %s: %d tools\n", srv.ID, len(result.Tools))
totalRaw += len(result.Tools)
for _, tool := range result.Tools {
name := strings.TrimSpace(tool.Name)
if name == "" {
continue
}
// Direct match: CLI canonical equals server-prefixed tool name
// (e.g., "doc.copy_document"). Cross-owned canonicals are skipped
// here — their reviewed identity feeds them below.
canonicalKey := srv.ID + "." + name
if ref, hasRef := registryMap[canonicalKey]; hasRef && !crossOwned[canonicalKey] {
mergeLiveMCPTool(allTools, canonicalKey, tool, ref)
}
// Cross-server match: registry canonicals whose reviewed
// interface_ref points at this live tool (one live tool may feed
// several canonicals, e.g. advperm_enable/disable → set_advanced_permission).
for _, canonical := range crossRefs[canonicalKey] {
mergeLiveMCPTool(allTools, canonical, tool, registryMap[canonical])
}
}
}
matched := 0
for _, t := range allTools {
if _, ok := t["interface_ref"]; ok {
matched++
}
}
fmt.Fprintf(stderr, "fetch_mcp_metadata: MCP matched=%d, with interface_ref=%d\n", len(allTools), matched)
// Fill gaps: for registry canonicals not covered by MCP tools/list OR
// previous data, add stub entries (interface_ref only).
stubs := 0
for canonicalKey, ref := range registryMap {
if _, exists := allTools[canonicalKey]; exists {
continue
}
allTools[canonicalKey] = map[string]any{
"interface_ref": ref,
}
stubs++
}
if stubs > 0 {
fmt.Fprintf(stderr, "fetch_mcp_metadata: added %d registry stubs (no MCP data, interface_ref only)\n", stubs)
}
// Compute coverage fields required by check-schema-catalog.sh. Failed
// services must be reported honestly so policy can spot snapshot gaps.
if len(failedServices) > 0 {
fmt.Fprintf(stderr, "fetch_mcp_metadata: %d/%d services unreachable: %s\n",
len(failedServices), len(servers), strings.Join(failedServices, ", "))
}
metadata := map[string]any{
"version": 1,
"source": "mcp-tools-list+cli-registry",
"coverage": buildCoverage(len(servers), failedServices, totalRaw, len(allTools), stubs),
"tools": allTools,
}
// source_revision: git commit hash (proves provenance).
if rev, err := os.ReadFile(gitHeadPath); err == nil {
metadata["source_revision"] = strings.TrimSpace(string(rev))
}
if err := writeMetadata(*output, metadata); err != nil {
fmt.Fprintf(stderr, "fetch_mcp_metadata: %v\n", err)
return 1
}
fmt.Fprintf(stderr, "fetch_mcp_metadata: wrote %d tools to %s\n", len(allTools), *output)
return 0
}
// resolveToken returns the access token from DWS_ACCESS_TOKEN or, as a
// fallback, the DWS keychain.
func resolveToken(stderr io.Writer) string {
token := strings.TrimSpace(getenv("DWS_ACCESS_TOKEN"))
if token != "" {
return token
}
td, err := loadTokenData()
if err != nil || td == nil || td.AccessToken == "" {
return ""
}
fmt.Fprintf(stderr, "fetch_mcp_metadata: loaded token from keychain (%d chars)\n", len(td.AccessToken))
return td.AccessToken
}
// writeMetadata marshals the snapshot and writes it to the output path.
func writeMetadata(path string, metadata map[string]any) error {
data, err := json.MarshalIndent(metadata, "", " ")
if err != nil {
return fmt.Errorf("marshal failed: %w", err)
}
data = append(data, '\n')
if err := os.WriteFile(path, data, 0644); err != nil {
return fmt.Errorf("write %s failed: %w", path, err)
}
return nil
}
// buildCoverage reports snapshot coverage honestly: snapshot_services only
// counts services whose tools/list succeeded, missing_services names the
// failures, and matched_tools excludes registry stubs (entries carrying no
// live MCP metadata) so a stub-heavy snapshot cannot claim full matching.
func buildCoverage(sourceServices int, failedServices []string, sourceTools, surfaceTools, stubs int) map[string]any {
missing := failedServices
if missing == nil {
missing = []string{}
}
return map[string]any{
"surface_scope": "source_revision",
"source_services": sourceServices,
"snapshot_services": sourceServices - len(missing),
"missing_services": missing,
"source_tools": sourceTools,
"surface_tools": surfaceTools,
"matched_tools": surfaceTools - stubs,
"aliased_tools": 0,
"unmatched_tools": stubs,
}
}
// mergeLiveMCPTool replaces stale live-derived fields while retaining an
// existing reviewed interface_ref. Some CLI canonicals intentionally route to
// a differently named product/RPC, so the previous cross-server mapping must
// survive even though title, description, and parameters are refreshed.
func mergeLiveMCPTool(allTools map[string]map[string]any, canonicalKey string, tool transport.ToolDescriptor, fallbackRef map[string]string) {
interfaceRef := any(fallbackRef)
if previous := allTools[canonicalKey]; previous != nil {
if reviewedRef, ok := previous["interface_ref"]; ok && reviewedRef != nil {
interfaceRef = reviewedRef
}
}
entry := map[string]any{
"title": tool.Title,
"description": tool.Description,
"interface_ref": interfaceRef,
}
if tool.InputSchema != nil {
entry["parameters"] = extractParams(tool.InputSchema)
}
allTools[canonicalKey] = entry
}
// buildCrossServerRefs indexes reviewed cross-server mappings from the
// previous snapshot: for every registry canonical whose interface_ref names a
// different MCP identity (product_id.rpc_name != canonical), the live key is
// mapped back to that canonical. One live tool may serve several canonicals,
// so values are slices, sorted for deterministic merge order.
func buildCrossServerRefs(prevTools map[string]map[string]any, registryMap map[string]map[string]string) map[string][]string {
index := map[string][]string{}
for canonical, entry := range prevTools {
if _, inRegistry := registryMap[canonical]; !inRegistry {
continue
}
ref, ok := entry["interface_ref"].(map[string]any)
if !ok {
continue
}
productID, _ := ref["product_id"].(string)
rpcName, _ := ref["rpc_name"].(string)
if productID == "" || rpcName == "" {
continue
}
liveKey := productID + "." + rpcName
if liveKey == canonical {
continue
}
index[liveKey] = append(index[liveKey], canonical)
}
for _, canonicals := range index {
sort.Strings(canonicals)
}
return index
}
// collectedIdentityInterfaceRefs collects command identity from the live
// command tree — the replacement for the retired reviewed CommandRegistry —
// and derives the canonical_path → {product_id, rpc_name} mapping used for
// interface_ref injection.
func collectedIdentityInterfaceRefs() (map[string]map[string]string, error) {
root := app.NewSchemaSourceRootCommand()
specs, _, err := collectIdentitySpecs(root)
if err != nil {
return nil, fmt.Errorf("collect command identity: %w", err)
}
out := make(map[string]map[string]string, len(specs))
for _, spec := range specs {
cp := strings.TrimSpace(spec.CanonicalPath)
if cp == "" || !strings.Contains(cp, ".") {
continue
}
parts := strings.SplitN(cp, ".", 2)
out[cp] = map[string]string{
"product_id": parts[0],
"rpc_name": parts[1],
}
}
return out, nil
}
// loadRegistryInterfaceRefs builds the canonical_path → interface_ref mapping
// from the collected command identity. 与旧实现同等告警:静默返回空映射会让
// 所有 live tool 被丢弃、产出 stub-only 快照且零提示(P1#1 的故障模式)。
func loadRegistryInterfaceRefs(stderr io.Writer) map[string]map[string]string {
refs, err := registrySource()
if err != nil {
fmt.Fprintf(stderr, "fetch_mcp_metadata: warning: cannot collect command identity: %v\n", err)
return map[string]map[string]string{}
}
return refs
}
func retiredPinnedMCPMetadataPath(path string) bool {
cleaned := strings.ReplaceAll(strings.TrimSpace(path), "\\", "/")
return cleaned == "internal/cli/schema_mcp_metadata.json" ||
strings.HasSuffix(cleaned, "/internal/cli/schema_mcp_metadata.json")
}
// extractParams converts a JSON Schema inputSchema (from MCP tools/list) into
// the flat param-name → metadata map used by diagnostic dumps.
func extractParams(inputSchema map[string]any) map[string]map[string]any {
if inputSchema == nil {
return nil
}
properties, ok := inputSchema["properties"].(map[string]any)
if !ok {
return nil
}
requiredSet := map[string]bool{}
if req, ok := inputSchema["required"].([]any); ok {
for _, r := range req {
if s, ok := r.(string); ok {
requiredSet[s] = true
}
}
}
params := make(map[string]map[string]any, len(properties))
for name, raw := range properties {
prop, ok := raw.(map[string]any)
if !ok {
continue
}
meta := map[string]any{}
if t, ok := prop["type"].(string); ok {
meta["type"] = t
}
if d, ok := prop["description"].(string); ok {
meta["description"] = d
}
if d, ok := prop["default"].(string); ok {
meta["default"] = d
}
if e, ok := prop["enum"].([]any); ok {
enums := make([]string, 0, len(e))
for _, v := range e {
if s, ok := v.(string); ok {
enums = append(enums, s)
}
}
if len(enums) > 0 {
meta["enum"] = enums
}
}
meta["required"] = requiredSet[name]
params[name] = meta
}
return params
}
+612
View File
@@ -0,0 +1,612 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"math"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/syncdata"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/transport"
"github.com/spf13/cobra"
)
func TestCrossPlatformCoverageLoadRegistryInterfaceRefsCollectsIdentity(t *testing.T) {
var stderr bytes.Buffer
refs := loadRegistryInterfaceRefs(&stderr)
if len(refs) == 0 {
t.Fatal("loadRegistryInterfaceRefs() returned no collected commands")
}
got, ok := refs["calendar.list_calendars"]
if !ok {
t.Fatal("calendar.list_calendars missing from collected command identity")
}
if got["product_id"] != "calendar" || got["rpc_name"] != "list_calendars" {
t.Fatalf("calendar.list_calendars ref = %#v", got)
}
direct, err := collectedIdentityInterfaceRefs()
if err != nil {
t.Fatalf("collectedIdentityInterfaceRefs() error = %v", err)
}
if len(direct) == 0 || direct["calendar.list_calendars"]["rpc_name"] != "list_calendars" {
t.Fatalf("collectedIdentityInterfaceRefs() = %#v", direct["calendar.list_calendars"])
}
prevRegistry := registrySource
prevCollect := collectIdentitySpecs
t.Cleanup(func() {
registrySource = prevRegistry
collectIdentitySpecs = prevCollect
})
registrySource = func() (map[string]map[string]string, error) {
return nil, errors.New("collect boom")
}
stderr.Reset()
if got := loadRegistryInterfaceRefs(&stderr); len(got) != 0 || !strings.Contains(stderr.String(), "cannot collect command identity") {
t.Fatalf("loadRegistryInterfaceRefs error path = %#v stderr=%q", got, stderr.String())
}
collectIdentitySpecs = func(*cobra.Command) ([]cli.CommandSpec, cli.IdentityCollectionReport, error) {
return nil, cli.IdentityCollectionReport{}, errors.New("walk boom")
}
if _, err := collectedIdentityInterfaceRefs(); err == nil || !strings.Contains(err.Error(), "collect command identity") {
t.Fatalf("collectedIdentityInterfaceRefs wrap error = %v", err)
}
collectIdentitySpecs = func(*cobra.Command) ([]cli.CommandSpec, cli.IdentityCollectionReport, error) {
return []cli.CommandSpec{
{CanonicalPath: ""},
{CanonicalPath: "nodot"},
{CanonicalPath: "doc.create"},
}, cli.IdentityCollectionReport{}, nil
}
gotRefs, err := collectedIdentityInterfaceRefs()
if err != nil || len(gotRefs) != 1 || gotRefs["doc.create"]["rpc_name"] != "create" {
t.Fatalf("collectedIdentityInterfaceRefs skip = %#v err=%v", gotRefs, err)
}
}
func TestBuildCrossServerRefs(t *testing.T) {
registryMap := map[string]map[string]string{
"aitable.advperm_enable": {"product_id": "aitable", "rpc_name": "advperm_enable"},
"aitable.advperm_disable": {"product_id": "aitable", "rpc_name": "advperm_disable"},
"doc.copy_document": {"product_id": "doc", "rpc_name": "copy_document"},
}
prevTools := map[string]map[string]any{
// Fan-out: two canonicals share one live tool; insertion order must
// not affect the sorted result.
"aitable.advperm_enable": {
"interface_ref": map[string]any{"product_id": "aitable-helper", "rpc_name": "set_advanced_permission"},
},
"aitable.advperm_disable": {
"interface_ref": map[string]any{"product_id": "aitable-helper", "rpc_name": "set_advanced_permission"},
},
// Identity ref (live key == canonical) needs no cross entry.
"doc.copy_document": {
"interface_ref": map[string]any{"product_id": "doc", "rpc_name": "copy_document"},
},
// Not in the registry: must be ignored.
"ghost.tool": {
"interface_ref": map[string]any{"product_id": "ghost-helper", "rpc_name": "haunt"},
},
}
got := buildCrossServerRefs(prevTools, registryMap)
want := map[string][]string{
"aitable-helper.set_advanced_permission": {"aitable.advperm_disable", "aitable.advperm_enable"},
}
if len(got) != len(want) {
t.Fatalf("index = %#v, want %#v", got, want)
}
for k, v := range want {
if gv := got[k]; len(gv) != len(v) || gv[0] != v[0] || gv[1] != v[1] {
t.Fatalf("index[%q] = %v, want %v", k, gv, v)
}
}
}
func TestBuildCrossServerRefsSkipsMalformedRefs(t *testing.T) {
registryMap := map[string]map[string]string{
"a.x": {"product_id": "a", "rpc_name": "x"},
"a.y": {"product_id": "a", "rpc_name": "y"},
"a.z": {"product_id": "a", "rpc_name": "z"},
}
prevTools := map[string]map[string]any{
"a.x": {"interface_ref": "not-a-map"},
"a.y": {"interface_ref": map[string]any{"product_id": "", "rpc_name": "r"}},
"a.z": {"title": "no ref at all"},
}
if got := buildCrossServerRefs(prevTools, registryMap); len(got) != 0 {
t.Fatalf("index = %#v, want empty", got)
}
}
func TestRunRefreshesCrossServerTools(t *testing.T) {
registry := func() (map[string]map[string]string, error) {
return map[string]map[string]string{
"aitable.advperm_enable": {"product_id": "aitable", "rpc_name": "advperm_enable"},
"aitable.advperm_disable": {"product_id": "aitable", "rpc_name": "advperm_disable"},
}, nil
}
servers := []syncdata.ServerInfo{{ID: "aitable-helper", Endpoint: "https://helper.example"}}
lister := &fakeLister{
results: map[string]transport.ToolsListResult{
"https://helper.example": {Tools: []transport.ToolDescriptor{
{Name: "set_advanced_permission", Title: "live title", Description: "live desc"},
}},
},
}
stubDeps(t, "env-token", nil, servers, lister, registry)
output := filepath.Join(t.TempDir(), "snapshot.json")
prev := `{"tools":{
"aitable.advperm_enable":{"title":"stale","interface_ref":{"product_id":"aitable-helper","rpc_name":"set_advanced_permission"}},
"aitable.advperm_disable":{"title":"stale","interface_ref":{"product_id":"aitable-helper","rpc_name":"set_advanced_permission"}}
}}`
if err := os.WriteFile(output, []byte(prev), 0o600); err != nil {
t.Fatal(err)
}
var stderr bytes.Buffer
if code := run([]string{"--output", output}, &stderr); code != 0 {
t.Fatalf("run() = %d, stderr=%s", code, stderr.String())
}
if !strings.Contains(stderr.String(), "cross-server ref index: 1 live keys") {
t.Fatalf("stderr = %q, want cross-server index log", stderr.String())
}
data, err := os.ReadFile(output)
if err != nil {
t.Fatal(err)
}
var snapshot struct {
Tools map[string]map[string]any `json:"tools"`
}
if err := json.Unmarshal(data, &snapshot); err != nil {
t.Fatal(err)
}
for _, canonical := range []string{"aitable.advperm_enable", "aitable.advperm_disable"} {
entry := snapshot.Tools[canonical]
if entry["title"] != "live title" || entry["description"] != "live desc" {
t.Fatalf("%s = %#v, want live refresh", canonical, entry)
}
ref := entry["interface_ref"].(map[string]any)
if ref["product_id"] != "aitable-helper" || ref["rpc_name"] != "set_advanced_permission" {
t.Fatalf("%s reviewed ref lost: %#v", canonical, ref)
}
}
}
// TestRunCrossOwnedCanonicalIgnoresNameCoincidence:canonical 拥有评审过的
// 跨 server 身份时,另一 server 上恰好同名的工具不得直连覆盖其元数据——
// 数据源只能是评审身份指向的 live 工具。
func TestRunCrossOwnedCanonicalIgnoresNameCoincidence(t *testing.T) {
registry := func() (map[string]map[string]string, error) {
return map[string]map[string]string{
"aitable.advperm_enable": {"product_id": "aitable", "rpc_name": "advperm_enable"},
}, nil
}
servers := []syncdata.ServerInfo{
{ID: "aitable", Endpoint: "https://aitable.example"},
{ID: "aitable-helper", Endpoint: "https://helper.example"},
}
lister := &fakeLister{
results: map[string]transport.ToolsListResult{
// 同名巧合:aitable server 上恰好也有 advperm_enable。
"https://aitable.example": {Tools: []transport.ToolDescriptor{
{Name: "advperm_enable", Title: "coincidence title", Description: "coincidence desc"},
}},
"https://helper.example": {Tools: []transport.ToolDescriptor{
{Name: "set_advanced_permission", Title: "owner title", Description: "owner desc"},
}},
},
}
stubDeps(t, "env-token", nil, servers, lister, registry)
output := filepath.Join(t.TempDir(), "snapshot.json")
prev := `{"tools":{"aitable.advperm_enable":{"title":"stale","interface_ref":{"product_id":"aitable-helper","rpc_name":"set_advanced_permission"}}}}`
if err := os.WriteFile(output, []byte(prev), 0o600); err != nil {
t.Fatal(err)
}
var stderr bytes.Buffer
if code := run([]string{"--output", output}, &stderr); code != 0 {
t.Fatalf("run() = %d, stderr=%s", code, stderr.String())
}
data, err := os.ReadFile(output)
if err != nil {
t.Fatal(err)
}
var snapshot struct {
Tools map[string]map[string]any `json:"tools"`
}
if err := json.Unmarshal(data, &snapshot); err != nil {
t.Fatal(err)
}
entry := snapshot.Tools["aitable.advperm_enable"]
if entry["title"] != "owner title" || entry["description"] != "owner desc" {
t.Fatalf("entry = %#v, want reviewed-identity source to win over name coincidence", entry)
}
}
func TestMergeLiveMCPToolRefreshesExistingMetadata(t *testing.T) {
const canonical = "calendar.list_calendars"
reviewedRef := map[string]any{
"product_id": "calendar-helper",
"rpc_name": "list_user_calendars",
}
allTools := map[string]map[string]any{
canonical: {
"title": "old title",
"description": "old description",
"interface_ref": reviewedRef,
"parameters": map[string]any{
"stale": map[string]any{"type": "string"},
},
},
}
live := transport.ToolDescriptor{
Name: "list_calendars",
Title: "new title",
Description: "new description",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"cursor": map[string]any{
"type": "string",
"description": "next page cursor",
},
},
"required": []any{"cursor"},
},
}
fallbackRef := map[string]string{
"product_id": "calendar",
"rpc_name": "list_calendars",
}
mergeLiveMCPTool(allTools, canonical, live, fallbackRef)
got := allTools[canonical]
if got["title"] != "new title" || got["description"] != "new description" {
t.Fatalf("live metadata was not refreshed: %#v", got)
}
if !reflect.DeepEqual(got["interface_ref"], reviewedRef) {
t.Fatalf("interface_ref = %#v, want reviewed mapping %#v", got["interface_ref"], reviewedRef)
}
params, ok := got["parameters"].(map[string]map[string]any)
if !ok {
t.Fatalf("parameters type = %T, want refreshed parameter map", got["parameters"])
}
if _, stale := params["stale"]; stale {
t.Fatalf("stale parameter survived refresh: %#v", params)
}
if cursor := params["cursor"]; cursor["type"] != "string" || cursor["description"] != "next page cursor" || cursor["required"] != true {
t.Fatalf("cursor parameter = %#v", cursor)
}
}
func TestBuildCoverageReportsFailedServices(t *testing.T) {
got := buildCoverage(26, []string{"doc", "sheet"}, 800, 813, 40)
if got["source_services"] != 26 {
t.Fatalf("source_services = %v, want 26", got["source_services"])
}
if got["snapshot_services"] != 24 {
t.Fatalf("snapshot_services = %v, want 24 (26 sources - 2 failures)", got["snapshot_services"])
}
if !reflect.DeepEqual(got["missing_services"], []string{"doc", "sheet"}) {
t.Fatalf("missing_services = %#v, want failed service IDs", got["missing_services"])
}
// matched 必须剔除 stub 占位,unmatched 据实等于 stub 数。
if got["matched_tools"] != 773 || got["unmatched_tools"] != 40 {
t.Fatalf("matched/unmatched = %v/%v, want 773/40 (813 surface - 40 stubs)", got["matched_tools"], got["unmatched_tools"])
}
if got["source_tools"] != 800 || got["surface_tools"] != 813 {
t.Fatalf("tool counts = %#v", got)
}
}
func TestBuildCoverageFullSnapshotHasNoMissingServices(t *testing.T) {
got := buildCoverage(26, nil, 813, 813, 0)
if got["snapshot_services"] != 26 {
t.Fatalf("snapshot_services = %v, want 26", got["snapshot_services"])
}
if !reflect.DeepEqual(got["missing_services"], []string{}) {
t.Fatalf("missing_services = %#v, want empty non-nil slice", got["missing_services"])
}
if got["matched_tools"] != 813 || got["unmatched_tools"] != 0 {
t.Fatalf("matched/unmatched = %v/%v, want 813/0 for stub-free snapshot", got["matched_tools"], got["unmatched_tools"])
}
}
// fakeLister returns canned tools/list results per endpoint.
type fakeLister struct {
results map[string]transport.ToolsListResult
errs map[string]error
}
func (f *fakeLister) ListTools(_ context.Context, endpoint string) (transport.ToolsListResult, error) {
if err := f.errs[endpoint]; err != nil {
return transport.ToolsListResult{}, err
}
return f.results[endpoint], nil
}
// stubDeps swaps every injection point for the duration of one test.
func stubDeps(t *testing.T, token string, keychain func() (*auth.TokenData, error), servers []syncdata.ServerInfo, lister toolLister, registry func() (map[string]map[string]string, error)) {
t.Helper()
origGetenv, origLoad, origServers, origNew, origRegistry := getenv, loadTokenData, staticServers, newToolLister, registrySource
t.Cleanup(func() {
getenv, loadTokenData, staticServers, newToolLister, registrySource = origGetenv, origLoad, origServers, origNew, origRegistry
})
getenv = func(key string) string {
if key == "DWS_ACCESS_TOKEN" {
return token
}
return ""
}
loadTokenData = keychain
staticServers = func() []syncdata.ServerInfo { return servers }
newToolLister = func(string) toolLister { return lister }
registrySource = registry
}
func testRegistryRefs() (map[string]map[string]string, error) {
return map[string]map[string]string{
"doc.copy_document": {"product_id": "doc", "rpc_name": "copy_document"},
"doc.get_document": {"product_id": "doc", "rpc_name": "get_document"},
}, nil
}
func TestCrossPlatformCoverageRunRefusesRetiredPinnedMCPMetadataPath(t *testing.T) {
stubDeps(t, "env-token", nil, nil, &fakeLister{}, testRegistryRefs)
var stderr bytes.Buffer
code := run([]string{"--output", "internal/cli/schema_mcp_metadata.json"}, &stderr)
if code != 2 {
t.Fatalf("run(retired pin) = %d, want 2", code)
}
if !strings.Contains(stderr.String(), "refusing to write retired Schema pin") {
t.Fatalf("stderr = %q, want retired-pin refusal", stderr.String())
}
if !retiredPinnedMCPMetadataPath("internal/cli/schema_mcp_metadata.json") ||
!retiredPinnedMCPMetadataPath("/tmp/repo/internal/cli/schema_mcp_metadata.json") ||
retiredPinnedMCPMetadataPath("artifacts/mcp_metadata_diagnostic.json") {
t.Fatal("retiredPinnedMCPMetadataPath classification is incorrect")
}
}
func TestRunNoTokenFails(t *testing.T) {
stubDeps(t, "", func() (*auth.TokenData, error) { return nil, errors.New("no keychain") }, nil, &fakeLister{}, testRegistryRefs)
var stderr bytes.Buffer
if code := run(nil, &stderr); code != 1 {
t.Fatalf("run() = %d, want 1", code)
}
if !strings.Contains(stderr.String(), "no auth token") {
t.Fatalf("stderr = %q, want no-auth-token hint", stderr.String())
}
}
func TestRunInvalidFlagFails(t *testing.T) {
stubDeps(t, "tok", nil, nil, &fakeLister{}, testRegistryRefs)
var stderr bytes.Buffer
if code := run([]string{"--nonexistent"}, &stderr); code != 2 {
t.Fatalf("run() = %d, want 2", code)
}
}
func TestResolveTokenKeychainFallback(t *testing.T) {
stubDeps(t, "", func() (*auth.TokenData, error) {
return &auth.TokenData{AccessToken: "kc-token"}, nil
}, nil, &fakeLister{}, testRegistryRefs)
var stderr bytes.Buffer
if got := resolveToken(&stderr); got != "kc-token" {
t.Fatalf("resolveToken() = %q, want kc-token", got)
}
if !strings.Contains(stderr.String(), "loaded token from keychain") {
t.Fatalf("stderr = %q, want keychain log", stderr.String())
}
}
func TestResolveTokenEmptyKeychainToken(t *testing.T) {
stubDeps(t, "", func() (*auth.TokenData, error) { return &auth.TokenData{}, nil }, nil, &fakeLister{}, testRegistryRefs)
var stderr bytes.Buffer
if got := resolveToken(&stderr); got != "" {
t.Fatalf("resolveToken() = %q, want empty", got)
}
}
func TestCrossPlatformCoverageRunWritesSnapshotWithHonestCoverage(t *testing.T) {
servers := []syncdata.ServerInfo{
{ID: "doc", Endpoint: "https://doc.example"},
{ID: "sheet", Endpoint: "https://sheet.example"},
{ID: "blank", Endpoint: " "},
}
lister := &fakeLister{
results: map[string]transport.ToolsListResult{
"https://doc.example": {Tools: []transport.ToolDescriptor{
{Name: "copy_document", Title: "复制文档", Description: "copy", InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"doc_id": map[string]any{"type": "string", "description": "文档 ID", "default": "d", "enum": []any{"a", "b", 3}},
"bogus": "not-a-map",
},
"required": []any{"doc_id", 42},
}},
{Name: " "},
{Name: "not_in_registry"},
}},
},
errs: map[string]error{"https://sheet.example": errors.New("boom")},
}
stubDeps(t, "env-token", nil, servers, lister, testRegistryRefs)
dir := t.TempDir()
output := filepath.Join(dir, "snapshot.json")
prev := `{"tools":{"doc.get_document":{"interface_ref":{"product_id":"doc-helper","rpc_name":"fetch_document"}}}}`
if err := os.WriteFile(output, []byte(prev), 0o600); err != nil {
t.Fatal(err)
}
var stderr bytes.Buffer
if code := run([]string{"--output", output}, &stderr); code != 0 {
t.Fatalf("run() = %d, stderr=%s", code, stderr.String())
}
data, err := os.ReadFile(output)
if err != nil {
t.Fatal(err)
}
var snapshot struct {
Version int `json:"version"`
Coverage map[string]any `json:"coverage"`
Tools map[string]map[string]any
}
if err := json.Unmarshal(data, &snapshot); err != nil {
t.Fatal(err)
}
if snapshot.Version != 1 {
t.Fatalf("version = %d", snapshot.Version)
}
if got := snapshot.Coverage["snapshot_services"].(float64); got != 2 {
t.Fatalf("snapshot_services = %v, want 2 (3 servers - 1 failed; blank endpoint not counted as failed)", got)
}
if got := snapshot.Coverage["missing_services"].([]any); len(got) != 1 || got[0] != "sheet" {
t.Fatalf("missing_services = %v, want [sheet]", got)
}
live := snapshot.Tools["doc.copy_document"]
if live == nil || live["title"] != "复制文档" {
t.Fatalf("doc.copy_document = %#v, want live metadata", live)
}
params := live["parameters"].(map[string]any)
docID := params["doc_id"].(map[string]any)
if docID["type"] != "string" || docID["required"] != true || docID["default"] != "d" {
t.Fatalf("doc_id = %#v", docID)
}
if enum := docID["enum"].([]any); len(enum) != 2 {
t.Fatalf("enum = %v, want the 2 string members only", enum)
}
if _, ok := params["bogus"]; ok {
t.Fatal("non-map property should be skipped")
}
prevRef := snapshot.Tools["doc.get_document"]["interface_ref"].(map[string]any)
if prevRef["product_id"] != "doc-helper" {
t.Fatalf("previous reviewed ref lost: %#v", prevRef)
}
if _, ok := snapshot.Tools["not_in_registry"]; ok {
t.Fatal("tools outside the registry must be dropped")
}
if !strings.Contains(stderr.String(), "services unreachable: sheet") {
t.Fatalf("stderr = %q, want unreachable log", stderr.String())
}
}
func TestRunIgnoresCorruptPreviousSnapshot(t *testing.T) {
stubDeps(t, "env-token", nil, nil, &fakeLister{}, testRegistryRefs)
output := filepath.Join(t.TempDir(), "snapshot.json")
if err := os.WriteFile(output, []byte("{corrupt"), 0o600); err != nil {
t.Fatal(err)
}
var stderr bytes.Buffer
if code := run([]string{"--output", output}, &stderr); code != 0 {
t.Fatalf("run() = %d, stderr=%s", code, stderr.String())
}
}
func TestCrossPlatformCoverageRunRegistryLoadFailureStillWritesStublessSnapshot(t *testing.T) {
stubDeps(t, "env-token", nil, nil, &fakeLister{}, func() (map[string]map[string]string, error) { return nil, errors.New("no identity") })
output := filepath.Join(t.TempDir(), "snapshot.json")
var stderr bytes.Buffer
if code := run([]string{"--output", output}, &stderr); code != 0 {
t.Fatalf("run() = %d, stderr=%s", code, stderr.String())
}
if !strings.Contains(stderr.String(), "cannot collect command identity") {
t.Fatalf("stderr = %q, want identity collection warning", stderr.String())
}
}
func TestRunWriteFailure(t *testing.T) {
stubDeps(t, "env-token", nil, nil, &fakeLister{}, testRegistryRefs)
var stderr bytes.Buffer
badPath := filepath.Join(t.TempDir(), "missing-dir", "snapshot.json")
if code := run([]string{"--output", badPath}, &stderr); code != 1 {
t.Fatalf("run() = %d, want 1 on write failure", code)
}
}
func TestWriteMetadataMarshalFailure(t *testing.T) {
err := writeMetadata(filepath.Join(t.TempDir(), "out.json"), map[string]any{"bad": math.NaN()})
if err == nil || !strings.Contains(err.Error(), "marshal failed") {
t.Fatalf("err = %v, want marshal failure", err)
}
}
func TestMainDelegatesToRun(t *testing.T) {
stubDeps(t, "env-token", nil, nil, &fakeLister{}, testRegistryRefs)
origExit, origArgs := osExit, os.Args
t.Cleanup(func() { osExit, os.Args = origExit, origArgs })
exitCode := -1
osExit = func(code int) { exitCode = code }
os.Args = []string{"fetch_mcp_metadata", "--output", filepath.Join(t.TempDir(), "snapshot.json")}
main()
if exitCode != 0 {
t.Fatalf("main() exited with %d, want 0", exitCode)
}
}
func TestExtractParamsNilAndNonObjectSchemas(t *testing.T) {
if got := extractParams(nil); got != nil {
t.Fatalf("extractParams(nil) = %v, want nil", got)
}
if got := extractParams(map[string]any{"type": "object"}); got != nil {
t.Fatalf("extractParams(no properties) = %v, want nil", got)
}
}
func TestCrossPlatformCoverageNewToolListerBuildsAuthedClient(t *testing.T) {
if lister := newToolLister("tok"); lister == nil {
t.Fatal("newToolLister returned nil")
}
}
func TestRunRecordsSourceRevision(t *testing.T) {
stubDeps(t, "env-token", nil, nil, &fakeLister{}, testRegistryRefs)
dir := t.TempDir()
head := filepath.Join(dir, "HEAD")
if err := os.WriteFile(head, []byte("ref: refs/heads/feature\n"), 0o600); err != nil {
t.Fatal(err)
}
origHead := gitHeadPath
t.Cleanup(func() { gitHeadPath = origHead })
gitHeadPath = head
output := filepath.Join(dir, "snapshot.json")
var stderr bytes.Buffer
if code := run([]string{"--output", output}, &stderr); code != 0 {
t.Fatalf("run() = %d, stderr=%s", code, stderr.String())
}
data, err := os.ReadFile(output)
if err != nil {
t.Fatal(err)
}
var snapshot struct {
SourceRevision string `json:"source_revision"`
}
if err := json.Unmarshal(data, &snapshot); err != nil {
t.Fatal(err)
}
if snapshot.SourceRevision != "ref: refs/heads/feature" {
t.Fatalf("source_revision = %q", snapshot.SourceRevision)
}
}
+340
View File
@@ -0,0 +1,340 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// interface-snapshot is an internal CI helper. It is intentionally a separate
// binary so it can be copied into a temporary worktree and compiled against an
// older revision's real Cobra root.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/i18n"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/interfacesnapshot"
"github.com/spf13/cobra"
)
var newRootCommand = func() *cobra.Command { return app.NewRootCommand() }
func main() {
os.Exit(run(os.Args[1:], os.Stdout, os.Stderr))
}
func run(args []string, stdout, stderr io.Writer) int {
if len(args) == 0 {
printUsage(stderr)
return 2
}
switch args[0] {
case "generate":
if err := runGenerate(args[1:], stdout, stderr); err != nil {
fmt.Fprintln(stderr, err)
return 2
}
return 0
case "compare":
compatible, err := runCompare(args[1:], stdout, stderr)
if err != nil {
fmt.Fprintln(stderr, err)
return 2
}
if !compatible {
return 1
}
return 0
default:
fmt.Fprintf(stderr, "unknown command %q\n", args[0])
printUsage(stderr)
return 2
}
}
func runGenerate(args []string, stdout, stderr io.Writer) error {
flags := flag.NewFlagSet("generate", flag.ContinueOnError)
flags.SetOutput(stderr)
output := flags.String("output", "-", "snapshot output path, or - for stdout")
if err := flags.Parse(args); err != nil {
return err
}
if flags.NArg() != 0 {
return fmt.Errorf("generate accepts no positional arguments")
}
home, err := os.MkdirTemp("", "dws-interface-snapshot-*")
if err != nil {
return fmt.Errorf("create isolated home: %w", err)
}
defer os.RemoveAll(home)
environment := map[string]string{
"DWS_CONFIG_DIR": home,
"DWS_LANG": "en",
"HOME": home,
"NO_COLOR": "1",
"USERPROFILE": home,
}
type previousEnv struct {
value string
set bool
}
previous := make(map[string]previousEnv, len(environment))
for key, value := range environment {
oldValue, wasSet := os.LookupEnv(key)
previous[key] = previousEnv{value: oldValue, set: wasSet}
if err := os.Setenv(key, value); err != nil {
return fmt.Errorf("set %s: %w", key, err)
}
}
defer func() {
for key, old := range previous {
if old.set {
_ = os.Setenv(key, old.value)
} else {
_ = os.Unsetenv(key)
}
}
}()
previousLang := i18n.Lang()
defer i18n.SetLang(previousLang)
i18n.SetLang("en")
root := newRootCommand()
snapshot := interfacesnapshot.Capture(root)
if err := validateHelpRendering(root, snapshot); err != nil {
return err
}
if *output == "-" {
return interfacesnapshot.Write(stdout, snapshot)
}
file, err := os.Create(filepath.Clean(*output))
if err != nil {
return fmt.Errorf("create snapshot %q: %w", *output, err)
}
writeErr := interfacesnapshot.Write(file, snapshot)
closeErr := file.Close()
if writeErr != nil {
return fmt.Errorf("write snapshot %q: %w", *output, writeErr)
}
if closeErr != nil {
return fmt.Errorf("close snapshot %q: %w", *output, closeErr)
}
return nil
}
func runCompare(args []string, stdout, stderr io.Writer) (bool, error) {
flags := flag.NewFlagSet("compare", flag.ContinueOnError)
flags.SetOutput(stderr)
currentPath := flags.String("current", "", "candidate snapshot path")
basePath := flags.String("base", "", "target main/development baseline snapshot path")
stablePath := flags.String("stable", "", "latest stable GA snapshot path")
approvedMigrationsPath := flags.String(
"approved-flag-migrations",
"",
"merge-base-owned approved flag migration manifest",
)
candidateMigrationsPath := flags.String(
"candidate-flag-migrations",
"",
"candidate flag migration manifest",
)
approvedCommandMigrationsPath := flags.String(
"approved-command-migrations",
"",
"merge-base-owned approved command migration manifest",
)
candidateCommandMigrationsPath := flags.String(
"candidate-command-migrations",
"",
"candidate command migration manifest",
)
if err := flags.Parse(args); err != nil {
return false, err
}
if flags.NArg() != 0 {
return false, fmt.Errorf("compare accepts no positional arguments")
}
if *currentPath == "" {
return false, fmt.Errorf("compare requires --current")
}
if *basePath == "" && *stablePath == "" {
return false, fmt.Errorf("compare requires --base, --stable, or both")
}
if (*approvedMigrationsPath == "") != (*candidateMigrationsPath == "") {
return false, fmt.Errorf(
"--approved-flag-migrations and --candidate-flag-migrations must be provided together",
)
}
if (*approvedCommandMigrationsPath == "") != (*candidateCommandMigrationsPath == "") {
return false, fmt.Errorf(
"--approved-command-migrations and --candidate-command-migrations must be provided together",
)
}
if (*approvedMigrationsPath != "" || *approvedCommandMigrationsPath != "") && (*basePath == "" || *stablePath == "") {
return false, fmt.Errorf("migration compare requires both --base and --stable")
}
current, err := readSnapshot(*currentPath)
if err != nil {
return false, fmt.Errorf("read current snapshot: %w", err)
}
references := make(map[string]interfacesnapshot.Snapshot, 2)
if *basePath != "" {
references["main"], err = readSnapshot(*basePath)
if err != nil {
return false, fmt.Errorf("read main/development baseline snapshot: %w", err)
}
}
if *stablePath != "" {
references["stable"], err = readSnapshot(*stablePath)
if err != nil {
return false, fmt.Errorf("read stable snapshot: %w", err)
}
}
report := interfacesnapshot.CompareAll(current, references)
if *approvedCommandMigrationsPath != "" {
flagApproved := interfacesnapshot.FlagMigrationManifest{Version: interfacesnapshot.FlagMigrationManifestVersion, Migrations: []interfacesnapshot.FlagMigration{}}
flagCandidate := flagApproved
if *approvedMigrationsPath != "" {
flagApproved, err = readFlagMigrationManifest(*approvedMigrationsPath)
if err != nil {
return false, fmt.Errorf("read approved flag migrations: %w", err)
}
flagCandidate, err = readFlagMigrationManifest(*candidateMigrationsPath)
if err != nil {
return false, fmt.Errorf("read candidate flag migrations: %w", err)
}
}
commandApproved, readErr := readCommandMigrationManifest(*approvedCommandMigrationsPath)
if readErr != nil {
return false, fmt.Errorf("read approved command migrations: %w", readErr)
}
commandCandidate, readErr := readCommandMigrationManifest(*candidateCommandMigrationsPath)
if readErr != nil {
return false, fmt.Errorf("read candidate command migrations: %w", readErr)
}
report, err = interfacesnapshot.CompareAllWithInterfaceMigrations(
current,
references,
flagApproved,
flagCandidate,
commandApproved,
commandCandidate,
)
if err != nil {
return false, fmt.Errorf("validate interface migration lifecycle: %w", err)
}
} else if *approvedMigrationsPath != "" {
approved, readErr := readFlagMigrationManifest(*approvedMigrationsPath)
if readErr != nil {
return false, fmt.Errorf("read approved flag migrations: %w", readErr)
}
candidate, readErr := readFlagMigrationManifest(*candidateMigrationsPath)
if readErr != nil {
return false, fmt.Errorf("read candidate flag migrations: %w", readErr)
}
report, err = interfacesnapshot.CompareAllWithFlagMigrations(
current,
references,
approved,
candidate,
)
if err != nil {
return false, fmt.Errorf("validate flag migration lifecycle: %w", err)
}
}
encoder := json.NewEncoder(stdout)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ")
if err := encoder.Encode(report); err != nil {
return false, fmt.Errorf("write comparison report: %w", err)
}
return report.Compatible, nil
}
func readFlagMigrationManifest(path string) (interfacesnapshot.FlagMigrationManifest, error) {
file, err := os.Open(filepath.Clean(path))
if err != nil {
return interfacesnapshot.FlagMigrationManifest{}, err
}
defer file.Close()
return interfacesnapshot.ReadFlagMigrationManifest(file)
}
func readCommandMigrationManifest(path string) (interfacesnapshot.CommandMigrationManifest, error) {
file, err := os.Open(filepath.Clean(path))
if err != nil {
return interfacesnapshot.CommandMigrationManifest{}, err
}
defer file.Close()
return interfacesnapshot.ReadCommandMigrationManifest(file)
}
func validateHelpRendering(root *cobra.Command, snapshot interfacesnapshot.Snapshot) error {
for _, command := range snapshot.Commands {
path := strings.TrimPrefix(command.Path, "dws")
resolved, remaining, err := root.Find(strings.Fields(path))
if err != nil || len(remaining) != 0 || resolved == nil {
return fmt.Errorf("resolve %q before help rendering: remaining=%v error=%v", command.Path, remaining, err)
}
if err := renderCommandHelp(resolved); err != nil {
return fmt.Errorf("render %q help: %w", command.Path, err)
}
}
return nil
}
func renderCommandHelp(command *cobra.Command) (err error) {
var stdout, stderr bytes.Buffer
command.InitDefaultHelpFlag()
command.SetOut(&stdout)
command.SetErr(&stderr)
defer func() {
if recovered := recover(); recovered != nil {
err = fmt.Errorf("help renderer panicked: %v", recovered)
}
}()
command.HelpFunc()(command, []string{})
if stderr.Len() > 0 {
return fmt.Errorf("help renderer wrote an error: %s", strings.TrimSpace(stderr.String()))
}
if stdout.Len() == 0 {
return fmt.Errorf("help renderer produced empty output")
}
return nil
}
func readSnapshot(path string) (interfacesnapshot.Snapshot, error) {
file, err := os.Open(filepath.Clean(path))
if err != nil {
return interfacesnapshot.Snapshot{}, err
}
defer file.Close()
return interfacesnapshot.Read(file)
}
func printUsage(w io.Writer) {
fmt.Fprintln(w, "usage:")
fmt.Fprintln(w, " interface-snapshot generate [--output FILE]")
fmt.Fprintln(w, " interface-snapshot compare --current FILE [--base FILE] [--stable FILE] [--approved-flag-migrations FILE --candidate-flag-migrations FILE] [--approved-command-migrations FILE --candidate-command-migrations FILE]")
}
+700
View File
@@ -0,0 +1,700 @@
// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bytes"
"errors"
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/interfacesnapshot"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam"
"github.com/spf13/cobra"
)
func TestCrossPlatformCoverageRunGenerateCapturesActualRootOffline(t *testing.T) {
var stdout, stderr bytes.Buffer
if exitCode := run([]string{"generate"}, &stdout, &stderr); exitCode != 0 {
t.Fatalf("run(generate) exit=%d stderr=%s", exitCode, stderr.String())
}
snapshot, err := interfacesnapshot.Read(bytes.NewReader(stdout.Bytes()))
if err != nil {
t.Fatalf("decode generated snapshot: %v", err)
}
commands := make(map[string]interfacesnapshot.Command, len(snapshot.Commands))
for _, command := range snapshot.Commands {
commands[command.Path] = command
}
for _, path := range []string{"dws", "dws chat", "dws dev app create"} {
if _, ok := commands[path]; !ok {
t.Errorf("actual root snapshot is missing %q", path)
}
}
for _, path := range []string{"dws completion", "dws help"} {
if _, ok := commands[path]; ok {
t.Errorf("framework-noise path %q leaked into snapshot", path)
}
}
create := commands["dws dev app create"]
if !hasFlag(create.LocalFlags, "name", "string") {
t.Errorf("dev app create local flags do not contain --name string: %#v", create.LocalFlags)
}
if !hasFlag(create.InheritedFlags, "profile", "string") {
t.Errorf("dev app create inherited flags do not contain --profile string: %#v", create.InheritedFlags)
}
}
func TestCrossPlatformCoverageRunGenerateRejectsHelpRenderingFailure(t *testing.T) {
testseam.Swap(t, &newRootCommand, func() *cobra.Command {
root := &cobra.Command{Use: "dws"}
root.SetHelpFunc(func(command *cobra.Command, _ []string) {
_, _ = io.WriteString(command.ErrOrStderr(), "injected help failure")
})
return root
})
var stdout, stderr bytes.Buffer
if exitCode := run([]string{"generate"}, &stdout, &stderr); exitCode != 2 {
t.Fatalf("run(generate) exit=%d stderr=%s", exitCode, stderr.String())
}
if !strings.Contains(stderr.String(), "injected help failure") {
t.Fatalf("run(generate) stderr=%q", stderr.String())
}
}
func TestCrossPlatformCoverageRunCompareUsesBothSnapshotInputsAndExitCode(t *testing.T) {
current := commandSnapshot("dws")
mergeBase := commandSnapshot("dws")
stable := commandSnapshot("dws", "dws legacy")
dir := t.TempDir()
currentPath := writeSnapshot(t, dir, "current.json", current)
mergeBasePath := writeSnapshot(t, dir, "base.json", mergeBase)
stablePath := writeSnapshot(t, dir, "stable.json", stable)
var stdout, stderr bytes.Buffer
exitCode := run([]string{
"compare",
"--current", currentPath,
"--base", mergeBasePath,
"--stable", stablePath,
}, &stdout, &stderr)
if exitCode != 1 {
t.Fatalf("run(compare) exit=%d, want 1; stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
}
if !bytes.Contains(stdout.Bytes(), []byte(`"reference": "main"`)) ||
!bytes.Contains(stdout.Bytes(), []byte(`"reference": "stable"`)) ||
!bytes.Contains(stdout.Bytes(), []byte(`"kind": "command_removed"`)) {
t.Fatalf("comparison report does not contain both references and the blocking change:\n%s", stdout.String())
}
}
func TestCrossPlatformCoverageRunCompareEnforcesBaseOwnedFlagMigrationLifecycle(t *testing.T) {
dir := t.TempDir()
before := flagMigrationSnapshot(false)
after := flagMigrationSnapshot(true)
currentPath := writeSnapshot(t, dir, "current.json", after)
basePath := writeSnapshot(t, dir, "base.json", before)
stablePath := writeSnapshot(t, dir, "stable.json", before)
approvedPath := writeManifest(t, dir, "approved.json", flagMigrationManifestJSON("pending"))
candidatePath := writeManifest(t, dir, "candidate.json", flagMigrationManifestJSON("consumed"))
var stdout, stderr bytes.Buffer
exitCode := run([]string{
"compare",
"--current", currentPath,
"--base", basePath,
"--stable", stablePath,
"--approved-flag-migrations", approvedPath,
"--candidate-flag-migrations", candidatePath,
}, &stdout, &stderr)
if exitCode != 0 {
t.Fatalf("exact base-owned migration exit=%d stderr=%s", exitCode, stderr.String())
}
if !bytes.Contains(stdout.Bytes(), []byte(`"compatible": true`)) {
t.Fatalf("exact migration report is not compatible:\n%s", stdout.String())
}
stdout.Reset()
stderr.Reset()
emptyApproved := writeManifest(t, dir, "empty-approved.json", `{"version":1,"migrations":[]}`)
exitCode = run([]string{
"compare",
"--current", currentPath,
"--base", basePath,
"--stable", stablePath,
"--approved-flag-migrations", emptyApproved,
"--candidate-flag-migrations", candidatePath,
}, &stdout, &stderr)
if exitCode != 2 || !strings.Contains(stderr.String(), "must start pending") {
t.Fatalf("candidate self-approval exit=%d stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
}
}
func TestCrossPlatformCoverageRunCompareRequiresBothFlagMigrationInputs(t *testing.T) {
dir := t.TempDir()
currentPath := writeSnapshot(t, dir, "current.json", commandSnapshot("dws"))
basePath := writeSnapshot(t, dir, "base.json", commandSnapshot("dws"))
approvedPath := writeManifest(t, dir, "approved.json", `{"version":1,"migrations":[]}`)
var stdout, stderr bytes.Buffer
exitCode := run([]string{
"compare",
"--current", currentPath,
"--base", basePath,
"--approved-flag-migrations", approvedPath,
}, &stdout, &stderr)
if exitCode != 2 || !strings.Contains(stderr.String(), "must be provided together") {
t.Fatalf("one-sided migration input exit=%d stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
}
}
func TestCrossPlatformCoverageRunCompareCommandMigrationInputs(t *testing.T) {
dir := t.TempDir()
snapshotPath := writeSnapshot(t, dir, "snapshot.json", commandSnapshot("dws"))
emptyFlag := writeManifest(t, dir, "empty-flags.json", `{"version":1,"migrations":[]}`)
emptyCommand := writeManifest(t, dir, "empty-commands.json", `{"version":1,"migrations":[]}`)
invalid := writeManifest(t, dir, "invalid-commands.json", `{`)
var stdout, stderr bytes.Buffer
args := []string{
"compare",
"--current", snapshotPath,
"--base", snapshotPath,
"--stable", snapshotPath,
"--approved-flag-migrations", emptyFlag,
"--candidate-flag-migrations", emptyFlag,
"--approved-command-migrations", emptyCommand,
"--candidate-command-migrations", emptyCommand,
}
if exitCode := run(args, &stdout, &stderr); exitCode != 0 {
t.Fatalf("combined migration compare exit=%d stderr=%s", exitCode, stderr.String())
}
for _, test := range []struct {
name string
approved string
candidate string
want string
}{
{"approved flag", invalid, emptyFlag, "read approved flag migrations"},
{"candidate flag", emptyFlag, invalid, "read candidate flag migrations"},
} {
t.Run(test.name, func(t *testing.T) {
stdout.Reset()
stderr.Reset()
testArgs := []string{
"compare", "--current", snapshotPath, "--base", snapshotPath, "--stable", snapshotPath,
"--approved-flag-migrations", test.approved,
"--candidate-flag-migrations", test.candidate,
"--approved-command-migrations", emptyCommand,
"--candidate-command-migrations", emptyCommand,
}
if exitCode := run(testArgs, &stdout, &stderr); exitCode != 2 || !strings.Contains(stderr.String(), test.want) {
t.Fatalf("combined flag error exit=%d stderr=%s", exitCode, stderr.String())
}
})
}
for _, test := range []struct {
name string
approved string
candidate string
want string
}{
{"approved", invalid, emptyCommand, "read approved command migrations"},
{"candidate", emptyCommand, invalid, "read candidate command migrations"},
} {
t.Run(test.name, func(t *testing.T) {
stdout.Reset()
stderr.Reset()
testArgs := []string{
"compare", "--current", snapshotPath, "--base", snapshotPath, "--stable", snapshotPath,
"--approved-command-migrations", test.approved,
"--candidate-command-migrations", test.candidate,
}
if exitCode := run(testArgs, &stdout, &stderr); exitCode != 2 || !strings.Contains(stderr.String(), test.want) {
t.Fatalf("command manifest error exit=%d stderr=%s", exitCode, stderr.String())
}
})
}
stderr.Reset()
if exitCode := run([]string{
"compare", "--current", snapshotPath, "--base", snapshotPath,
"--approved-command-migrations", emptyCommand,
}, &stdout, &stderr); exitCode != 2 || !strings.Contains(stderr.String(), "provided together") {
t.Fatalf("one-sided command manifest exit=%d stderr=%s", exitCode, stderr.String())
}
if _, err := readCommandMigrationManifest(filepath.Join(dir, "missing.json")); err == nil {
t.Fatal("missing command migration manifest unexpectedly read")
}
if _, err := readCommandMigrationManifest(invalid); err == nil {
t.Fatal("invalid command migration manifest unexpectedly read")
}
pending := writeManifest(t, dir, "pending-command.json", commandMigrationManifestJSON("pending"))
consumed := writeManifest(t, dir, "consumed-command.json", commandMigrationManifestJSON("consumed"))
stderr.Reset()
if exitCode := run([]string{
"compare", "--current", snapshotPath, "--base", snapshotPath, "--stable", snapshotPath,
"--approved-command-migrations", pending,
"--candidate-command-migrations", consumed,
}, &stdout, &stderr); exitCode != 2 || !strings.Contains(stderr.String(), "validate interface migration lifecycle") {
t.Fatalf("command lifecycle error exit=%d stderr=%s", exitCode, stderr.String())
}
}
func TestCrossPlatformCoverageRunCompareRequiresBothReferencesForFlagMigrations(t *testing.T) {
dir := t.TempDir()
currentPath := writeSnapshot(t, dir, "current.json", commandSnapshot("dws"))
basePath := writeSnapshot(t, dir, "base.json", commandSnapshot("dws"))
stablePath := writeSnapshot(t, dir, "stable.json", commandSnapshot("dws"))
approvedPath := writeManifest(t, dir, "approved.json", `{"version":1,"migrations":[]}`)
candidatePath := writeManifest(t, dir, "candidate.json", `{"version":1,"migrations":[]}`)
tests := []struct {
name string
args []string
}{
{
name: "missing stable",
args: []string{"--base", basePath},
},
{
name: "missing base",
args: []string{"--stable", stablePath},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
args := []string{"compare", "--current", currentPath}
args = append(args, test.args...)
args = append(args,
"--approved-flag-migrations", approvedPath,
"--candidate-flag-migrations", candidatePath,
)
var stdout, stderr bytes.Buffer
exitCode := run(args, &stdout, &stderr)
if exitCode != 2 || !strings.Contains(stderr.String(), "requires both --base and --stable") {
t.Fatalf("one-reference migration compare exit=%d stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
}
})
}
}
func TestCrossPlatformCoverageRunPrintsUsageForMissingAndUnknownCommands(t *testing.T) {
tests := []struct {
name string
args []string
wantStderr []string
}{
{
name: "missing command",
args: nil,
wantStderr: []string{"usage:", "interface-snapshot generate", "--approved-flag-migrations"},
},
{
name: "unknown command",
args: []string{"unknown"},
wantStderr: []string{`unknown command "unknown"`, "usage:", "interface-snapshot compare"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var stdout, stderr bytes.Buffer
if exitCode := run(test.args, &stdout, &stderr); exitCode != 2 {
t.Fatalf("run(%v) exit=%d, want 2", test.args, exitCode)
}
if stdout.Len() != 0 {
t.Fatalf("run(%v) unexpectedly wrote stdout: %s", test.args, stdout.String())
}
for _, want := range test.wantStderr {
if !strings.Contains(stderr.String(), want) {
t.Errorf("run(%v) stderr missing %q:\n%s", test.args, want, stderr.String())
}
}
})
}
}
func TestCrossPlatformCoverageRunRejectsInvalidSubcommandArguments(t *testing.T) {
tests := []struct {
name string
args []string
want string
}{
{name: "generate unknown flag", args: []string{"generate", "--unknown"}, want: "flag provided but not defined"},
{name: "generate positional", args: []string{"generate", "unexpected"}, want: "generate accepts no positional arguments"},
{name: "compare unknown flag", args: []string{"compare", "--unknown"}, want: "flag provided but not defined"},
{name: "compare positional", args: []string{"compare", "unexpected"}, want: "compare accepts no positional arguments"},
{name: "compare missing current", args: []string{"compare", "--base", "base.json"}, want: "compare requires --current"},
{name: "compare missing reference", args: []string{"compare", "--current", "current.json"}, want: "compare requires --base, --stable, or both"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var stdout, stderr bytes.Buffer
if exitCode := run(test.args, &stdout, &stderr); exitCode != 2 {
t.Fatalf("run(%v) exit=%d, want 2", test.args, exitCode)
}
if !strings.Contains(stderr.String(), test.want) {
t.Fatalf("run(%v) stderr missing %q:\n%s", test.args, test.want, stderr.String())
}
})
}
}
func TestCrossPlatformCoverageRunGenerateRejectsUnsafeOutputPath(t *testing.T) {
outputDirectory := t.TempDir()
var stdout, stderr bytes.Buffer
exitCode := run([]string{"generate", "--output", outputDirectory}, &stdout, &stderr)
if exitCode != 2 || !strings.Contains(stderr.String(), "create snapshot") {
t.Fatalf("directory output exit=%d stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
}
}
func TestCrossPlatformCoverageRunGenerateReportsTemporaryDirectoryFailure(t *testing.T) {
missingTempRoot := filepath.Join(t.TempDir(), "missing")
for _, name := range []string{"TMPDIR", "TMP", "TEMP"} {
t.Setenv(name, missingTempRoot)
}
var stdout, stderr bytes.Buffer
exitCode := run([]string{"generate"}, &stdout, &stderr)
if exitCode != 2 || !strings.Contains(stderr.String(), "create isolated home") {
t.Fatalf("invalid temporary root exit=%d stdout=%s stderr=%s", exitCode, stdout.String(), stderr.String())
}
}
func TestCrossPlatformCoverageRunCompareReportsSnapshotReadFailures(t *testing.T) {
dir := t.TempDir()
validPath := writeSnapshot(t, dir, "valid.json", commandSnapshot("dws"))
missingPath := filepath.Join(dir, "missing.json")
tests := []struct {
name string
args []string
want string
}{
{
name: "current",
args: []string{"compare", "--current", missingPath, "--base", validPath},
want: "read current snapshot",
},
{
name: "main",
args: []string{"compare", "--current", validPath, "--base", missingPath},
want: "read main/development baseline snapshot",
},
{
name: "stable",
args: []string{"compare", "--current", validPath, "--stable", missingPath},
want: "read stable snapshot",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var stdout, stderr bytes.Buffer
if exitCode := run(test.args, &stdout, &stderr); exitCode != 2 {
t.Fatalf("run(compare) exit=%d, want 2", exitCode)
}
if !strings.Contains(stderr.String(), test.want) {
t.Fatalf("stderr missing %q:\n%s", test.want, stderr.String())
}
})
}
}
func TestCrossPlatformCoverageRunCompareReportsEachManifestReadFailure(t *testing.T) {
dir := t.TempDir()
snapshotPath := writeSnapshot(t, dir, "snapshot.json", commandSnapshot("dws"))
validManifest := writeManifest(t, dir, "valid-manifest.json", `{"version":1,"migrations":[]}`)
invalidManifest := writeManifest(t, dir, "invalid-manifest.json", `{`)
tests := []struct {
name string
approved string
candidate string
want string
}{
{
name: "approved manifest",
approved: invalidManifest,
candidate: validManifest,
want: "read approved flag migrations",
},
{
name: "candidate manifest",
approved: validManifest,
candidate: invalidManifest,
want: "read candidate flag migrations",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var stdout, stderr bytes.Buffer
exitCode := run([]string{
"compare",
"--current", snapshotPath,
"--base", snapshotPath,
"--stable", snapshotPath,
"--approved-flag-migrations", test.approved,
"--candidate-flag-migrations", test.candidate,
}, &stdout, &stderr)
if exitCode != 2 || !strings.Contains(stderr.String(), test.want) {
t.Fatalf("%s exit=%d stdout=%s stderr=%s", test.name, exitCode, stdout.String(), stderr.String())
}
})
}
}
func TestCrossPlatformCoverageRunCompareReportsOutputFailure(t *testing.T) {
dir := t.TempDir()
snapshotPath := writeSnapshot(t, dir, "snapshot.json", commandSnapshot("dws"))
var stderr bytes.Buffer
exitCode := run([]string{
"compare",
"--current", snapshotPath,
"--base", snapshotPath,
}, failingWriter{}, &stderr)
if exitCode != 2 || !strings.Contains(stderr.String(), "write comparison report") {
t.Fatalf("comparison output failure exit=%d stderr=%s", exitCode, stderr.String())
}
}
func TestCrossPlatformCoverageReadHelpersRejectMissingAndInvalidInputs(t *testing.T) {
dir := t.TempDir()
missingPath := filepath.Join(dir, "missing.json")
invalidPath := filepath.Join(dir, "invalid.json")
if err := os.WriteFile(invalidPath, []byte(`{`), 0o600); err != nil {
t.Fatalf("write invalid fixture: %v", err)
}
if _, err := readSnapshot(missingPath); err == nil {
t.Fatal("readSnapshot(missing) unexpectedly succeeded")
}
if _, err := readSnapshot(invalidPath); err == nil {
t.Fatal("readSnapshot(invalid) unexpectedly succeeded")
}
if _, err := readFlagMigrationManifest(missingPath); err == nil {
t.Fatal("readFlagMigrationManifest(missing) unexpectedly succeeded")
}
if _, err := readFlagMigrationManifest(invalidPath); err == nil {
t.Fatal("readFlagMigrationManifest(invalid) unexpectedly succeeded")
}
}
func TestCrossPlatformCoverageValidateHelpRenderingReportsResolveError(t *testing.T) {
root := &cobra.Command{Use: "dws"}
err := validateHelpRendering(root, commandSnapshot("dws missing"))
if err == nil || !strings.Contains(err.Error(), `resolve "dws missing" before help rendering`) {
t.Fatalf("validateHelpRendering resolve error = %v", err)
}
}
func TestCrossPlatformCoverageValidateHelpRenderingReportsTemplateError(t *testing.T) {
root := &cobra.Command{Use: "dws"}
root.SetHelpTemplate(`{{index .Commands 99}}`)
err := validateHelpRendering(root, commandSnapshot("dws"))
if err == nil || !strings.Contains(err.Error(), `render "dws" help`) {
t.Fatalf("validateHelpRendering template error = %v", err)
}
}
func TestCrossPlatformCoverageValidateHelpRenderingRecoversTemplatePanic(t *testing.T) {
root := &cobra.Command{Use: "dws"}
root.SetHelpTemplate("{{")
err := validateHelpRendering(root, commandSnapshot("dws"))
if err == nil || !strings.Contains(err.Error(), `render "dws" help`) {
t.Fatalf("validateHelpRendering template panic = %v", err)
}
}
func TestCrossPlatformCoverageValidateHelpRenderingRejectsCustomHelpStderr(t *testing.T) {
root := &cobra.Command{Use: "dws"}
root.SetHelpFunc(func(command *cobra.Command, _ []string) {
_, _ = io.WriteString(command.ErrOrStderr(), "injected help failure")
})
err := validateHelpRendering(root, commandSnapshot("dws"))
if err == nil || !strings.Contains(err.Error(), "injected help failure") {
t.Fatalf("validateHelpRendering custom stderr = %v", err)
}
}
func TestCrossPlatformCoverageValidateHelpRenderingRejectsEmptyOutput(t *testing.T) {
root := &cobra.Command{Use: "dws"}
root.SetHelpFunc(func(*cobra.Command, []string) {})
err := validateHelpRendering(root, commandSnapshot("dws"))
if err == nil || !strings.Contains(err.Error(), "empty") {
t.Fatalf("validateHelpRendering empty output = %v", err)
}
}
func TestCrossPlatformCoverageValidateHelpRenderingAcceptsNormalOutput(t *testing.T) {
root := &cobra.Command{Use: "dws", Short: "root command"}
if err := validateHelpRendering(root, commandSnapshot("dws")); err != nil {
t.Fatalf("validateHelpRendering normal output: %v", err)
}
}
type failingWriter struct{}
func (failingWriter) Write([]byte) (int, error) {
return 0, errors.New("injected write failure")
}
var _ io.Writer = failingWriter{}
func commandSnapshot(paths ...string) interfacesnapshot.Snapshot {
commands := make([]interfacesnapshot.Command, 0, len(paths))
for _, path := range paths {
commands = append(commands, interfacesnapshot.Command{
Path: path,
Aliases: []string{},
LocalFlags: []interfacesnapshot.Flag{},
InheritedFlags: []interfacesnapshot.Flag{},
})
}
return interfacesnapshot.Snapshot{
SchemaVersion: interfacesnapshot.SchemaVersion,
Rules: interfacesnapshot.Rules{
ExcludedCommandSubtrees: []string{},
ExcludedFlags: []string{},
},
Commands: commands,
}
}
func writeSnapshot(t *testing.T, dir, name string, snapshot interfacesnapshot.Snapshot) string {
t.Helper()
path := filepath.Join(dir, name)
file, err := os.Create(path)
if err != nil {
t.Fatalf("create %s: %v", path, err)
}
if err := interfacesnapshot.Write(file, snapshot); err != nil {
file.Close()
t.Fatalf("write %s: %v", path, err)
}
if err := file.Close(); err != nil {
t.Fatalf("close %s: %v", path, err)
}
return path
}
func writeManifest(t *testing.T, dir, name, contents string) string {
t.Helper()
path := filepath.Join(dir, name)
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
t.Fatalf("write %s: %v", path, err)
}
return path
}
func flagMigrationSnapshot(after bool) interfacesnapshot.Snapshot {
legacy := interfacesnapshot.Flag{
Name: "legacy-id",
Shorthand: "l",
Type: "string",
Default: "",
NoOpt: "auto",
Required: true,
}
flags := []interfacesnapshot.Flag{legacy}
if after {
legacy.Required = false
legacy.Hidden = true
legacy.AliasOf = "message-id"
flags = []interfacesnapshot.Flag{
legacy,
{Name: "message-id", Type: "string", Default: "", Required: true},
}
}
return interfacesnapshot.Snapshot{
SchemaVersion: interfacesnapshot.SchemaVersion,
Rules: interfacesnapshot.Rules{
ExcludedCommandSubtrees: []string{},
ExcludedFlags: []string{},
},
Commands: []interfacesnapshot.Command{
{Path: "dws", Runnable: true, Aliases: []string{}, LocalFlags: []interfacesnapshot.Flag{}, InheritedFlags: []interfacesnapshot.Flag{}},
{Path: "dws chat send", Runnable: true, Aliases: []string{}, LocalFlags: flags, InheritedFlags: []interfacesnapshot.Flag{}},
},
}
}
func flagMigrationManifestJSON(state string) string {
return strings.Replace(`{
"version": 1,
"migrations": [{
"command": "dws chat send",
"legacy": {
"name": "legacy-id",
"before": {"present": true, "type": "string", "required": true, "shorthand": "l", "no_opt": "auto", "scope": "local"},
"after": {"present": true, "type": "string", "hidden": true, "shorthand": "l", "no_opt": "auto", "scope": "local", "alias_of": "message-id"}
},
"canonical": {
"name": "message-id",
"before": {"present": false},
"after": {"present": true, "type": "string", "required": true, "scope": "local"}
},
"state": "STATE",
"reason": "reviewed exact migration"
}]
}`, "STATE", state, 1)
}
func commandMigrationManifestJSON(state string) string {
return strings.Replace(`{
"version": 1,
"migrations": [{
"kind": "command_move",
"legacy": {
"command": "dws chat message old",
"before": {"present": true, "runnable": true},
"after": {"present": true, "runnable": true, "hidden": true}
},
"replacement": {
"command": "dws chat topic new",
"before": {"present": false},
"after": {"present": true, "runnable": true}
},
"schema": {
"product_id": "chat",
"source_tool_id": "chat.move",
"replacement_tool_id": "chat.move",
"parameters": []
},
"state": "STATE",
"reason": "reviewed command migration"
}]
}`, "STATE", state, 1)
}
func hasFlag(flags []interfacesnapshot.Flag, name, flagType string) bool {
for _, flag := range flags {
if flag.Name == name && flag.Type == flagType {
return true
}
}
return false
}
+67 -1
View File
@@ -15,10 +15,76 @@ package main
import (
"os"
"strings"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app"
"gitlab.alibaba-inc.com/aes/aem-go-sdk/clitrack"
)
var (
appExecute = app.ExecuteWithTelemetry
resolveTelemetryIdentity = app.ResolveTelemetryIdentity
trackRun = func(cfg clitrack.Config, execute func() error, exitCode func(error) int) {
clitrack.New(cfg).Run(execute, exitCode)
}
)
// trackedExitError tells clitrack that the command failed without asking it to
// print the error a second time. The already-rendered message is published via
// ExtraFields c5, while app.Execute remains the sole owner of presentation.
type trackedExitError struct{}
func (trackedExitError) Error() string { return "" }
func trackerConfig(identity app.TelemetryIdentity, commandPath, errorMessage *string) clitrack.Config {
return clitrack.Config{
PID: "wcCRwZ",
App: "dws",
Version: app.RawVersion(),
UID: identity.UserID,
Username: identity.UserName,
NoCommandLine: true,
NoCwd: true,
NoAutomaticDimensions: true,
ExtraFields: func() map[string]string {
fields := map[string]string{"c9": *commandPath}
if identity.CorpID != "" {
fields["c10"] = identity.CorpID
}
if *errorMessage != "" {
fields["c5"] = *errorMessage
}
return fields
},
}
}
func telemetryOptedOut() bool {
return strings.TrimSpace(os.Getenv("DO_NOT_TRACK")) != ""
}
func main() {
os.Exit(app.Execute())
optedOut := telemetryOptedOut()
identity := app.TelemetryIdentity{}
if !optedOut {
identity = resolveTelemetryIdentity(os.Args[1:])
}
exitCode := 0
commandPath := "dws"
errorMessage := ""
cfg := trackerConfig(identity, &commandPath, &errorMessage)
if optedOut {
cfg.PID = ""
}
trackRun(
cfg,
func() error {
exitCode, commandPath, errorMessage = appExecute()
if exitCode != 0 {
return trackedExitError{}
}
return nil
},
func(error) int { return exitCode },
)
}
+220
View File
@@ -0,0 +1,220 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"slices"
"sort"
"strings"
"testing"
"time"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam"
"gitlab.alibaba-inc.com/aes/aem-go-sdk/clitrack"
)
func TestCrossPlatformCoverageMainRunsThroughCLITracker(t *testing.T) {
for _, wantCode := range []int{0, 1, 3, 5} {
t.Run(fmt.Sprintf("exit_%d", wantCode), func(t *testing.T) {
t.Setenv("DO_NOT_TRACK", "")
wantError := ""
if wantCode != 0 {
wantError = "synthetic failure"
}
testseam.Swap(t, &os.Args, []string{"dws", "sheet", "read", "--profile", "corp-a"})
testseam.Swap(t, &resolveTelemetryIdentity, func(args []string) app.TelemetryIdentity {
if strings.Join(args, " ") != "sheet read --profile corp-a" {
t.Fatalf("telemetry identity args = %#v", args)
}
return app.TelemetryIdentity{UserID: "user-1", UserName: "Alice", CorpID: "corp-1"}
})
testseam.Swap(t, &appExecute, func() (int, string, string) { return wantCode, "sheet read", wantError })
called := false
testseam.Swap(t, &trackRun, func(cfg clitrack.Config, execute func() error, exitCode func(error) int) {
called = true
if cfg.PID != "wcCRwZ" || cfg.App != "dws" {
t.Fatalf("tracker identity = PID %q App %q", cfg.PID, cfg.App)
}
if cfg.Version != app.RawVersion() {
t.Fatalf("tracker Version = %q, want %q", cfg.Version, app.RawVersion())
}
if !cfg.NoCommandLine || !cfg.NoCwd || !cfg.NoAutomaticDimensions || cfg.CaptureOutput {
t.Fatalf("tracker privacy config = NoCommandLine %v NoCwd %v NoAutomaticDimensions %v CaptureOutput %v", cfg.NoCommandLine, cfg.NoCwd, cfg.NoAutomaticDimensions, cfg.CaptureOutput)
}
if cfg.Env != "" || cfg.EventID != "" || cfg.Endpoint != "" || cfg.FlushTimeout != 0 || cfg.OutputMaxLen != 0 {
t.Fatalf("tracker SDK defaults were overridden: %#v", cfg)
}
if cfg.UID != "user-1" || cfg.Username != "Alice" || cfg.UserType != "" {
t.Fatalf("tracker user identity = UID %q Username %q UserType %q", cfg.UID, cfg.Username, cfg.UserType)
}
err := execute()
if wantCode == 0 && err != nil {
t.Fatalf("successful tracked execute error = %v", err)
}
if wantCode != 0 && (err == nil || err.Error() != "") {
t.Fatalf("failed tracked execute error = %#v, want empty sentinel", err)
}
if gotCode := exitCode(err); gotCode != wantCode {
t.Fatalf("tracked exit code = %d, want %d", gotCode, wantCode)
}
fields := cfg.ExtraFields()
if fields["c9"] != "sheet read" || fields["c10"] != "corp-1" || fields["c5"] != wantError {
t.Fatalf("tracker extra fields = %#v, want command path, corp ID, and error %q", fields, wantError)
}
if (wantError == "" && len(fields) != 2) || (wantError != "" && len(fields) != 3) {
t.Fatalf("tracker extra field count = %d for error %q", len(fields), wantError)
}
})
main()
if !called {
t.Fatalf("trackRun was not called for exit code %d", wantCode)
}
})
}
}
func TestCrossPlatformCoverageTrackerConfigOmitsEmptyOrganization(t *testing.T) {
commandPath := "version"
errorMessage := ""
cfg := trackerConfig(app.TelemetryIdentity{}, &commandPath, &errorMessage)
if cfg.UID != "" {
t.Fatalf("empty identity UID = %q", cfg.UID)
}
if cfg.Username != "" {
t.Fatalf("empty identity Username = %q", cfg.Username)
}
if fields := cfg.ExtraFields(); len(fields) != 1 || fields["c9"] != "version" {
t.Fatalf("empty organization fields = %#v", fields)
}
}
func TestCrossPlatformCoverageDefaultTrackRunNoopTracker(t *testing.T) {
called := false
trackRun(clitrack.Config{}, func() error {
called = true
return nil
}, nil)
if !called {
t.Fatal("default tracker did not execute callback")
}
}
func TestCrossPlatformCoverageMainRespectsDoNotTrack(t *testing.T) {
t.Setenv("DO_NOT_TRACK", "1")
testseam.Swap(t, &os.Args, []string{"dws", "version"})
testseam.Swap(t, &resolveTelemetryIdentity, func([]string) app.TelemetryIdentity {
t.Fatal("DO_NOT_TRACK must skip telemetry identity reads")
return app.TelemetryIdentity{}
})
testseam.Swap(t, &appExecute, func() (int, string, string) { return 0, "version", "" })
testseam.Swap(t, &trackRun, func(cfg clitrack.Config, execute func() error, exitCode func(error) int) {
if cfg.PID != "" || cfg.UID != "" || cfg.Username != "" {
t.Fatalf("opted-out tracker config = %#v", cfg)
}
if err := execute(); err != nil {
t.Fatalf("opted-out execution failed: %v", err)
}
if code := exitCode(nil); code != 0 {
t.Fatalf("opted-out exit code = %d, want 0", code)
}
})
main()
}
func TestCrossPlatformCoverageTrackerPayloadUsesReviewedFieldWhitelist(t *testing.T) {
testseam.Protect(t, &os.Args)
os.Args = []string{"dws", "sheet", "read", "--access-token", "must-not-leak"}
t.Setenv("SHELL", "/bin/zsh")
t.Setenv("TERM_SESSION_ID", "stable-session")
t.Setenv("TMUX_PANE", "%42")
t.Setenv("LANG", "zh_CN.UTF-8")
t.Setenv("LC_ALL", "zh_CN.UTF-8")
t.Chdir(t.TempDir())
requestBody := make(chan []byte, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
body, _ := io.ReadAll(req.Body)
requestBody <- body
w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
commandPath := "sheet read"
errorMessage := ""
cfg := trackerConfig(app.TelemetryIdentity{UserID: "user-1", UserName: "Alice", CorpID: "corp-1"}, &commandPath, &errorMessage)
cfg.Endpoint = server.URL
cfg.FlushTimeout = time.Second
clitrack.New(cfg).Run(func() error { return nil }, nil)
var body []byte
select {
case body = <-requestBody:
case <-time.After(time.Second):
t.Fatal("timed out waiting for telemetry request")
}
var envelope map[string]string
if err := json.Unmarshal(body, &envelope); err != nil {
t.Fatalf("decode telemetry request %q: %v", body, err)
}
decoded, err := url.QueryUnescape(envelope["gokey"])
if err != nil {
t.Fatalf("decode gokey: %v", err)
}
globalFields, err := url.ParseQuery(decoded)
if err != nil {
t.Fatalf("parse global telemetry fields: %v", err)
}
eventFields, err := url.ParseQuery(globalFields.Get("msg"))
if err != nil {
t.Fatalf("parse event telemetry fields: %v", err)
}
assertTelemetryKeys(t, globalFields, []string{"app_name", "app_version", "env", "msg", "pid", "platform", "uid", "username", "version"})
assertTelemetryKeys(t, eventFields, []string{"c1", "c10", "c3", "c4", "c9", "p1", "p4", "ts", "type"})
for key, want := range map[string]string{
"app_name": "dws", "app_version": app.RawVersion(), "env": "prod", "pid": "wcCRwZ",
"platform": "cli", "uid": "user-1", "username": "Alice", "version": app.RawVersion(),
} {
if got := globalFields.Get(key); got != want {
t.Fatalf("global telemetry field %s = %q, want %q", key, got, want)
}
}
for key, want := range map[string]string{
"type": "event", "p1": "cli.exec", "p4": "SYS", "c1": "dws", "c3": "0", "c9": "sheet read", "c10": "corp-1",
} {
if got := eventFields.Get(key); got != want {
t.Fatalf("event telemetry field %s = %q, want %q", key, got, want)
}
}
for _, key := range []string{"device_id", "ext", "os", "os_version", "pv_id", "sdk_version", "sid", "timezone_offset"} {
if globalFields.Has(key) {
t.Fatalf("global telemetry leaked %s: %q", key, decoded)
}
}
for _, key := range []string{"c2", "c5", "c6", "c7", "c8"} {
if eventFields.Has(key) {
t.Fatalf("event telemetry leaked %s: %q", key, globalFields.Get("msg"))
}
}
}
func assertTelemetryKeys(t *testing.T, fields url.Values, want []string) {
t.Helper()
got := make([]string, 0, len(fields))
for key := range fields {
got = append(got, key)
}
sort.Strings(got)
if !slices.Equal(got, want) {
t.Fatalf("telemetry keys = %v, want %v", got, want)
}
}
+62 -5
View File
@@ -1,22 +1,26 @@
# Architecture
`dws` is a Go CLI that turns DingTalk MCP metadata into a command-line surface for both humans and AI agents.
`dws` is a Go CLI with a versioned, static command surface for DingTalk MCP capabilities. Cobra help serves humans; runtime-assembled Schema (`ResolveSchemaBuild`) serves AI agents.
## High-Level Flow
1. `cmd` is the CLI entrypoint, invoking `internal/app` to build the root Cobra command tree.
2. `internal/app` wires static utility commands (`auth`, `audit`, `schema`, `completion`), product helper commands, and plugin commands.
2. `internal/app` wires static utility commands (`auth`, `audit`, `schema`, `completion`), product helpers, and versioned plugin descriptors.
3. `internal/helpers` contains the main command handlers for all product surfaces (`dev`, `chat`, `calendar`, `contact`, `aitable`, etc.).
4. `internal/executor` and `internal/transport` execute MCP JSON-RPC calls; `internal/output` formats responses.
5. `internal/auth` manages login state, PAT tokens, and agent-code detection.
6. Schema assembly (`ResolveSchemaBuild`) starts from the reviewed `CommandRegistry`, binds each identity to the exact current Cobra leaf, and then resolves typed constraints, sanitized MCP snapshots, and leaf ContractFinal / ProductDecl into one `SchemaRegistry`. Startup and Schema queries do not call MCP `tools/list`. There is no generate-written Catalog delivery step.
7. Production Catalog / `ResolveMeta` consume the lazily assembled registry via `RegisterSchemaSourceRoot` → `ResolveSchemaBuild` / `deliverySchemaCatalog` (声明即 Catalog; lazy `sync.Once`). `ResolveMeta` projects Identity/Safety/Selection from that assembly into an in-process map cache — not a committed `schema_catalog/` or `schema_meta_index.*` fixture. Flag-to-interface property delivery is owned by leaf `ParamDecl.Property` (native annotations). `schema_parameter_mapping_ledger.go` holds reviewed `mapping_exclusions` / `removals` (the empty `schema_parameter_bindings.json` audit table is retired). CLI `required` and constraints come from the resolved typed contract, while MCP `required` remains interface-only metadata.
8. Agent selection results are fixed in versioned review inputs. Every public tool has explicit use/avoid/example and interface disposition metadata; Skill references that are not current leaves require an explicit alias/group/stale/out-of-surface review instead of fuzzy runtime matching.
## Repository Structure
- `cmd`: CLI entrypoint
- `internal/app`: root command wiring, static utility commands, and plugin loading
- `internal/helpers`: product command handlers (dev, chat, calendar, contact, etc.)
- `internal/plugin`: plugin-based dynamic command loader
- `internal/cli`: catalog types and endpoint loader (static endpoint mode)
- `internal/plugin`: versioned plugin manifest, hook, skill, and transport descriptor loading
- `internal/cli`: Schema assembly, `dws schema` query, and catalog contracts
- `internal/generator`: CI/determinism tools (`cmd_schema_catalog` dump) and param-alias generate
- `internal/executor`: invocation dispatch and result handling
- `internal/transport`: MCP HTTP client and request signing
- `internal/auth`: login, token management, agent-code detection, identity
@@ -26,11 +30,16 @@
- `internal/security`: endpoint allowlist and domain trust
- `internal/safety`: runtime safety checks (confirm prompts, dry-run guards)
- `internal/cobracmd`: shared Cobra command builders
- `internal/corecmd`: dispatch-agnostic leaf-command base — flag registration,
alias/env/default value resolution, required and cross-flag constraint
validation, Risk write confirmation, toolArgs assembly, Runtime Schema
projection. Distinct from `internal/cobracmd` (generic tree helpers): it owns
the declarative leaf contract (`corecmd.Spec`) that the LeafSpec framework is
built on and that the Shortcut adapter projects into.
- `internal/pat`: PAT (Personal Access Token) authorization flow
- `internal/output`: response formatting (json, table, raw, pretty)
- `internal/logging`: structured logging and argument sanitization
- `internal/tui`: terminal UI helpers
- `internal/recovery`: panic recovery and graceful degradation
- `pkg/configmeta`: environment variable registry and documentation
- `pkg/config`: configuration constants and paths
- `pkg/edition`: edition detection (oss vs enterprise)
@@ -39,3 +48,51 @@
- `skills/`: bundled agent skills (mono/ and multi/ layouts)
- `test/`: CLI, integration, contract, unit, and skill E2E tests
- `scripts/`: install scripts, policy checks, and CI helpers
## Quality Pipeline
Quality enforcement is layered so a pull request receives fast, deterministic
admission feedback without pretending that downstream integration has already
run.
```mermaid
flowchart TB
PR["Pull request"] --> CLASSIFY["Fail-closed risk classification"]
CLASSIFY --> DOCS["Documentation-only<br/>asset/content validation"]
CLASSIFY --> STANDARD["Standard<br/>affected + reverse-dependent race<br/>scope-matched HEAD/base coverage"]
CLASSIFY --> HIGH["High-risk / main<br/>full race + native tests"]
DOCS --> CA["CI"]
STANDARD --> CA
HIGH --> CA
subgraph CA_CHECKS["Nine required contexts"]
L["Lint"]
T["Test"]
C["Coverage"]
P["Policy"]
E["Edition"]
I["Interface Integrity"]
A["AI Behavior"]
S["CLI Smoke"]
M["Mock MCP"]
end
CA --> CA_CHECKS
CA_CHECKS --> MAIN["Protected main"]
MAIN --> MP["Main Integration — 主干集成<br/>Multi-profile E2E"]
MAIN --> PLATFORM["Risk-selected / release native platform validation"]
MP --> RELEASE["Release delivery"]
PLATFORM --> RELEASE
```
All nine named contexts are produced for every tier. Domain-specific helpers
run when their owned surface is affected; otherwise the corresponding context
records an explicit unaffected success. Standard code changes still receive
representative Darwin/Windows compilation. High-risk PRs and protected `main`
run the complete race and native test suites, while platform-sensitive diffs
also receive native changed-code coverage.
Review orchestration is also base-owned: it requests one eligible peer without
executing PR code, re-routes an updated head when needed, and auto-merge
completes only after the latest push has peer approval plus the current
revision's nine strict contexts. Complete Multi-profile E2E remains downstream
of PR admission. See [`docs/ci-pr-gates.md`](ci-pr-gates.md) for the exact
classification, context, reviewer, and ruleset contract.
+69 -1
View File
@@ -43,7 +43,7 @@ repository root while preserving repo-local guidance for automation.
- Error message or category issues: inspect `internal/errors`
- Audit log issues: inspect `internal/audit`
- Plugin loading or command surface: inspect `internal/plugin`
- Failure or degraded mode: inspect `internal/errors`, `internal/recovery`
- Failure or degraded mode: inspect `internal/errors`
## Policy Checks
@@ -62,6 +62,74 @@ make lint
git diff --check
```
## Homebrew Formula Delivery
Official releases use the Release workflow's built-in `GITHUB_TOKEN` to update
exactly one tracked Formula after the immutable GitHub assets and their
checksums have passed verification. The publisher validates the rendered Ruby,
commits only the configured Formula path, never force-pushes `main`, and retries
from a fresh clone up to three times when `main` advances concurrently. Normal
stable and beta releases do not create a Formula PR or run a permission
canary. The workflow uses the existing repository-scoped
`HOMEBREW_PR_TOKEN` release identity because GitHub does not allow its built-in
Actions App to bypass this repository's rulesets. That identity is the sole
user bypass actor on the two default-branch rulesets. The workflow creates the
nine Code Admission checks for the Formula-only commit only after proving its
sole parent already has all nine successful checks and the committed Formula
exactly matches this release's verified bytes.
Keep `HOMEBREW_PR_TOKEN` repository-scoped with `Contents: write` and
`Pull requests: write` (the latter remains necessary for withdrawal rollback),
keep its owner as the designated ruleset bypass actor, and do not reuse
`RELEASE_GOVERNANCE_TOKEN`. The workflow and publisher provide the Formula-only
path restriction; GitHub rulesets do not infer that restriction from the token.
## Release Governance and Recovery
Store `RELEASE_GOVERNANCE_TOKEN` as a dedicated Actions secret with only
repository `Administration: read`. The immutable-releases REST endpoint is an
administration setting and cannot be read by the workflow's built-in
`GITHUB_TOKEN`. Both the default-branch governance preflight and the tag
contract use this same credential so a missing or expired identity is detected
before an irreversible tag is created.
Recovery is restricted to an existing annotated tag whose exact tag object,
commit, sealed metadata, original failed run/attempt, requester identity and
Release state all match; it then reuses the normal release jobs without a
second-person environment approval. A same-run “Re-run failed jobs” is even
lighter: the seal job may adopt an existing tag only when its complete
authority matches that run and its original attempt is not newer than the
current attempt. Do not put publication secrets in temporary branches or
create ad-hoc recovery workflows.
Cloud-sealed releases mirror to OSS only when the repository variable
`ENABLE_OSS_MIRROR` is exactly `true`. Leave the variable unset while no Bucket
is provisioned; GitHub, npm, and Homebrew delivery can then complete without
running the OSS step. Once enabled, missing credentials, an invalid Bucket, or
an upload failure remains fail-closed. The cloud tag immutably records the
decision as `OSS-Mirror: enabled|deferred`; publication and withdrawal consume
that sealed value instead of the variable's later state. Deferred releases
cannot use `repair_oss_version`; enabling OSS applies to later release tags
until an audited immutable repair marker is implemented.
If an immutable GitHub Release and npm package were delivered but an enabled
downstream China mirror failed, dispatch the normal `Release` workflow from the
protected default branch with exactly one of `repair_gitee_version` or
`repair_oss_version`. Channel repair accepts a fully successful exact release,
or a failed exact-tag run only when its latest attempt completed the release
contract, build, Apple signature, immutable GitHub publication, and npm
delivery checks for the exact tagged commit. OSS repair additionally requires
the tag's sealed policy to be `enabled`. It then downloads and re-verifies the
immutable assets before invoking only the selected mirror. For a failed
release, an OSS repair requires the OSS step itself to be the recorded failure.
A Gitee repair accepts either a failed Gitee job or a Gitee job that was
skipped behind that OSS failure; the latter is an explicit Gitee backfill and
does not claim that OSS has been repaired. Gitee repair requires `GITEE_TOKEN`,
`GITEE_USER`, and `GITEE_REPO`; OSS repair requires `OSS_ACCESS_KEY_ID`,
`OSS_ACCESS_KEY_SECRET`, `OSS_ENDPOINT`, and `OSS_BUCKET` (with optional
`OSS_PREFIX`) as Actions secrets. Missing credentials fail the selected repair
closed.
## Handoff Checklist
Before handoff, include:
+268
View File
@@ -0,0 +1,268 @@
# CI — PR 合入门禁
The pull-request admission layer has exactly nine required external contexts:
| Required context | Contract |
|---|---|
| `Lint` | Stable PR revision/risk classification plus applicable formatting, `go vet`, and Actionlint |
| `Test` | Tier-selected race/unit/release-script tests plus representative cross-platform compilation |
| `Coverage` | Scope-matched overall non-regression and 100% changed-code coverage |
| `Policy` | Repository policy and the fail-closed CHANGELOG contract |
| `Edition` | Edition contract tests |
| `Interface Integrity` | CLI, Schema, Skill, and stable-release compatibility |
| `AI Behavior` | Base-owned policy for PRs labeled `ai-generated` |
| `CLI Smoke` | Offline help for every public top-level command |
| `Mock MCP` | HTTP and stdio MCP lifecycle smoke tests |
The workflow display name is `CI`. Parallel helper
jobs may implement `Test` and `Coverage`, but they are not ruleset contexts.
Do not require an aggregate alias or a downstream integration check in place of
the nine contracts above.
`AI Behavior` is evaluated by a `pull_request_target` workflow that never
checks out or executes PR code. It writes the exact `AI Behavior` status to the
current PR head. Its Files API read is bracketed by base/head revision checks,
so a synchronize race fails closed. The same workflow supplies a successful
`AI Behavior` check run on protected `main` pushes for release governance.
## Exact CHANGELOG-only fast path
A pull request qualifies only when GitHub reports exactly one changed file,
that file is an in-place modification of `CHANGELOG.md`, and the base and head
both retain it as a regular non-executable `100644` blob. Add, delete, rename,
symlink, executable-mode, and second-file changes do not qualify.
`Lint` classifies the Files API result only after verifying that the API's base
and head equal the event revision both before and after pagination. `Policy`
checks out GitHub's PR merge ref and verifies its parents:
```text
HEAD^1 = pull_request.base.sha
HEAD^2 = pull_request.head.sha
```
It then runs:
```sh
./scripts/policy/check-changelog-pr.sh \
--fast-path "$PR_BASE_SHA" HEAD
```
The exact fast path remains limited to historic one-file maintenance. A
release-seal PR uses `--content-only`, which permits the generated
`CHANGELOG.md` change together with archival moves from `.changes/` to
`.changes/released/`; it receives the normal scoped admission instead of this
fast path. Ordinary PRs must not modify `CHANGELOG.md`; they add a standalone
release fragment instead. The validator and its policy dependencies in that merge tree are byte-for-byte the current base
versions. Validation targets the synthetic merge tree, not the feature-branch
tree, so a stale branch cannot supply an older validator or combine with newer
base notes into an invalid final CHANGELOG.
All nine admission contexts are still emitted and must succeed. Expensive
implementation helpers are skipped; the named contexts record that their code
surface is unaffected.
The protected `main` push keeps that fast path only when all of these
fail-closed conditions hold:
- the event is a non-forced update of the existing `refs/heads/main`;
- the event `after` SHA is the exact workflow SHA, and both event SHAs are
complete, non-zero commit IDs;
- GitHub's comparison reports the previous main tip as the unique linear merge
base, with no commits behind it;
- the complete resulting tree diff is exactly one in-place modification of
`CHANGELOG.md`;
- the previous main tip already has successful GitHub Actions checks for all
nine Code Admission contexts.
`Policy` then independently checks out the pushed revision and runs the same
`check-changelog-pr.sh --fast-path` contract from the event's `before` SHA to
its `after` SHA. If identity, ancestry, file scope, tree mode, CHANGELOG
content, or predecessor admission cannot be proved, classification falls back
to the complete main admission suite. A source change can therefore never
inherit the CHANGELOG-only result.
Any PR that touches `CHANGELOG.md` but also changes another file runs the same
content contract in `Policy` with `--content-only`. That mode accepts only
fragment archival moves (`.changes/<name>.md` to
`.changes/released/<version>/<name>.md`) alongside the changelog; source and
documentation changes are rejected. It still rejects invalid dates or
versions, missing bullets, placeholder `TODO`/`TBD`, unmanaged-section
changes, and unsafe tree modes.
## Risk tiers and downstream boundaries
`Lint` resolves the complete base/head diff before any helper is skipped.
Unknown or truncated input fails closed into the high-risk tier.
| Tier | Selection | Admission work |
|---|---|---|
| Documentation-only | Only prose/documentation assets; no executable, generated, workflow, packaging, or interface surface | Documentation and repository-asset validation; expensive code helpers skip while every required context still succeeds |
| Standard | Ordinary code change with a stable package graph | Race tests for changed Go packages and their reverse dependencies; candidate and merge-base coverage over the same impacted scope and `coverpkg`; representative Darwin/Windows compilation |
| High-risk / protected `main` | Workflow/policy, package add/remove/rename, generated Schema/registry, platform, auth/keychain, installer, packaging, release, transport, recovery, or an unprovable infrastructure classification | Complete race suite and full native macOS/Windows tests, plus every affected domain gate |
Domain helpers (`Edition`, `Interface Integrity`, `CLI Smoke`, and `Mock MCP`,
for example) execute their substantive suites when the diff can affect that
contract or when the high-risk tier is selected. Otherwise their stable named
contexts still report a successful, explicit unaffected result. Release-script
tests follow the same impact rule. This preserves the ruleset contract without
charging every developer for unrelated work.
Platform-sensitive changes additionally run native changed-code coverage.
Protected `main` always runs native tests; generic portable changes are held to
the Linux changed-code gate rather than being forced to manufacture
platform-only coverage.
Complete `Multi-profile E2E` is not a PR admission context. It belongs to the
`Main Integration — 主干集成` workflow and runs only after a push to `main` (or
an explicit manual dispatch). A failing downstream run remains a real
regression and must be repaired, but it must not be represented by a synthetic
successful PR check.
```mermaid
flowchart TB
PR["Pull request"] --> ADMISSION["CI"]
ADMISSION --> L["Lint"]
ADMISSION --> T["Test"]
ADMISSION --> C["Coverage"]
ADMISSION --> P["Policy"]
ADMISSION --> E["Edition"]
ADMISSION --> I["Interface Integrity"]
ADMISSION --> A["AI Behavior"]
ADMISSION --> S["CLI Smoke"]
ADMISSION --> M["Mock MCP"]
ADMISSION --> MAIN["Protected main"]
MAIN --> NATIVE["Full native platform matrix"]
MAIN --> E2E["Multi-profile E2E"]
MAIN --> RELEASE["Release delivery"]
```
## Review ownership and auto-merge
A base-owned `pull_request_target` workflow routes newly opened, updated,
reopened, or newly ready PRs targeting `main` to one eligible peer reviewer. It
does not check out or execute PR code, excludes both the author and the known
latest pusher, and balances the open requested-review load across the reviewed
maintainer pool. A current-head approval or change request is preserved; after
a new push, stale activity does not suppress a fresh request, and an
outstanding change requester is preferred for continuity.
The branch ruleset keeps one human approval and all nine strict required
contexts, and requires someone other than the latest pusher to approve after
the most recent head update. Repository auto-merge is enabled for ready PRs,
so a PR merges after that approval and the current revision's nine checks are
green. If `main` advances, strict checks rerun before merge. The reviewer
router is orchestration, not a quality context, and must not be added to the
ruleset.
## Running focused gates locally
Run the contracts relevant to the change. Ordinary contributors are not
expected to repeat every CI job locally:
```sh
make build
make policy
make interface-integrity BASE_REF=<merge-base> STABLE_REF=<stable-tag> CANDIDATE_REF=<candidate-sha>
make schema-compatibility BASE_REF=<merge-base> STABLE_REF=<stable-tag> CANDIDATE_REF=<candidate-sha>
make skill-command-integrity
make cli-smoke
make mock-mcp-smoke
go test -v -count=1 ./pkg/editiontest/...
```
CI 先解析并核对精确的 merge-base、最近可达且未撤回的 stable GA tag 和已提交的 candidate
SHA,再调用 `make authoritative-interface-integrity`。本地 `make interface-integrity`
与该 CI target 都只委托给同一个 modern authoritative wrapper,不存在第二个比较
入口。省略 `BASE_REF` 时本地 target 默认比较 `origin/main`,省略 `STABLE_REF` 时自动
选择该 base 可达且未撤回的最近 stable GA tag,省略 `CANDIDATE_REF` 时比较已提交的 `HEAD`。
需要逐字复现某次 CI 时,应显式传入该次运行记录的 merge-base、stable tag 和
candidate SHA。
`make update-interface-baseline` / `make reset-interface-baseline` 只维护
`test/fixtures/cli-interface-baseline.txt` 这一份非权威 CLI Smoke fixture。底层旧
`check-interface-baseline.sh` 不再作为本地或 CI 的兼容性审批入口,也不能用于批准
flag 迁移。
Schema compatibility 使用同一组 base、stable、candidate refs,以及 base-owned flag
与 command migration ledgers。merge-base-owned checker 分别规范化 merge-base 与
stable 的完整 Schema,并让 candidate 对两份历史 contract 独立执行检查;它只把已通过
Interface lifecycle 的 exact rename、command move 或 flag extraction 规范化到当前历史
副本,不会维护第二份 allowlist,也不会放宽其他 Schema 历史字段。
For a release-seal branch that archives rendered fragments:
```sh
base_ref=$(git merge-base HEAD origin/main)
./scripts/policy/check-changelog-pr.sh --content-only "$base_ref" HEAD
```
`make coverage-gate` is an enforcement step, not a profile generator. For a
standard PR, CI derives changed packages and their reverse-dependency test
closure, then generates candidate and merge-base profiles with the same test
scope and `coverpkg`. High-risk and protected-main runs use the complete
profiles. The complete candidate profile is produced by disjoint per-shard
helper jobs (`scripts/ci/test-packages.sh list-coverage`, kept serial with
`-p 1` inside each shard; `verify` proves the shard union equals the
full-suite scope exactly once) and concatenated in the aggregate job before
enforcement. The complete merge-base profile is restored from an exact-key
cache written by the last green `main` push of that same commit (key:
merge-base SHA plus resolved Go version); any miss falls back to recomputing
it in a merge-base worktree. The trusted `main` producer and PR consumer use
the same dedicated cache profile path because GitHub includes that path in the
cache version; the runtime-facing candidate and baseline filenames remain
separate. Near-miss reuse is forbidden — the caches carry no prefix restore
keys, because a neighbouring commit's profile would compare the candidate
against the wrong baseline. Supporting and (when
platform-selected) native profiles are generated before the aggregate
`Coverage` context evaluates them. The
aggregate and native gates require 100% coverage for changed executable Go
statements. Overall coverage remains an unrounded, zero-tolerance,
scope-matched merge-base non-regression check. Candidate and baseline profiles
are evaluated by the same block-deduplicating checker; supporting policy and
shortcut profiles contribute to changed-code coverage only. The checked-in
badge is presentation only and is never read as a gate input.
CLI 兼容检查只使用 modern Interface Snapshot 这一处权威比较 seam,并从 PR
merge-base 和最近的可达 stable release 生成权威快照。本治理机制合入后,
merge-base 拥有生成器、比较器和已审批迁移清单,因此 candidate 不能通过修改
helper、fixture 或在同一 PR 新增 self-approval 记录来放行 breaking change。首次
bootstrap 仍由 merge-base 已有的 modern helper 做无豁免比较,并只接受 candidate
提交中的规范空清单;完整边界见下方治理文档。
精确的两阶段 flag 迁移生命周期见
[CLI flag 兼容迁移治理](cli-interface-flag-migrations.md)。治理 PR 只能在
surface 未变化时新增 `pending`;后续产品 PR 达到审批的精确 surface 后,才能
消费 base-owned 记录并改为 `consumed`。在 main 与 stable 都达到 after 状态前
必须保留该回执,之后再由单独 PR 清理。机制只放行记录中的 legacy
visible-to-hidden,以及 canonical required 新增或提升;删除、type、scope、
shorthand、no-opt 和任何无关漂移仍然阻塞。Schema 可以新增;历史 product、
tool、parameter、mapping、positional execution、constraint 与 safety 语义继续
受保护。`alias_of` 只是一项由 `FlagSpec.Aliases` 产生的框架关系证据,不是 payload
等价证明;产品 PR 仍须证明 canonical 与 legacy 的最终运行 payload 等价并在 transport
前拒绝冲突输入。当前迁移清单为空,不授权 PR #904。
## Required GitHub repository settings
The `main` quality ruleset must enable strict required-status-check policy
(`strict_required_status_checks_policy=true`) so a PR is revalidated whenever
`main` advances. It must require these exact contexts and no legacy aliases:
- `Lint`
- `Test`
- `Coverage`
- `Policy`
- `Edition`
- `Interface Integrity`
- `AI Behavior`
- `CLI Smoke`
- `Mock MCP`
Do not require helper jobs, `Multi-profile E2E`, or an aggregate admission
alias. Update ruleset contexts only after the new names have appeared on the
protected branch, so a rename cannot silently remove enforcement or leave an
unproducible required context.
The branch ruleset also requires one approval after the latest push. Enable
repository auto-merge and automatic head-branch deletion; keep the base-owned
reviewer router outside the required-context list.
+223
View File
@@ -0,0 +1,223 @@
# CLI Help / Schema 兼容迁移治理
本文定义一种受控迁移:保留旧 flag 的可执行兼容性,但把它从 Help 与 Agent Schema 中隐藏,并将新的规范 flag 设为唯一可见入口。迁移必须保持原 flag 的 requiredness:optional 只能迁到 optional,required 只能迁到 required。它只解决这一种精确变更,不是通用 breaking-change 豁免。
同一套 base-owned lifecycle 也治理两类跨命令迁移:旧命令保留执行能力但从 Help / Schema 导航隐藏,并迁到新的公开命令路径;或把旧命令中的一个可选 flag 拆成新的专用命令。跨命令迁移只允许清单精确声明的 `command_became_hidden` / `flag_became_hidden` 及其 Schema 投影,不是通用 command-path breaking-change 豁免。
同名 flag 的精确类型迁移属于另一类评审机制,只能进入
`internal/interfacesnapshot/reviewed.go` 与 legacy smoke helper 的镜像表;flag rename
只能进入本文的 JSON lifecycle ledger。一项迁移不得跨两种机制组合授权。
## 唯一比较入口与信任边界
PR 与本地兼容性审批的唯一权威比较入口是 modern Interface Snapshot:
- `cmd/interface-snapshot` 生成和比较快照;
- `internal/interfacesnapshot` 实现兼容规则和迁移生命周期;
- `scripts/policy/check-command-compatibility.sh` 组装 candidate、PR merge-base 和最近可达且未撤回的 stable GA 三份快照;
- `scripts/policy/check-authoritative-interface-baselines.sh` 只保留为 Makefile 的兼容包装,不再维护第二套判断逻辑。
紧随其后的 Schema compatibility 不是第二份审批清单。它从同一 merge-base-owned
ledger 和同一组三方 Interface Snapshot 取得已经完成 lifecycle 校验的
authorization。merge-base-owned checker 会分别规范化 merge-base 与 stable 的完整
Schema,并让 candidate 对两份历史 contract 独立执行检查;授权的 flag rename 只会
精确投影到当前被检查的历史副本。candidate 不能为 CLI 与 Schema 分别提供两套例外。
`make interface-integrity` 也调用上述 authoritative wrapper;默认 base 为
`origin/main`,stable 可由包装脚本自动解析,candidate 默认为已提交的 `HEAD`。旧
`scripts/policy/check-interface-baseline.sh` 只供
`make update-interface-baseline` / `make reset-interface-baseline` 维护非权威 CLI
Smoke fixture,不参与迁移审批。
直接调用 `interface-snapshot compare` 时,只要提供 migration manifest 参数,就必须
同时提供 `--base` 与 `--stable`;核心 lifecycle 也拒绝缺失 stable 的非空清单,避免
调用方因漏传历史参考而提前清理 consumed receipt。
PR merge-base 同时拥有快照生成器、比较器和已审批清单。门禁用这套 base-owned helper 检查同一个已提交 candidate revision、merge-base 与 stable,candidate 不能通过修改自己的 Go 比较 helper 来放宽规则。candidate 中的清单只参与迁移状态流转,不能批准同一个 PR 引入的接口变化。首次引入 flag 机制时,merge-base 尚无迁移解析器;bootstrap 会用 merge-base 已有的 modern Interface Snapshot 做不带豁免的普通比较,并只接受 candidate 中逐字匹配的空 flag 清单。后续引入 command migration 扩展时,base 已拥有 flag comparator;bootstrap 仍只执行 base-owned 普通比较,不向旧 helper 传入新的 command ledger,因此允许随治理 PR 提交仍处于 before 的 pending 计划,也不会授予任何迁移豁免。bootstrap 无法让旧 helper 证明新治理实现本身正确,因此本治理 PR 的新 parser、lifecycle、launcher 与 hostile tests 仍是必须由真人评审的受保护策略变更;它们合入后才成为后续 PR 的 base-owned authority。
这条边界保护比较规则和审批数据,不是任意代码沙箱。GitHub workflow / launcher 的变更仍由仓库保护规则和真人评审负责;candidate Cobra 构建也会执行 candidate 代码,因此对同一 runner 上的主动恶意代码,需要独立进程或文件系统隔离,不能把本门禁描述成已经解决。
已审批清单固定为:
```text
scripts/policy/interface-migrations/approved-flag-migrations-v1.json
scripts/policy/interface-migrations/approved-command-migrations-v1.json
```
清单使用严格 JSON 解析:版本、字段名大小写、JSON 值类型、命令路径和 flag 名都必须精确;拒绝重复键、未知键、scalar `null` 与尾随 JSON 值,`reason` 不能为空;禁止 `*`、`?`、前缀规则或其他 wildcard。清单中的 `pending` 记录只记录已评审计划,并授权其精确列出的后续产品迁移;候选与 merge-base 仍必须精确匹配 `before`,不能授权同一个提交中的接口变化,也不能作为其他命令或参数的通配豁免。
## 跨命令迁移原语
`approved-command-migrations-v1.json` 只接受两种 `kind`:
| kind | CLI after 状态 | Schema 允许的精确投影 |
|---|---|---|
| `command_move` | legacy 命令仍 runnable、由 visible 变 hidden;replacement 由 absent 变 visible runnable | 同一 stable tool identity 的 `primary_cli_path` 改到 replacement;只允许清单列出的参数改名,参数类型、property、requiredness、default 等必须等价 |
| `flag_extraction` | legacy 命令保持 visible runnable;指定 legacy flag 仍可执行但由 visible 变 hidden;replacement 由 absent 变 visible runnable | source tool 只删除指定参数;replacement tool 必须位于精确的新路径,并保持 source 的 interface 与 safety identity;清单必须完整列出每个 source 参数到 replacement 参数或常量 property 的承接关系 |
`command_move` 只能隐藏没有子命令的 legacy leaf,且 legacy 与 replacement
不得互为祖先路径;整棵命令树的迁移需要单独设计逐叶治理,不能复用这一原语。
稳定 Schema tool 可以继续接受普通的 optional 参数新增,但不得借路径迁移引入清单未登记的
`required`、`cli_required` 或 `required_when` 参数;参数改名的目标也不得与历史
Schema 中已有的其他参数重名,避免把两个历史参数静默合并。`flag_extraction` 只接受
optional bool legacy flag,不能隐藏仍由 Cobra hard-required 的参数。它必须对 source tool
的全部历史参数逐项声明:普通参数使用精确 `from` → `to`(同名也必须显式写出),且恰好
一个与 legacy flag 同名的 `from` 使用 `replacement_constant`,不得同时声明 `to`;所有
`from` 与 replacement 参数/property 目标必须唯一。legacy bool flag 的 `no_opt` 必须等于
常量布尔值的字符串形式。v1 只治理 optional bool flag 的 `NoOpt=true` 激活分支,因此
`replacement_constant.value` 与 legacy `no_opt` 都必须是 `true`;negative flag、默认即
`true` 或固定 `false` 的语义不在本轮证明范围,必须另行设计,不能借本清单放行。
`replacement_constant` 不是清单自报即可成立的例外。after 阶段的 Interface Snapshot
必须从 replacement 命令的同一份框架运行时声明中捕获完全一致的 property/value,缺失、
值不符或额外常量都会使 lifecycle 落入 partial。对于 #1054,`dws chat topic create`
必须通过 `NewLeafCommand` 的 `ConstParams` 声明并实际注入
`convThreadEnabled=true`;手写 `RunE` 固定值、Cobra annotation 或只改清单都不能提供这份
同源证据,Snapshot 只读取 `corecmd` 包内私有注册表公开的只读副本。第一次向旧快照增加
bool 常量证据属于 bootstrap;一旦任一历史快照已记录该
证据,普通 Interface Compare 会持续要求 property/value 集合完全一致,因此 ledger 清理后
删除、翻转或增加常量仍会阻塞。若 candidate 改动 command ledger,则
`internal/corecmd/corecmd.go`、`internal/corecmd/interface_const_params.go` 与
`internal/helpers/leaf.go` 三份执行/证据桥必须保持 base Git blob 不变;框架演进必须先用
独立 PR 合入,不能和产品消费混在一起。
replacement 必须保留 source 已发布的 dry-run 能力:历史 `dry_run` 非空时不得删除或改值;
历史未声明时允许 replacement 新增 dry-run。这与普通 Schema 兼容规则保持同一单调边界。
两种迁移都要求旧 argv 继续可执行。删除旧命令、删除旧 flag、把 legacy 改成 non-runnable、改变未登记的历史参数、改变 interface / safety,或只完成部分 before → after 转换都会 fail closed。命令别名会先规范到 reference 的 canonical path,但清单本身仍只能记录精确 canonical 命令,不能用 alias 或前缀扩大授权。
跨命令清单复用下文同一套 `pending → consumed → cleanup` 生命周期。治理 PR 只能新增 `pending` 且产品 surface 必须仍是 before;后续产品 PR 才能一次性切到 after 并改为 `consumed`。candidate 新增的 pending 记录不能批准自己的改动。
当前首批 pending 记录覆盖 `chat topic` 收口:`chat group create --thread` 拆到 `chat topic create`,以及 `chat message list-topic-replies` / `forward-topic` 迁到对应的 `chat topic` 命令。前一条完整登记 `name` / `type` / `users` 的同名承接,以及 `thread` → `convThreadEnabled=true` 的常量承接。产品 PR 消费这些记录时只能把三条 `state` 改为 `consumed`,不得改写其 before、after、Schema mapping、constant 或 reason。
## 两阶段迁移与回执清理
每条迁移以 `(command, legacy flag, canonical flag)` 为唯一精确键,并经历以下生命周期:
| 阶段 | PR 可以做什么 | 必须满足的快照状态 |
|---|---|---|
| 1. 治理审批 | 新增 `state: pending` 的精确记录;不得在同一个 PR 修改产品 surface | candidate 和 merge-base 都与记录中的 `before` 完全一致;该记录不改变 stable 的判断 |
| 2. 产品迁移 | merge-base 已拥有 `pending` 后,按记录一次性切到精确 `after`,并把记录改为 `state: consumed` | legacy 仍存在但由 visible 变 hidden,且声明 `alias_of`;canonical 的 requiredness 与 legacy 迁移前完全一致 |
| 3. 保留回执 | 产品 PR 合入后,如果 stable 仍是 `before`,继续保留 `consumed` | merge-base 或 stable 仍有任一份尚未达到 `after` |
| 4. 单独清理 | 当 merge-base 和 stable 都已经是 `after`,在后续 PR 删除该记录 | 两份参考快照均精确匹配 `after`;继续保留过期回执会被门禁拒绝 |
因此,新增 `pending` 和修改产品 surface 不能发生在同一个 PR;candidate 自己新增的记录不能 self-approve。迁移也不能部分执行:legacy、canonical、`alias_of` 或状态只要有一项不匹配,门禁即失败。
下面只是清单结构示例,不代表已审批命令;实际字段必须从 Interface Snapshot 核对:
```json
{
"version": 1,
"migrations": [
{
"command": "dws chat message recall",
"legacy": {
"name": "msg-id",
"before": {
"present": true,
"type": "string",
"required": true,
"scope": "local"
},
"after": {
"present": true,
"type": "string",
"hidden": true,
"scope": "local",
"alias_of": "message-id"
}
},
"canonical": {
"name": "message-id",
"before": { "present": false },
"after": {
"present": true,
"type": "string",
"required": true,
"scope": "local"
}
},
"state": "pending",
"reason": "保留旧 argv 兼容性,并将规范 flag 设为唯一可见入口"
}
]
}
```
产品迁移 PR 必须保持同一条记录的命令、flag、before/after 和 reason 不变,只把 `pending` 改成 `consumed`。
## `alias_of` 是框架来源的受评审关系证据
`alias_of` 不是 Schema 同义词、参数概念词典或任意文字声明。它只能由 `FlagSpec.Aliases` 写入,并与内部 origin `corecmd.flag_spec_aliases.v1` 成对出现;每次 Interface Integrity 都会在已提交的 detached candidate 上执行源码门禁,禁止其他生产文件写入或复刻这些 evidence token。Interface Snapshot 会验证:
- legacy 与 canonical 位于同一个可执行命令;
- canonical flag 确实存在;
- legacy 与 canonical 类型一致;
- legacy 不是指向自身,也不存在 alias chain;
- legacy 的 after 状态精确指向该记录中的 canonical flag。
通过命令框架声明 `FlagSpec.Aliases` 时,框架会自动注册隐藏的兼容 flag,并写入两项 relation annotation;仅手写 `alias_of`、伪造 origin、重复值或不精确值都会让快照生成失败。不要用 Schema overlay、迁移清单或手写 Cobra annotation 伪造关系。
这项关系证据只证明 legacy/canonical 经过受控框架路径建立关系,不证明最终 transport payload 等价,也不会替产品代码实现命令特有的值同步。当前框架还禁止把
`MarkRequired` 与 `FlagSpec.Aliases` 直接组合,因为 Cobra 的 hard-required
校验只识别 canonical spelling。若产品迁移同时需要 canonical 的 Cobra required
标记和 legacy spelling,产品 PR 必须提供明确的运行时方案,并通过 canonical / legacy
最终 payload 等价、同值输入一致、冲突输入在 transport 前失败、legacy 仍可调用但 Help 隐藏等测试;迁移清单和 relation evidence 都不能替代这些证明。
## 豁免边界
一条 base-owned、状态正确且前后快照精确匹配的记录,只会从普通兼容报告中移除以下两类预期 finding:
1. legacy flag 的 `flag_became_hidden`(visible → hidden);
2. required legacy 被新增的 required canonical 替代时产生的 `required_flag_added`;如果 canonical 在 before 阶段只是 hidden 占位符,则允许它在转为公开拼写时继承 legacy 的 requiredness。已有的 visible canonical 不允许借 rename 改变 requiredness。
以下变化仍按普通兼容规则阻塞,不能被迁移记录掩盖:
- 删除 legacy、canonical、命令或其他 flag;
- flag 类型或迁移记录中的 scope、shorthand、`no_opt` 漂移;
- `alias_of` 缺失、指向变化或 alias chain;
- 命令路径及任何无关的阻塞性接口变化;
- 不精确、部分完成、超出记录范围的 surface 变化。
## Schema 投影边界
Agent-visible command 会把 visible Cobra flag 投影为 Schema parameter,因此合法的
legacy hidden 迁移会同时表现为历史 parameter 消失,constraint member 也可能从
legacy 名改为 canonical 名。Schema adapter 只接受已经由三方 Interface Snapshot
判定为 authorized 的迁移,并按 tool 的精确 `primary_cli_path` 绑定:
- reference 仍处于 `before` 且该 flag 有 Schema surface 时,baseline legacy parameter
必须存在,candidate legacy parameter 必须消失,candidate canonical parameter 必须存在;
如果 baseline 只有 canonical、没有 legacy,则 adapter 不得借 CLI ledger 提升
`required` / `cli_required` 或重写 constraint;
- rename 前后的 `type`、`property`、`interface_type`、default、format、enum 与
`required_when` 必须完全一致;
- `required` / `cli_required` 必须在 rename 前后完全一致,升高或降低都失败;
- constraint 只允许在同一 tool 内按已枚举的 legacy → canonical map 做 member 替换、
排序与去重;group kind、非迁移 member 或 group 增删仍然阻塞;
- 多个 legacy 指向同一 canonical 时,所有历史 parameter signature 必须一致,否则
fail closed。
adapter 先构造经过上述验证的历史 contract 副本,再调用原 Schema checker;它不会按
错误字符串删除 finding。这样既能处理纯 rename,也能阻止“旧 required 参数改名后意外
变为 optional”或 property 漂移等伪兼容。`consumed` 回执在 merge-base Schema 已经处于
canonical-only `after` 状态时不需要再次投影;adapter 保持 baseline 不变,由原 checker
验证 candidate 是否仍与该 canonical contract 兼容。
## 本地验证
先确保 merge-base 和 stable tag 已在本地,然后运行与 CI 相同的权威门禁:
```sh
make interface-integrity \
BASE_REF=<merge-base> \
STABLE_REF=<stable-tag> \
CANDIDATE_REF=<candidate-sha>
make schema-compatibility \
BASE_REF=<merge-base> \
STABLE_REF=<stable-tag> \
CANDIDATE_REF=<candidate-sha>
```
`STABLE_REF` 必须解析到从该 merge-base 可达的最高未撤回 stable GA tag;primary checker 会按 release contract 独立核对,不能用任意 after commit 或已撤回版本提前清理回执。包装脚本可以在省略时自动解析。`CANDIDATE_REF` 省略时固定为命令启动时的已提交 `HEAD`;评审和复现 CI 时应显式传入 candidate SHA,避免 surface 与清单来自不同 revision。
+209
View File
@@ -0,0 +1,209 @@
# command 领域模型
本文档描述 `internal/corecmd` 包的领域模型——类型、概念及其关系。
## 核心模型图
```
┌─────────────────────────────────────────────────────────────────────┐
│ corecmd.Spec │
│ (一个叶子命令的完整契约) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─── CLI 表面 ───┐ ┌─── 参数声明 ───────────────────────────┐ │
│ │ Use │ │ FlagSpec[] │ │
│ │ Short │ │ ├─ Name / Kind / Default │ │
│ │ Long │ │ ├─ Required / MarkRequired │ │
│ │ Example │ │ ├─ Aliases[] / EnvVar (回退链) │ │
│ └────────────────┘ │ ├─ Bind / Transform / OmitEmpty │ │
│ │ └─ Enum / Format / SchemaDescription │ │
│ │ │ │
│ │ Constraint[] │ │
│ │ ├─ at_least_one │ │
│ │ ├─ exactly_one │ │
│ │ └─ mutually_exclusive │ │
│ │ │ │
│ │ ConstParams map[string]any │ │
│ └────────────────────────────────────────┘ │
│ │
│ ┌─── 安全模型 ──────────────────────────────────────────────────┐ │
│ │ Safety contract.SafetySpec │ │
│ │ ├─ Effect (read / write / destructive) │ │
│ │ ├─ Risk (low / medium / high) │ │
│ │ ├─ Confirmation (not_required / user_required) ──▶ 运行时门 │ │
│ │ └─ Idempotency (idempotent / retryable / …) │ │
│ │ │ │
│ │ 四字段彼此独立;同一值同时供运行时与 Schema 使用 │ │
│ │ ConfirmFirst: bool (只控制确认门顺序) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── Contract 声明 (Agent 可见的元数据) ────────────────────────┐ │
│ │ ContractDecl │ │
│ │ ├─ Title / Description │ │
│ │ ├─ contract.DryRunSpec {PreviewKind, RemoteReads} │ │
│ │ ├─ contract.InterfaceSpec {Mode, Availability, Reason, Ref}│ │
│ │ ├─ contract.SelectionSpec {AgentSummary, UseWhen, AvoidWhen│ │
│ │ │ Prerequisites, Tips, Examples} │ │
│ │ ├─ contract.ToolIdentitySpec {ProductID, CanonicalPath, …} │ │
│ │ └─ Positionals[] {Name, Type, Required, Variadic} │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── 执行体 (恰好一个) ─────────────────────────────────────────┐ │
│ │ Invoke(Ctx, toolArgs) ← #830 过渡:单步派发(目标 mcpbind)│ │
│ │ Orchestrate(Ctx) ← #830 过渡:多步编排(目标 Handler)│ │
│ │ RunE(cmd, args) ← 逃生舱:完全自定义 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─── 钩子 ─────────────────────────────────────────────────────┐ │
│ │ Validate(cmd, args) ← 条件式业务校验(约束表达不了的) │ │
│ │ PostMount(cmd) ← 挂载收尾(设置 Args 等 cobra 属性) │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
## 构建与执行流
```
corecmd.Spec ──── corecmd.New() ────▶ cobra.Command ──── 用户执行 ────▶ Ctx
│ │ │
构建时检查: 注册产物: 执行上下文:
• validateDispatchDecl • Flags + Aliases • Str(name)
• validateSafetySpec • Annotations (Schema) • Int(name)
• validateContractDecl • Long (约束 help) • Bool(name)
• RegisterFlags • RunE (管线) • StrSlice(name)
• ValidateConstraintDecls • Changed(name)
• embedContractIntoSchema • DryRun() / Yes()
• AnnotateConstraints
• PostMount
```
## 领域概念
| 概念 | 类型 | 职责 |
|------|------|------|
| **corecmd.Spec** | struct | 一个命令的完整契约(声明 + 执行) |
| **FlagSpec** | struct | 一个参数的注册、回退链、绑定规则 |
| **Constraint** | struct | 参数间的关系约束 |
| **Safety** | contract.SafetySpec | 运行时与 Schema 共用的安全契约 |
| **ContractDecl** | struct | Agent 可见的完整工具规格声明 |
| **contract.SelectionSpec** | struct | Agent 选择该工具的语义指引 |
| **contract.InterfaceSpec** | struct | 工具的接口模式与可用性 |
| **contract.DryRunSpec** | struct | dry-run 能力声明 |
| **contract.ToolIdentitySpec** | struct | 工具在注册表中的身份标识 |
| **contract.RuntimeSchemaPositional** | struct | 有序位置参数声明 |
| **Ctx** | struct | 执行上下文(类型安全的 flag 读取) |
| **New** | func | 统一构建器(`corecmd.Spec` → `*cobra.Command`) |
## SafetySpec
`corecmd.Spec.Safety` 使用 `corecmd/contract.SafetySpec`(无 cli 类型别名),没有 command 自定义 Risk/Safety 枚举,也没有 `SafetyDecl` 覆盖层:
```go
Safety: contract.SafetySpec{
Effect: "write",
Risk: "high",
Confirmation: "user_required",
Idempotency: "unknown",
}
```
- `Confirmation == "user_required"` 时执行确认;`--yes` 和 `--dry-run` 可跳过交互。
- `Effect`、`Risk`、`Idempotency` 不参与确认决策,也不会改写 `Confirmation`。
- 任意一个字段非空时,四个字段必须全部显式声明;构建时拒绝部分声明。
- 完全空值仅作为历史只读默认,最终发布为 `read/low/not_required/idempotent`。
## FlagSpec 有效值回退链
框架统一的 flag 值解析顺序:
```
显式主 flag (Changed)
│ 空?
▼
隐藏别名 (Changed, 按声明序)
│ 空?
▼
环境变量 (EnvVar)
│ 空?
▼
注册默认值 (Default)
│ 空?
▼
ArgDefault (兜底)
```
各 Kind 的特殊行为:
| Kind | 入参条件 | 回退链 |
|------|----------|--------|
| KindString | 有效值非空(或 !OmitEmpty) | 完整参与 |
| KindInt | 值 ≠ 0(putInt 语义) | 完整参与 |
| KindBool | Changed 时入参(显式 false 也下发) | 不参与别名/env 回退 |
| KindStringSlice | 存在非空元素 | 仅 Changed 的主 flag/alias |
## Constraint 约束
声明式跨 flag 关系,构建时校验合法性,运行时统一执行:
| Kind | 语义 | 错误文案示例 |
|------|------|-------------|
| `at_least_one` | 至少提供一个 | "请至少指定 --a、--b 之一" |
| `exactly_one` | 恰好提供一个 | "请指定 --a、--b 之一" / "只能指定其一" |
| `mutually_exclusive` | 最多提供一个 | "参数 --a、--b 互斥,只能指定其一" |
"是否提供"的判定复用有效值回退链(显式主 flag → 别名 → env),注册默认值不算作已提供。
## ContractDecl 子结构
### contract.SelectionSpec(Agent 选择指引)
```go
contract.SelectionSpec{
AgentSummary: "一句话描述工具做什么",
UseWhen: []string{"在什么场景下应该选择这个工具"},
AvoidWhen: []string{"什么场景不应该用,应该用什么替代"},
Prerequisites: []string{"使用前提条件"},
Tips: []string{"使用技巧"},
Examples: []string{"dws dev app create --name Bot --dry-run"},
}
```
### contract.InterfaceSpec(接口模式)
```go
contract.InterfaceSpec{
Mode: "composite", // local / mcp / composite
Availability: "available", // available / unavailable
Reason: "...", // composite/unavailable 时的原因
Ref: &contract.InterfaceRefSpec{ // mcp 时的 ref
ProductID: "...",
RPCName: "...",
},
}
```
### contract.DryRunSpec(dry-run 能力)
```go
contract.DryRunSpec{
PreviewKind: "invocation", // invocation / request / plan
RemoteReads: false, // dry-run 时是否发起远端读
}
```
## 执行体三选一
| 执行体 | 适用场景 | 框架做了什么 |
|--------|----------|-------------|
| **Invoke** | #830 过渡单步派发(生产仍用;目标 mcpbind) | 框架完成 required→constraint→validate→buildArgs→confirm,传入装配好的 toolArgs |
| **Orchestrate** | #830 过渡多步编排(生产仍用;目标 Handler) | 框架完成 required→constraint→validate→confirm,传入 Ctx 自行组装调用 |
| **RunE** | 逃生舱 | 框架仍执行 Safety 确认,具体业务执行完全自定义 |
## 设计不变量
1. **一个 corecmd.Spec = 一个叶子命令的全部事实**
2. **声明面绝不调用后端**——command 是 dispatch-agnostic
3. **执行面绝不发明 CLI 表面**——业务 flag 必须在 Flags 声明
4. **构建时拦截 > 运行时报错**——声明错误 panic 在注册阶段
5. **SafetySpec 是单一事实源**——Confirmation 驱动运行时,其余字段原样进入 Schema
6. **声明即 review**——代码中的 Schema 经 code review 后直接投影,不依赖外部 hint 文件
+286
View File
@@ -0,0 +1,286 @@
# 命令框架架构
本文档描述 `internal/corecmd` 统一命令框架的当前架构,面向框架使用者和维护者。
## 概览
```
用户输入 → cobra 命令树 → corecmd.New() → 运行时管线 → 后端派发
```
命令框架将 CLI 命令的**声明**与**执行**分离:
- **声明面** — 数据字段描述命令是什么(flag、约束、SafetySpec、Contract 元数据)
- **执行面** — 钩子函数描述命令做什么(校验、派发、编排)
框架负责:flag 注册、有效值回退链、required/约束校验、SafetySpec 确认、toolArgs 装配、Agent Runtime Schema 投影。
## 核心类型
### corecmd.Spec
统一的类型化命令规格,是框架的核心数据结构:
```go
type Spec struct {
// 声明面
Use string
Short string
Long string
Example string
Flags []FlagSpec
Constraints []Constraint
Safety contract.SafetySpec // 运行时与 Schema 的单一安全来源
ConfirmFirst bool // 确认门先于参数校验
ConstParams map[string]any
Contract ContractDecl // 叶子 Contract 声明(非 Catalog Schema)
// 执行面(恰好一个;Invoke/Orchestrate 为 #830 过渡派发 API,目标 mcpbind+Handler)
Invoke func(c *Ctx, toolArgs map[string]any) error // 过渡:单步
Orchestrate func(c *Ctx) error // 过渡:多步
RunE func(cmd *cobra.Command, args []string) error // 逃生舱
// 钩子
Validate func(cmd *cobra.Command, args []string) error
PostMount func(cmd *cobra.Command)
}
```
### SafetySpec(单一安全来源)
`Spec.Safety` 使用 `corecmd/contract.SafetySpec`:
| 字段 | 职责 |
|------|------|
| `Effect` | 操作影响:read / write / destructive |
| `Risk` | 风险等级:low / medium / high |
| `Confirmation` | 是否需要用户确认:not_required / user_required |
| `Idempotency` | 幂等性:idempotent / retryable / non_idempotent / unknown |
四个字段彼此独立。框架只读取 `Confirmation` 决定运行时确认,其余字段原样发布到 Schema,不从一个字段机械推导另一个。非空 SafetySpec 必须一次声明完整:
```go
Safety: contract.SafetySpec{
Effect: "write",
Risk: "high",
Confirmation: "user_required",
Idempotency: "unknown",
},
```
完全空值保留历史只读默认 `read/low/not_required/idempotent`;不存在 Risk/Safety 枚举或覆盖优先级链。
### FlagSpec
声明一个 flag 的注册方式、有效值回退链、到 toolArgs 的绑定:
```go
type FlagSpec struct {
Name string // flag 名(kebab-case)
Usage string // --help 文案
Kind FlagKind // String / Int / Bool / StringSlice
Default string // 注册默认值
Required bool // 框架校验非空
Aliases []string // 隐藏别名
EnvVar string // 环境变量回退
Bind string // toolArgs 键名(空则用 Name)
Transform func(string) (any, error) // 值转换
// ...更多字段见源码
}
```
### Constraint
跨 flag 关系约束:
```go
type Constraint struct {
Kind ConstraintKind // at_least_one / exactly_one / mutually_exclusive
Flags []string
}
```
## 有效值回退链
flag 解析按以下顺序取值(先命中先生效):
```
显式主 flag (Changed) → 隐藏别名 (Changed) → 环境变量 → 注册默认值
│
ArgDefault ←──┘ (兜底)
```
- KindBool:仅 Changed 时生效,不参与回退链
- KindStringSlice:仅主 flag / alias Changed 时生效,元素恒 TrimSpace
- KindInt:非零才入 toolArgs(putInt 语义)
## 构建时流程
`corecmd.New(spec)` 执行以下构建时检查(失败则 panic):
1. **validateDispatchDecl** — 恰好一个执行体(Invoke/Orchestrate/RunE)
2. **validateSafetySpec** — 非空 SafetySpec 的四个独立字段必须完整
3. **validateContractDecl** — Contract 声明完整性(Description、AgentSummary、UseWhen、AvoidWhen、Examples、Interface)
4. **RegisterFlags** — flag + alias 注册到 cobra
5. **ValidateConstraintDecls** — 约束引用的 flag 必须存在
6. **embedContractIntoSchema** — 投影到 dws.schema.* annotations
7. **AnnotateConstraints** — 约束渲染到 --help
8. **PostMount** — 调用方的挂载收尾钩子
## 运行时流程
生成的 `RunE` 按以下顺序执行:
```
[ConfirmFirst? → ConfirmSafety] ← 可选:先确认后校验
│
▼
ValidateRequired ← 有效值回退链校验
│
▼
ValidateConstraints ← 互斥/至少一个/恰好一个
│
▼
Validate hook ← 条件式业务校验(可选)
│
▼
BuildArgs ← flag → toolArgs 装配
│
▼
ConstParams 合并
│
▼
[!ConfirmFirst? → ConfirmSafety] ← 默认顺序:校验后确认
│
▼
Invoke(ctx, toolArgs) ← #830 过渡:单步派发
或 Orchestrate(ctx) ← #830 过渡:多步编排
```
## 消费方式
### LeafSpec(MCP 直连叶子命令)
```go
func newDevAppCreateCommand(runner executor.Runner) *cobra.Command {
return NewLeafCommand(LeafSpec{
Use: "create",
Short: "创建开放平台企业内部应用",
Tool: devAppCreateTool,
Safety: contract.SafetySpec{
Effect: "write", Risk: "high",
Confirmation: "user_required", Idempotency: "unknown",
},
ConfirmFirst: true,
Flags: []LeafFlag{
{Name: "name", Usage: "应用名称 (必填)", Bind: "name",
Trim: true, Required: true, RequiredHint: "--name 为必填"},
},
Contract: ContractDecl{
Description: "创建开放平台企业内部应用",
DryRun: &contract.DryRunSpec{PreviewKind: "invocation"},
Interface: &contract.InterfaceSpec{Mode: "composite", Availability: "available", Reason: "create then configure"},
Selection: contract.SelectionSpec{
AgentSummary: "创建钉钉开放平台应用",
UseWhen: []string{"需要新建企业内部应用"},
AvoidWhen: []string{"应用已存在时用 update"},
Examples: []string{`dws dev app create --name "Bot" --dry-run`},
},
},
Call: devAppCall(runner),
})
}
```
`NewLeafCommand` 经 `FromLeafSpec()` 归一为 `corecmd.Spec`,再交 `corecmd.New()` 构建。这是**完全托管模式**:声明 + 执行都归 command。
### 声明元数据模式(既有命令补 Contract)
执行体必须冻结时,用同一套 `LeafSpec` 词汇只声明元数据,写在命令字面量旁:
```go
baseListCmd := &cobra.Command{
Use: "list", Short: "获取 AI 表格列表",
RunE: func(cmd *cobra.Command, args []string) error { /* 原执行体不动 */ },
}
DeclareLeafMetadata(baseListCmd, LeafSpec{
Safety: aitableSafetyRead(),
Contract: ContractDecl{
Description: "列出最近访问的 AI 表格 Base。",
Interface: aitableMCPInterface("list_bases"),
Selection: contract.SelectionSpec{
AgentSummary: "列出最近访问的 AI 表格 Base。",
UseWhen: []string{"只需浏览最近打开过的 Base 时"},
AvoidWhen: []string{"按名称查找优先 base search"},
Examples: []string{"dws aitable base list"},
},
},
})
```
`DeclareLeafMetadata` 调用 `corecmd.AttachContract` 挂 Safety+Contract;不注册 flag、不接管参数投影。可选 `Validate` 与 `ConfirmSafety` 同挂在 **RunE 包装器**内(不是 PreRunE)。当 `Safety.Confirmation=user_required` 时,用**同一份** SafetySpec 包一层 `ConfirmSafety`,保证执行门禁与 Catalog 同源;无 Validate 时确认推迟到 gated `CallTool`,成功返回却未确认则 fail-closed。迁移态入口;新命令仍应走 `NewLeafCommand`。
### 三档路径(当前可接受)
| 档 | 入口 | 说明 |
|---|---|---|
| **Tier1** | `corecmd.New` / `NewLeafCommand` | 完全托管:声明 + 执行都归框架 |
| **Tier2** | `DeclareLeafMetadata` | helpers 迁移态;**Shortcut 也可采用,可接受** |
| **Tier3** | 裸 Cobra | 应逐步收;新增裸叶需补声明或精确排除 |
长期展望(非当前硬要求):更多 Shortcut 可收敛到 mcpbind / 减少仅为参数装配的 `Execute`。**不要**把「Shortcut 必须去掉 Execute / 必须 mcpbind」当作当前门禁;也不要否定 Shortcut + `DeclareLeafMetadata`。
### Shortcut(智能快捷方式,已接入 live mount)
```go
func mount(s Shortcut) *cobra.Command {
return corecmd.New(FromShortcut(s))
}
spec := FromShortcut(Shortcut{
Service: "chat",
Command: "+demo",
Risk: RiskHighWrite,
Flags: []Flag{...},
Execute: func(rt *RuntimeContext) error { ... },
})
```
Shortcut 当前仍保留自身的 `Risk`,adapter 只在边界将它展开成完整
`contract.SafetySpec`;command/Leaf 不再保留该枚举。Shortcut 的 Cobra
type/default/usage provenance 保持不变,command 统一补充 Required、Enum 和关系约束投影。
需要补 Agent Schema 且执行体暂不迁入时,Shortcut 也可走 Tier2
`DeclareLeafMetadata`(与 helpers 同一路径)。
## 文件结构
| 文件 / 包 | 职责 |
|------|------|
| `internal/corecmd/corecmd.go` | 核心类型 + `New` 构建器 + 运行时管线 |
| `internal/corecmd/contract_decl.go` | ContractDecl 载荷类型 + 声明完整性守卫 |
| `internal/corecmd/contract/` | 契约 DTO(`SafetySpec` / `ParamDecl` / `ProductDecl` / `ContractFinalPayload`);**无** Cobra-keyed Final store |
| `internal/corecmd/runtimeannotate/` | `AnnotateRuntime*` 写注解(框架侧;`cli` 薄 re-export) |
| `internal/corecmd/contractfinal/` | ContractFinal Cobra store + `RegisterRuntimeContractFinal`(框架侧;`cli` 薄 re-export) |
| `internal/cli/homology/` | flag/help/schema 同源门禁(`HOM-*`) |
| `internal/helpers/leaf.go` | LeafSpec 门面:`NewLeafCommand`(完全托管)+ `DeclareLeafMetadata`(声明元数据) |
| `internal/shortcut/adapter.go` | FromShortcut 完整映射与 Risk 兼容边界 |
| `internal/shortcut/runner.go` | RuntimeContext;live mount 委托 `corecmd.New(FromShortcut(s))` |
## Schema 投影
声明即 review:代码中的 Contract 声明经过 code review 后直接投影为:
- **Agent Runtime Schema**(`dws.schema.*` Cobra annotations;经 `runtimeannotate` / ContractFinal 嵌入)
- **运行时组装的 SchemaRegistry / Catalog ToolSpec wire**(`RegisterSchemaSourceRoot` → `ResolveSchemaBuild`;`dws schema` / `--all` / 完整 leaf 载荷)
- **CommandMeta 投影**(装配 Once 同步缓存 `map[cli_path]CommandMeta`;`ResolveMeta` / `SafetyForCLIPath` / leaf `--help` Safety 稳态 O(1) 读缓存,与 SchemaRegistry 同源)
- **Dry-run Capabilities**(声明自动索引为 reviewed 能力)
生产权威是 leaf `ContractFinal` / `ProductDecl`(经 `RegisterSchemaSourceRoot` → `ResolveSchemaBuild` 装配进 Catalog);`InstallBuildTimeAgentMetadataJSON` 仅用于 `cmd_schema_catalog` 的 CI/local dump inject,不是生产交付路径。`schema_agent_metadata/` 与 `schema_hints/` 已退役。不再需要外部 hint 文件维护 selection/metadata/dry-run 信息。Catalog/meta-index 路径不得提交。
## 设计原则
1. **声明 vs 执行分离** — Flags/Constraints/Safety/Contract 是声明;Invoke/Validate/PostMount 是执行
2. **单一数据源** — 一份声明驱动 --help、Schema、catalog、runtime 校验
3. **安全字段不互推** — Confirmation 单独驱动确认,Effect/Risk/Idempotency 原样发布
4. **构建时拦截 > 运行时报错** — 声明不完整在命令注册时 panic,不等到用户触发
5. **边界兼容** — Shortcut 暂由 adapter 转换,Leaf 直接声明 SafetySpec
+236
View File
@@ -0,0 +1,236 @@
# 命令框架对比:DWS command vs lark-cli vs GWS
本文档对比 DWS(钉钉工作区 CLI)、lark-cli(飞书 CLI)和 GWS(Google Workspace CLI / gcloud)三套命令框架的设计差异。
## 总览对比
| 维度 | DWS (command) | lark-cli | GWS (gcloud) |
|------|---------------|----------|--------------|
| 语言 | Go | Go | Python (gcloud) / Go (部分) |
| CLI 框架 | cobra | cobra | argparse + calliope |
| 调用底座 | MCP JSON-RPC | Lark REST SDK (`CallAPITyped`) | Google API Client |
| 命令层次 | 2 层:LeafSpec + Shortcut | 3 层:Shortcuts + API Commands + Raw API | 2 层:surface commands + raw |
| Schema 来源 | 代码声明投影 | 代码声明 + 运行时 introspection | API Discovery 文档自动生成 |
| Agent 适配 | 内建 (dws.schema.*) | 内建 (--print-schema) | 外挂 (MCP adapter) |
## 架构对比
### DWS command
```
corecmd.Spec (声明) → corecmd.New() → cobra.Command
│
├── contract.SafetySpec (运行时 + Schema 单一安全来源)
├── FlagSpec[] (参数 + 回退链 + 绑定)
├── Constraint[] (互斥/至少一个)
├── ContractDecl (Agent Selection/DryRun/Interface)
│
└── Invoke / Orchestrate / RunE (执行)
```
**核心特点**:
- 声明与执行严格分离
- SafetySpec 四个独立字段直接对齐 Agent Runtime Schema
- 有效值回退链:flag → alias → env → default
- 框架统一校验、装配、确认、投影
- Schema 从代码声明直接投影,无外部 hint 文件
### lark-cli
```
Shortcut (声明) → runner.Mount() → cobra.Command
│
├── Risk string (确认行为)
├── Scopes / ConditionalScopes (OAuth 权限)
├── Flag[] (参数 + Enum + Input sources)
├── AuthTypes (user/bot)
│
├── DryRun hook → DryRunAPI
├── Validate hook
└── Execute hook → RuntimeContext → CallAPITyped
```
**核心特点**:
- Execute 内直接调 REST API (`CallAPITyped`)
- DryRun 是独立 hook(返回结构化 API 计划)
- 内建 OAuth scope 声明与预检
- `--print-schema --flag-name` 运行时 introspection
- 无 Schema 投影层,Agent 通过 introspection 动态发现
### GWS (gcloud 风格)
```
API Discovery → 代码生成 → surface command
│
├── arguments (从 JSON Schema 自动生成)
├── request/response 映射
└── 自定义 action hook (少量)
```
**核心特点**:
- Schema-first:从 API Discovery 文档自动生成命令
- 参数直接映射 API 字段(flat schema)
- 人工 surface command 是 thin wrapper
- Agent 适配通过 MCP 外部 adapter
## 核心设计差异
### 1. 声明粒度
| 能力 | DWS command | lark-cli | GWS |
|------|-------------|----------|-----|
| 参数别名 + 环境变量回退 | ✅ FlagSpec.Aliases + EnvVar | ❌ 无 | ❌ 无 |
| 声明式约束 (互斥/至少一个) | ✅ Constraint[] | ❌ 只有 Validate hook | ✅ argparse group |
| 安全契约 | ✅ SafetySpec(effect/risk/confirmation/idempotency) | Risk | 无 |
| Schema 投影 (Agent metadata) | ✅ ContractDecl 内建 | ⚠️ 运行时 introspection | ❌ 外挂 |
| 参数绑定 (flag name → API key) | ✅ FlagSpec.Bind | ❌ 手写 | ✅ 自动映射 |
| ConstParams (固定载荷) | ✅ | ❌ 手写在 Execute | ✅ 隐式 |
| 确认门顺序可配 (ConfirmFirst) | ✅ | ❌ 固定顺序 | ❌ 无确认机制 |
### 2. 执行模型
| 维度 | DWS command | lark-cli | GWS |
|------|-------------|----------|-----|
| 参数装配 | 框架自动 (BuildArgs) | 手写 (`runtime.Str()/Bool()`) | 自动映射 |
| 派发方式 | Invoke(ctx, toolArgs) | Execute(ctx, runtime) | 自动调用 |
| 多步编排 | Orchestrate(ctx) | Execute 内链式 CallAPITyped | 不支持 |
| DryRun | 框架统一 (--dry-run flag) | 独立 DryRun hook 返回 API 计划 | 部分命令支持 |
| 错误分类 | apperrors 类型化 | errs.Problem 类型化 | HTTP status 映射 |
### 3. Agent 适配
| 维度 | DWS command | lark-cli | GWS |
|------|-------------|----------|-----|
| 工具发现 | `dws schema --all` (静态 catalog) | `--print-schema` (运行时) | API Discovery |
| 选择指引 | contract.SelectionSpec (UseWhen/AvoidWhen) | Description + Tips | 无 |
| 安全声明 | contract.SafetySpec 直接声明 | Risk string | 无 |
| dry-run 能力声明 | contract.DryRunSpec (reviewed) | DryRun hook 存在性 | 无 |
| 接口模式 | contract.InterfaceSpec (local/mcp/composite) | 隐式 (全部 REST) | 隐式 (全部 REST) |
### 4. Schema 生命周期
```
DWS: 代码声明 → code review → cobra annotation → catalog/metadata JSON
(单一数据源,构建时验证完整性)
lark-cli: 代码声明 → 运行时 introspection → Agent 动态发现
(无离线 catalog,Agent 必须执行命令才能发现)
GWS: API Discovery JSON → 代码生成 → surface command
(Schema-first,但命令行体验受限于 API 形状)
```
## 设计哲学对比
### DWS command 的选择
| 选择 | 理由 | 对比 |
|------|------|------|
| 框架装配参数 | 消除 N 个命令各写一份 toolArgs 装配 | lark-cli 每个 Execute 手动取 flag 值 |
| SafetySpec 单一来源 | confirmation 驱动运行时,其余字段原样发布且互不推导 | lark-cli 只有 Risk 一个维度 |
| 声明式约束 | 构建时校验合法性 + 投影到 Schema + 渲染帮助 | lark-cli 约束隐藏在 Validate 逻辑里 |
| Schema 构建时投影 | 离线 catalog 支持 Agent 批量发现 | lark-cli 需要逐个命令 introspection |
| 有效值回退链 | flag → alias → env 统一语义 | lark-cli 别名是独立 Flag 手动关联 |
| ConfirmFirst | 精确建模遗留语义 | lark-cli 确认始终在 Execute 内 |
### lark-cli 的选择
| 选择 | 理由 | 对比 |
|------|------|------|
| 直连 REST API | 精确控制请求/响应,可处理分页/重试 | DWS 通过 MCP 间接调用 |
| DryRun 返回 API 计划 | Agent 可预览将要发出的真实 HTTP 请求 | DWS dry-run 只展示参数 |
| OAuth scope 声明 | 框架预检权限,失败提前 | DWS 依赖 MCP 层鉴权 |
| `--print-schema` introspection | 运行时发现,无需维护离线 catalog | DWS 需要 re-generate |
| Input sources (file/@path/stdin) | 丰富的输入方式声明 | DWS 无此抽象 |
| PrintFlagSchema | 单 flag 级别的 JSON Schema 暴露 | DWS 只在 catalog 级别 |
### GWS (gcloud) 的选择
| 选择 | 理由 | 对比 |
|------|------|------|
| API Discovery 驱动 | 一份 Schema 生成所有:SDK/CLI/文档 | DWS/lark 手写 |
| Flat parameter 映射 | API 字段 = CLI flag,零转换 | DWS 需要 Bind 映射 |
| 无 shortcut 层 | API 粒度即用户粒度 | DWS/lark 有精选层 |
## 代码量对比
| 框架 | 核心框架代码 | 单命令声明开销 | 备注 |
|------|-------------|---------------|------|
| DWS command | ~1400 行 (command.go + contract_decl.go) | ~20-30 行 (纯声明) | 框架重、单命令轻 |
| lark-cli | ~800 行 (runner.go + types.go + common.go) | ~50-150 行 (声明 + Execute 逻辑) | 框架轻、单命令重 |
| GWS gcloud | ~5000+ 行 (calliope 框架) | ~10 行 (多数自动生成) | 框架最重、单命令最轻 |
## DWS 命令声明示例 vs lark-cli
### DWS (command / LeafSpec)
```go
NewLeafCommand(LeafSpec{
Use: "create",
Short: "创建应用",
Tool: "create_dev_app",
Safety: contract.SafetySpec{
Effect: "write", Risk: "high",
Confirmation: "user_required", Idempotency: "unknown",
},
ConfirmFirst: true,
Flags: []LeafFlag{
{Name: "name", Usage: "应用名称", Bind: "name",
Trim: true, Required: true, RequiredHint: "--name 为必填"},
},
Contract: ContractDecl{
Description: "创建开放平台企业内部应用",
DryRun: &contract.DryRunSpec{PreviewKind: "invocation"},
Interface: &contract.InterfaceSpec{Mode: "composite", Availability: "available", Reason: "create then configure"},
Selection: contract.SelectionSpec{
AgentSummary: "创建钉钉开放平台应用",
UseWhen: []string{"需要新建企业内部应用"},
AvoidWhen: []string{"应用已存在时用 update"},
Examples: []string{`dws dev app create --name "Bot" --dry-run`},
},
},
Call: devAppCall(runner),
})
```
### lark-cli (Shortcut)
```go
var CalendarCreate = common.Shortcut{
Service: "calendar",
Command: "+create",
Description: "Create a new calendar event",
Risk: "write",
Scopes: []string{"calendar:calendar"},
Flags: []common.Flag{
{Name: "summary", Desc: "Event title", Required: true},
{Name: "start", Desc: "Start time (RFC3339)", Required: true},
{Name: "end", Desc: "End time (RFC3339)", Required: true},
{Name: "attendees", Type: "string_slice", Desc: "Attendee emails"},
},
DryRun: func(ctx context.Context, rt *common.RuntimeContext) *common.DryRunAPI {
return &common.DryRunAPI{
Method: "POST",
Path: "/open-apis/calendar/v4/calendars/{id}/events",
Body: buildEventBody(rt),
}
},
Execute: func(ctx context.Context, rt *common.RuntimeContext) error {
body := buildEventBody(rt)
data, err := rt.CallAPITyped("POST",
"/open-apis/calendar/v4/calendars/{id}/events", nil, body)
if err != nil { return err }
return rt.Output(data)
},
}
```
## 适用场景总结
| 场景 | 最适合 | 原因 |
|------|--------|------|
| MCP 后端 + Agent Schema 投影 | **DWS command** | 内建 Schema 声明、SafetySpec 契约、离线 catalog |
| REST API 直连 + OAuth scope 管理 | **lark-cli** | CallAPITyped + scope 预检 + DryRun API 计划 |
| API-first 大规模 surface 生成 | **GWS gcloud** | Discovery 驱动,一份 Schema 生成一切 |
| 多步编排 (跨服务链式调用) | **lark-cli** / DWS Orchestrate | lark 的 CallAPITyped 链式 + DWS 的 Orchestrate |
| 遗留系统迁移 (保持行为等价) | **DWS command** | ConfirmFirst + 回退链 + catalog 漂移门禁 |
+13 -7
View File
@@ -40,7 +40,7 @@ Every command inherits these flags (documented here once, not repeated per comma
- [`dws doc` — DingTalk Doc](#dws-doc) · 21 commands
- [`dws drive` — DingTalk Drive](#dws-drive) · 6 commands
- [`dws minutes` — AI Minutes](#dws-minutes) · 19 commands
- [`dws oa` — OA Approval](#dws-oa) · 9 commands
- [`dws oa` — OA Approval](#dws-oa) · 12 commands
- [`dws report` — Reports](#dws-report) · 7 commands
- [`dws todo` — Todo Tasks](#dws-todo) · 6 commands
@@ -147,8 +147,8 @@ _Group chats, conversations, messages, and robot/webhook integrations._
| `dws chat group members remove` | Remove one or more members from a group chat. | When the agent kicks users who should no longer have access to the group. |
| `dws chat group rename` | Update the display name of a group chat. | When the agent is rebranding or clarifying the purpose of an existing group. |
| `dws chat list-top-conversations` | Fetch the list of conversations the current user has pinned to the top of their chat list. | When the agent needs to prioritize the user's most important conversations in a summary or dashboard. |
| `dws chat message list` | Pull the recent message history of a specific conversation (v2), paginated. | When the agent needs to read what has recently been said in a conversation to summarize or reason about it. |
| `dws chat message list-all` | Search all messages across the current user's conversations within a time range. | When the agent needs to audit or summarize everything the user saw across chats in a window. |
| `dws chat message list` | Pull the recent message history of a specific conversation, including quoted-message context for merged forwards and images. | When the agent needs to read what has recently been said in a conversation and retain the context of replies. |
| `dws chat message list-all` | Search all messages across the current user's conversations within a time range, surfacing any search-entitlement guidance. | When the agent needs to audit or summarize everything the user saw across chats in a window. |
| `dws chat message list-by-sender` | Fetch messages authored by a specific sender across both single and group chats. | When the agent needs to pull everything a particular colleague said recently. |
| `dws chat message list-focused` | Fetch messages from users the current user has marked as "special focus" (starred contacts). | When the agent builds a priority-inbox view highlighting messages from important people. |
| `dws chat message list-mentions` | Fetch messages where the current user was @-mentioned. | When the agent wants to surface items that explicitly require the user's attention. |
@@ -164,16 +164,19 @@ _Group chats, conversations, messages, and robot/webhook integrations._
## `dws contact` — Contact Directory
_Users, departments, and directory lookups._
_Users, departments, directory lookups, and enterprise onboarding._
**6 commands**
**9 commands**
| Command | Description | When to use |
|---|---|---|
| `dws contact account create` | Create a dedicated login account in the current enterprise. | When the user explicitly asks for an enterprise account or login account, rather than a new enterprise organization. |
| `dws contact dept list-members` | List members of a specific department by department ID. | When the agent needs the roster of a department to target communication or build a team overview. |
| `dws contact dept search` | Search departments in the organization's contact directory by keyword. | When the agent needs to resolve a department name to a department ID. |
| `dws contact org create` | Create a new DingTalk enterprise organization. | When the user explicitly asks to create or initialize an enterprise and provides its name and creator display name. |
| `dws contact user get` | Batch-fetch detailed profile information for one or more users by user ID. | When the agent needs names, titles, emails, or departments for a known set of user IDs. |
| `dws contact user get-self` | Retrieve the profile of the currently authenticated user. | When the agent needs to identify who it is acting on behalf of (user ID, name, org). |
| `dws contact user invite` | Invite one employee by mobile number into the current enterprise. | When the user explicitly asks to add an employee and has supplied the employee name and mobile number. |
| `dws contact user search` | Search users in the contact directory by keyword (name, title, etc.). | When the agent needs to resolve a person's display name to a user ID. |
| `dws contact user search-mobile` | Look up a user by mobile phone number. | When the agent has only a phone number and needs to find the corresponding DingTalk user. |
@@ -274,14 +277,17 @@ _AI meeting notes: listing, summary, todos, transcription, recording control, mi
## `dws oa` — OA Approval
_OA approval workflows: list, approve, reject, revoke, records._
_OA approval workflows: inspect forms, forecast routes, create instances, approve, reject, revoke, and audit records._
**9 commands**
**12 commands**
| Command | Description | When to use |
|---|---|---|
| `dws oa approval approve` | Approve a pending approval process instance (task) as the current user. | When the agent acts on a pending approval the user has delegated it to handle. |
| `dws oa approval create-instance` | Create a real approval process instance from validated form values or a complete request payload. | After the agent has inspected the form Schema, forecast the route, resolved any selectable approvers, and obtained explicit user confirmation. |
| `dws oa approval detail` | Retrieve full details of an approval process instance, including form fields, attachments, and state. | When the agent needs to read the content of an approval ticket before deciding on it or summarizing it. |
| `dws oa approval form-schema` | Retrieve the form Schema for an approval template by processCode. | Before collecting or validating values for a new approval instance. |
| `dws oa approval forecast-process` | Forecast the approval route for a template and its proposed form values. | Before creating an instance, especially when the route contains user-selectable approver or notifier nodes. |
| `dws oa approval list-forms` | List approval process templates (forms) the current user is allowed to initiate. | When the agent needs to pick the right approval form before submitting a new request. |
| `dws oa approval list-initiated` | List approval process instances the current user has initiated. | When the agent reviews the status of approvals the user submitted. |
| `dws oa approval list-pending` | List approval process instances currently awaiting action from the current user. | When the agent surfaces "needs your approval" items in the user's inbox. |
+1 -1
View File
@@ -1,7 +1,7 @@
# dws dev 命令集 · Agent 人肉手工评测集(10 条复合用例)
> 性质:**人肉手工评测集**——由测评人逐条手工跑、肉眼核对、人工判分,不是自动化脚本。
> 用途:评测 agent(加载 `dingtalk-dev` 技能后)能否正确处理开放平台 dev 任务。
> 用途:评测 agent(加载 `dingtalk-misc` 的 `references/devapp.md` 后)能否正确处理开放平台 dev 任务。
> 特点:10 条**复合用例**,每条串多个子任务,一条覆盖一类完整场景;10 条合起来覆盖全部 34 个子命令 + 8 类横切行为。
> 约定:所有命令应带 `--format json`;写操作应先 `--dry-run` 预览、用户确认后再 `--yes`;应用定位只用 `--unified-app-id`。
+3 -3
View File
@@ -6,7 +6,7 @@
## 一键安装
`dws dev` 能力已经合入主干并随正式版发布。专用安装脚本会下载预编译二进制 + `dingtalk-dev` skill,**只需要 curl + tar,不需要 git / go / make**。
`dws dev` 能力已经合入主干并随正式版发布。专用安装脚本会下载预编译二进制 + `dingtalk-misc` skill(开放平台应用文档落在 misc),**只需要 curl + tar,不需要 git / go / make**。
### macOS / Linux
@@ -24,7 +24,7 @@ irm https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/ma
1. 从 `DingTalk-Real-AI/dingtalk-workspace-cli` 的最新 Release 下载对应平台的预编译二进制。
2. 安装 `dws` 到默认目录 `~/.local/bin`。
3. 从 Release 的 skills 包里安装 `dingtalk-dev` skill 到本机已检测到的 Agent 目录。
3. 从 Release 的 skills 包里安装 `dingtalk-misc` skill 到本机已检测到的 Agent 目录。
支持这些环境变量(全部可选):
@@ -33,7 +33,7 @@ irm https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/ma
| `DEVAPP_REPO` | 覆盖发布仓库,默认 `DingTalk-Real-AI/dingtalk-workspace-cli` |
| `DEVAPP_VERSION` | 钉某个 release tag,默认取最新 release |
| `DWS_INSTALL_DIR` | 二进制安装目录,默认 `~/.local/bin` |
| `DWS_NO_SKILLS` | 设为 `1` 跳过 `dingtalk-dev` skill 安装 |
| `DWS_NO_SKILLS` | 设为 `1` 跳过 `dingtalk-misc` skill 安装 |
> `dws dev` 已在正式版里,所以你也可以直接用标准安装脚本 `install.sh`,二者都会带上 `dws dev`。
+203
View File
@@ -0,0 +1,203 @@
# 5分钟搭好团队知识库:DWS 让 IT 服务台自己开口答疑,你值得拥有
> 💡 **省流版摘要:**
> 别再用鼠标一个个建文档了,真的没必要!本文带你用 DWS(钉钉命令行工具),5 分钟搭好一个团队知识库:建空间、搭目录、批量导入 FAQ、配权限,全程命令行一步到位。最后再花 30 秒把它挂到钉钉机器人上——同事在群里 @ 一下,知识库自己开口答疑。知识管理员从"搬运工"升级为"甩手掌柜",就是这么简单。
>
> (温馨提示:本文内容可以直接丢给智能体,让智能体一次性逐步安装和使用)
## 一、痛点吐槽:管知识库有多累?
身为企业 IT / 知识管理员,你肯定经历过这些"九九八十一难"……
- **点击马拉松**:建空间点 5 下、建文件夹点 3 下、建一篇文档再点 4 下。一个季度下来,鼠标点击次数比写的字还多。
- **搬运工噩梦**:几百篇历史 FAQ 散落在本地 Word / Markdown 里,要搬进钉钉知识库?复制粘贴到天荒地老,格式还经常翻车。
- **权限苦差**:新同事入职要加权限、转岗要改角色、离职要移除。逐个空间点进去操作,漏一个就是安全隐患。
- **知识沉睡**:库是建好了,可同事还是习惯私聊问你"VPN 又连不上了怎么办"。知识库躺着吃灰,你继续当人肉客服。😭
今天,DWS(DingTalk Workspace CLI)闪亮登场!🌟
你不需要写一行代码,只要在终端敲几行命令,知识库的"建、搬、管、用"全链路一次搞定。更香的是:搭好的知识库可以直接挂到钉钉机器人上,让知识自己开口答疑。
## 二、DWS 是个啥?知识库的"遥控指挥中心"
DWS 是钉钉能力的原子化封装,把复杂的 OpenAPI 打包成简单指令。管知识库这件事,主要靠它的"三驾马车":
| 命令族 | 能干什么 |
|---|---|
| 🗂️ `dws wiki` | 知识库空间、目录节点、成员权限的全生命周期管理 |
| 📄 `dws doc` | 文档内容读写、本地文件批量导入、模板套用 |
| 📁 `dws drive` | 钉盘文件上传下载、全局搜索、归档备份 |
你可以把它想象成知识库的"遥控指挥中心" 🎮——既能你手动按(终端敲命令,比点界面快 10 倍),也能让 AI 帮你按(Claude Code、Qoder 等智能体直接听懂并调用)。
**适合谁用:**
- **企业 IT / 知识管理员**:批量建库、批量导入、批量管权限,脚本化解放双手
- **开发者 / AI 玩家**:把知识库挂到机器人上,打造 24 小时答疑小能手
- **重度钉钉用户 / 效率党**:一条命令搜全库,比在界面里翻目录快得多
## 三、搭好的知识库能帮你做什么?
| 场景 | 玩法 |
|---|---|
| 🛟 IT 服务台 FAQ 库 | VPN、邮箱、打印机常见问题集中沉淀,机器人自动答疑 |
| 📜 制度流程库 | 报销、请假、采购制度批量导入,全文秒搜 |
| 🎓 新人上岗手册 | 按部门建目录,入职即授权限,自助通关 |
| 🤖 知识库 + 机器人 | `--knowledge-source wiki:<spaceId>` 一挂,群里 @ 它就答 |
所想即所得,拒绝画饼,直接上菜!🍽️
## 四、5分钟倒计时,搭好你的团队知识库
> 以下命令全部经过真实环境跑通验证,放心照抄。
### 0. 安装并登录 DWS(约 1 分钟)
把以下指令复制给你的智能体(Claude Code、Qoder、Codex 等)执行,或手动在终端跑:
macOS / Linux:
```bash
curl -fsSL https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.sh | sh
```
Windows(PowerShell):
```powershell
irm https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/main/scripts/install.ps1 | iex
```
登录(提示授权请扫码):
```bash
dws auth login
```
【截图位:dws auth status 显示 token_valid: true】
### 1. 建一个知识库空间(10 秒)
```bash
dws wiki space create --name "IT服务台知识库" --desc "IT 常见问题与制度流程"
```
返回里的 `workspaceId` 就是空间的身份证号,后面每步都要用它。
【截图位:返回 workspaceId 与 spaceUrl】
### 2. 搭目录结构(20 秒)
知识库的结构 = 文件夹节点 + 文档节点。先建分类文件夹:
```bash
dws wiki node create --workspace <workspaceId> --name "常见问题FAQ" --type folder
dws wiki node create --workspace <workspaceId> --name "制度流程" --type folder
```
在文件夹下建一篇空文档(不加 `--folder` 就建在根目录):
```bash
dws wiki node create --workspace <workspaceId> --name "VPN连接失败排查指南" --folder <文件夹nodeId>
```
### 3. 批量导入历史文档(1 分钟,重头戏!)
几百篇本地 FAQ 不用复制粘贴,`doc import` 直接整批灌进知识库,Word、Excel、Markdown、txt 通吃:
```bash
# 单篇导入到指定文件夹
dws doc import --file ./vpn-faq.md --workspace <workspaceId> --folder <文件夹nodeId> --name "VPN连接失败排查指南"
# 批量导入整个目录(bash 一把梭)
for f in ./faq/*.md; do
dws doc import --file "$f" --workspace <workspaceId> --folder <文件夹nodeId>
done
```
已有在线文档想补内容?Markdown 直接写入:
```bash
dws doc update --node <文档nodeId> --content-file ./补充内容.md --mode append
```
【截图位:终端批量导入的滚动输出 + 知识库里齐刷刷的文档列表】
### 4. 配权限:把人拉进来(30 秒)
```bash
# 先用通讯录查到同事的 userId
dws contact user search --query "张三"
# 加为编辑者(--users 支持逗号分隔批量加)
dws wiki member add --workspace <workspaceId> --users <userId1>,<userId2> --role EDITOR
# 随时盘点成员
dws wiki member list --workspace <workspaceId>
```
### 5. 验收:搜一下,秒级命中(10 秒)
```bash
# 库内全文搜索
dws wiki node search --workspace <workspaceId> --query "VPN"
# 全局搜知识库空间
dws wiki space search --query "IT服务台"
```
【截图位:搜索结果命中文档标题】
### 6. 封神一步:挂到机器人上,知识自己开口答疑(30 秒)
如果你已经按《5分钟抱走你的嘴替机器人》建好了钉钉机器人,只需加一个参数:
```bash
dws dev connect --channel claudecode \
--robot-client-id <你的机器人ID> --robot-client-secret <你的机器人密钥> \
--knowledge-source wiki:<workspaceId>
```
机器人会自动从知识库拉取知识并缓存。同事在群里 @ 它问"VPN 连不上怎么办",它直接引用你刚导入的排查指南回答——你,终于不用当复读机了。😎
## 五、进阶使用技巧
### 知识库管理速查表
| 操作 | 命令 |
|---|---|
| 列出我的个人空间 | `dws wiki space list --type myWikiSpace` |
| 列出组织知识库 | `dws wiki space list --type orgWikiSpace` |
| 浏览库内节点树 | `dws wiki node list --workspace <ID> [--folder <nodeId>]` |
| 移动 / 复制节点 | `dws wiki node move` / `dws wiki node copy` |
| 改成员角色 | `dws wiki member update --users <UID> --role VIEWER` |
| 移除成员 | `dws wiki member remove --users <UID>` |
| 删除整个空间 | `dws wiki space delete --workspace <ID>`(进回收站,可恢复) |
### 老手避坑指南 ⛳
- 建在线表格用 `--type axls`,**`asheet` 服务端不支持**,别踩坑。
- `member list` 只返回姓名和角色、**不返回 userId**;要串联 `update` / `remove`,先用 `dws contact user search --query "<姓名>"` 反查。
- 搜索关键词的 flag 是 `--query`,`--keyword` 是遗留别名,新脚本请用 `--query`。
- 所有命令加 `--format json`,配合 `--jq` 过滤字段,写脚本时稳得一批。
- 破坏性操作(删空间、覆盖写文档)前加 `--dry-run` 先预览,确认无误再执行。
### 让机器人答得更好
| 开关 | 作用 |
|---|---|
| `--knowledge-source wiki:<spaceId>` | 从钉钉知识库拉知识作为答疑来源(本文主角) |
| `--knowledge-dir <目录>` | 挂本地 .md/.txt 知识目录,可与上面并存 |
| `--allowed-groups / --allowed-users` | 白名单,只让指定群或人触发 |
| `--daemon` | 后台常驻,关掉终端也不断线 |
## 六、更多 DWS 官方信息
- 钉钉 CLI 官网:https://open.dingtalk.com/dingtalk-cli
- 开源仓库:https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli
- 上一篇姊妹篇:《5分钟抱走你的嘴替机器人:启动钉钉DWS,你值得拥有》
## 七、欢迎加入交流群
【截图位:DWS 交流群二维码】
遇到问题来群里喊一声,官方同学在线答疑。下一篇想看什么?批量备份知识库?给知识库做权限审计?留言区点菜!🍻
+312
View File
@@ -0,0 +1,312 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="DWS Drive Shortcut 与 lark-cli 的业务能力、真实数据 E2E 证据和平台边界分析。">
<title>Drive Shortcut 能力全景|业务评审版</title>
<style>
:root {
color-scheme: light;
--paper: #f4f6f2; --surface: #fffefa; --ink: #17251f; --muted: #66746d;
--line: #dce2dc; --forest: #154f3d; --green: #17765a; --mint: #dff4e8;
--blue: #265f86; --blue-soft: #e7f1f7; --amber: #8c5a09; --amber-soft: #fff2cf;
--red: #a43b32; --red-soft: #fde9e5; --shadow: 0 14px 40px rgba(28, 48, 38, .08);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; color: var(--ink); background: var(--paper); font: 15px/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; }
a { color: inherit; text-decoration: none; }
code { padding: .12rem .38rem; border: 1px solid #d6e0da; border-radius: 6px; color: #174f3e; background: #f1f7f3; font: 600 .88em/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; white-space: nowrap; }
.wrap { width: min(1180px, calc(100% - 40px)); margin: auto; }
.hero { position: relative; overflow: hidden; padding: 64px 0 52px; color: #f7fff9; background: linear-gradient(125deg, #102b22 0%, #154c3c 57%, #1c6b54 100%); }
.hero::after { position: absolute; inset: -180px -100px auto auto; width: 540px; height: 540px; border: 1px solid rgba(255,255,255,.14); border-radius: 50%; box-shadow: 0 0 0 76px rgba(255,255,255,.035), 0 0 0 152px rgba(255,255,255,.025); content: ""; }
.hero-grid { position: relative; z-index: 1; display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(300px, .65fr); gap: 32px; align-items: end; }
.eyebrow, .section-kicker { margin: 0 0 9px; color: #9fd6bd; font-size: 11px; font-weight: 900; letter-spacing: .16em; text-transform: uppercase; }
h1 { margin: 0; font-size: clamp(40px, 6vw, 68px); line-height: 1.03; letter-spacing: -.045em; }
.subtitle { max-width: 760px; margin: 19px 0 0; color: #d3e9de; font-size: 17px; }
.meta-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 21px; }
.meta-pill { padding: 6px 10px; border: 1px solid rgba(255,255,255,.18); border-radius: 999px; color: #d5e9df; background: rgba(255,255,255,.07); font-size: 11px; font-weight: 750; }
.meta-pill.good { color: #bff2d5; border-color: rgba(139,232,179,.38); }
.hero-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.hero-stat { padding: 17px 16px; border: 1px solid rgba(255,255,255,.17); border-radius: 15px; background: rgba(255,255,255,.075); backdrop-filter: blur(8px); }
.hero-stat strong { display: block; font-size: 30px; line-height: 1; }
.hero-stat span { display: block; margin-top: 7px; color: #cce2d7; font-size: 11px; }
.nav { position: sticky; top: 0; z-index: 20; border-bottom: 1px solid var(--line); background: rgba(255,254,250,.94); backdrop-filter: blur(12px); }
.nav .wrap { display: flex; overflow-x: auto; }
.nav a { flex: 0 0 auto; padding: 14px 15px; color: #5b6c64; font-size: 12px; font-weight: 800; }
.nav a:hover { color: var(--forest); background: #eaf3ee; }
main { padding: 38px 0 74px; }
section { margin-top: 50px; scroll-margin-top: 72px; }
section:first-child { margin-top: 0; }
.section-head { display: flex; justify-content: space-between; gap: 28px; align-items: end; margin-bottom: 19px; }
h2 { margin: 0; font-size: clamp(25px, 3.2vw, 36px); line-height: 1.16; letter-spacing: -.025em; }
h3 { margin: 0 0 7px; font-size: 18px; }
.section-desc { max-width: 660px; margin: 0; color: var(--muted); font-size: 13px; }
.callout { padding: 19px 21px; border: 1px solid #bdd8cb; border-left: 4px solid var(--green); border-radius: 13px; background: #ecf7f1; box-shadow: 0 6px 20px rgba(28,48,38,.04); }
.callout strong { color: #13513d; }
.callout.warn { border-color: #ead29a; border-left-color: #b67508; background: #fff8e7; }
.callout.warn strong { color: #784b00; }
.callout.danger { border-color: #e9b7b1; border-left-color: var(--red); background: var(--red-soft); }
.score-grid, .domain-grid, .evidence-grid, .review-grid { display: grid; gap: 13px; }
.score-grid { grid-template-columns: repeat(4, 1fr); margin-top: 16px; }
.score, .domain-card, .evidence-card, .review-card { border: 1px solid var(--line); border-radius: 15px; background: var(--surface); box-shadow: var(--shadow); }
.score { padding: 19px; }
.score strong { display: block; color: var(--forest); font-size: 29px; line-height: 1; }
.score span { display: block; margin-top: 8px; color: var(--muted); font-size: 12px; }
.domain-grid { grid-template-columns: repeat(4, 1fr); }
.domain-card { position: relative; padding: 21px; overflow: hidden; }
.domain-card .number { position: absolute; top: 12px; right: 17px; color: #d5e8de; font: 800 42px/1 ui-monospace, monospace; }
.domain-card p { min-height: 64px; margin: 8px 0 12px; color: var(--muted); font-size: 13px; }
.domain-card small { color: var(--green); font-weight: 800; }
.compare { overflow: hidden; border: 1px solid var(--line); border-radius: 16px; background: var(--surface); box-shadow: var(--shadow); }
.compare-top { display: grid; grid-template-columns: repeat(3, 1fr); }
.compare-column { padding: 21px; border-right: 1px solid var(--line); }
.compare-column:last-child { border-right: 0; }
.compare-column p, .compare-column li { color: var(--muted); font-size: 13px; }
.compare-column ul { margin: 9px 0 0; padding-left: 18px; }
.compare-column.covered { border-top: 5px solid var(--green); }
.compare-column.partial { border-top: 5px solid #c78b22; }
.compare-column.gap { border-top: 5px solid var(--red); }
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px 14px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
th { color: #617069; background: #f7f8f5; font-size: 11px; font-weight: 900; letter-spacing: .03em; }
tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: #f8fbf8; }
.verdict, .badge { display: inline-flex; align-items: center; padding: 3px 8px; border-radius: 999px; font-size: 10px; font-weight: 900; white-space: nowrap; }
.v-covered, .badge.read { color: #116045; background: var(--mint); }
.v-ahead, .badge.smart { color: #20577c; background: var(--blue-soft); }
.v-partial, .badge.write { color: #7b510a; background: var(--amber-soft); }
.v-gap, .badge.high { color: #8f3028; background: var(--red-soft); }
.truth-grid { display: grid; grid-template-columns: 1.1fr .9fr; gap: 14px; }
.truth-card { padding: 22px; border: 1px solid var(--line); border-radius: 15px; background: var(--surface); box-shadow: var(--shadow); }
.truth-step { display: grid; grid-template-columns: 30px 1fr; gap: 11px; margin-top: 13px; }
.truth-step b { display: grid; width: 28px; height: 28px; place-items: center; border-radius: 50%; color: #fff; background: var(--forest); font-size: 12px; }
.truth-step strong, .truth-step span { display: block; }
.truth-step span { color: var(--muted); font-size: 12px; }
.toolbar { display: grid; grid-template-columns: minmax(260px, 1fr) 180px 180px auto; gap: 10px; align-items: center; margin: 18px 0; padding: 13px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface); }
input, select { width: 100%; min-height: 42px; padding: 9px 11px; border: 1px solid #ccd7d0; border-radius: 9px; color: var(--ink); background: #fff; font: inherit; }
input:focus, select:focus { outline: 3px solid rgba(23,118,90,.13); border-color: var(--green); }
.result-count { color: var(--muted); font-size: 12px; text-align: right; white-space: nowrap; }
.catalog { overflow: hidden; border: 1px solid var(--line); border-radius: 16px; background: var(--surface); box-shadow: var(--shadow); }
.shortcut-row { display: grid; grid-template-columns: 215px minmax(0, 1fr) 200px; gap: 16px; align-items: center; padding: 14px 17px; border-bottom: 1px solid var(--line); }
.shortcut-row:last-child { border-bottom: 0; }
.shortcut-row:hover { background: #f8fbf8; }
.command code { font-size: 12px; }
.row-main p { margin: 0; font-size: 13px; }
.row-main small { color: var(--muted); }
.badges { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 5px; }
.hidden-row { display: none; }
.evidence-grid { grid-template-columns: repeat(4, 1fr); }
.evidence-card { padding: 19px; }
.evidence-card strong { display: block; color: var(--forest); font-size: 23px; }
.evidence-card p { margin: 7px 0 0; color: var(--muted); font-size: 12px; }
.timeline { margin-top: 15px; border-left: 2px solid #bdd7ca; }
.event { position: relative; padding: 0 0 17px 22px; }
.event::before { position: absolute; left: -7px; top: 5px; width: 12px; height: 12px; border: 3px solid var(--paper); border-radius: 50%; background: var(--green); content: ""; }
.event b { display: block; }
.event span { color: var(--muted); font-size: 12px; }
.review-grid { grid-template-columns: repeat(3, 1fr); }
.review-card { position: relative; padding: 20px; }
.review-card .review-num { color: #b8d1c4; font: 800 12px/1 ui-monospace, monospace; letter-spacing: .1em; }
.review-card p { margin: 7px 0 0; color: var(--muted); font-size: 13px; }
footer { margin-top: 52px; padding: 23px 0; border-top: 1px solid var(--line); color: var(--muted); font-size: 11px; }
@media (max-width: 900px) { .hero-grid, .truth-grid { grid-template-columns: 1fr; } .score-grid, .domain-grid, .evidence-grid { grid-template-columns: repeat(2, 1fr); } .review-grid { grid-template-columns: 1fr 1fr; } .shortcut-row { grid-template-columns: 180px 1fr; } .badges { grid-column: 1 / -1; justify-content: flex-start; } }
@media (max-width: 620px) { .wrap { width: min(100% - 24px, 1180px); } .hero { padding: 44px 0 38px; } .hero-stats, .score-grid, .domain-grid, .evidence-grid, .review-grid, .compare-top { grid-template-columns: 1fr; } .compare-column { border-right: 0; border-bottom: 1px solid var(--line); } .toolbar { grid-template-columns: 1fr; } .result-count { text-align: left; } .shortcut-row { grid-template-columns: 1fr; } }
@media print { body { background: #fff; } .hero { color: var(--ink); background: #fff; border-bottom: 2px solid var(--ink); } .subtitle, .meta-pill, .hero-stat span { color: #425249; } .hero-stat { border-color: #aebbb3; } .nav, .toolbar { display: none; } .score, .domain-card, .compare, .truth-card, .catalog, .evidence-card, .review-card { box-shadow: none; break-inside: avoid; } }
</style>
</head>
<body>
<header class="hero">
<div class="wrap hero-grid">
<div>
<p class="eyebrow">Business Review · Drive</p>
<h1>Drive Shortcut<br>能力全景</h1>
<p class="subtitle">从 lark-cli 对齐出发,但不止于命令名:逐项审查输入、校验、多步编排、失败语义、真实字节和平台边界。</p>
<div class="meta-row">
<span class="meta-pill good">真实账号 E2E 已执行</span>
<span class="meta-pill">28 个公开入口</span>
<span class="meta-pill">统一 Result / Pagination</span>
<span class="meta-pill">报告已移除 PII / 凭证 / 业务正文</span>
</div>
</div>
<div class="hero-stats" aria-label="关键统计">
<div class="hero-stat"><strong>38</strong><span>lark-cli Drive 逐项审查</span></div>
<div class="hero-stat"><strong>26</strong><span>已覆盖或跨产品路由</span></div>
<div class="hero-stat"><strong>7</strong><span>部分对齐,边界已公开</span></div>
<div class="hero-stat"><strong>5</strong><span>客观不可对齐能力</span></div>
</div>
</div>
</header>
<nav class="nav"><div class="wrap"><a href="#overview">全景</a><a href="#compare">Lark 对齐</a><a href="#ahead">超越项</a><a href="#truth">真实语义</a><a href="#catalog">完整目录</a><a href="#e2e">E2E</a><a href="#review">评审</a></div></nav>
<main class="wrap">
<section id="overview">
<div class="section-head"><div><p class="section-kicker">Executive summary</p><h2>28 个公开入口,覆盖文件完整生命周期</h2></div><p class="section-desc">另有 <code>+publish-set</code> 已实现契约和读回逻辑,但真实普通文件与在线文档均被服务端拒绝,因此保持 unavailable,不进入 Agent 公开目录。</p></div>
<div class="callout"><strong>结论:</strong>Drive 已从 9 个偏原子入口扩展为 28 个可发现 Shortcut。它不仅补齐 Lark 的核心文件、版本和状态任务,还通过严格响应合同、真实落盘、写后读回、回收恢复和个人收藏形成更可审计的钉盘工作流。</div>
<div class="score-grid">
<article class="score"><strong>29</strong><span>已审查注册项(含 1 unavailable)</span></article>
<article class="score"><strong>25</strong><span>公开主能力 / 语义适配</span></article>
<article class="score"><strong>3</strong><span>公开兼容入口</span></article>
<article class="score"><strong>3</strong><span>高风险写入口,均需确认</span></article>
</div>
</section>
<section>
<div class="section-head"><div><p class="section-kicker">Capability map</p><h2>四个业务域</h2></div><p class="section-desc">目录按用户任务组织;兼容命令不重复计为新增能力。</p></div>
<div class="domain-grid">
<article class="domain-card"><span class="number">09</span><h3>发现与检查</h3><p>严格目录分页、搜索、最近访问,以及元数据、统计和封面聚合检查。</p><small>+list · +search · +recent · +inspect</small></article>
<article class="domain-card"><span class="number">09</span><h3>文件生命周期</h3><p>创建目录、上传下载、快捷方式、在线对象复制、移动重命名、删除与恢复。</p><small>+upload · +download · +rename · +recycle-restore</small></article>
<article class="domain-card"><span class="number">06</span><h3>个人与公开状态</h3><p>回收站清单、收藏闭环和互联网公开状态的独立安全域。</p><small>+star-list · +star-add · +publish-get</small></article>
<article class="domain-card"><span class="number">04</span><h3>历史版本</h3><p>版本列表、精确定位、真实字节下载与高风险回滚读回。</p><small>+version-history · +version-download · +version-revert</small></article>
</div>
</section>
<section id="compare">
<div class="section-head"><div><p class="section-kicker">Lark alignment</p><h2>对齐业务语义,不追求同名率</h2></div><p class="section-desc">38 项逐项核对。评论、导入导出和成员权限在 DWS 由更成熟的 Doc 或原子权限入口承接,不在 Drive 再复制一套。</p></div>
<div class="compare">
<div class="compare-top">
<article class="compare-column covered"><h3>26 · 已覆盖 / 路由</h3><p>上传下载、目录与快捷方式、版本、移动删除、状态、搜索、评论、导入导出和成员任务均有真实入口。</p></article>
<article class="compare-column partial"><h3>7 · 部分对齐</h3><p>预览、resolve/reaction、push/pull、权限申请与 setting 受对象模型或接口粒度约束,明确保留有限语义。</p></article>
<article class="compare-column gap"><h3>5 · 客观缺口</h3><p>删除评论恢复、普通文件版本删除、安全标签读写、可靠双向目录同步缺少必要下层接口。</p></article>
</div>
<div class="table-wrap"><table><thead><tr><th>Lark 任务组</th><th>DWS 主路径</th><th>结论</th><th>关键差异与交付决定</th></tr></thead><tbody>
<tr><td>upload / folder / shortcut / download</td><td><code>drive +upload</code> 等</td><td><span class="verdict v-ahead">增强</span></td><td>工作目录边界、OSS PUT、严格 commit、no-clobber、原子落盘、非零字节和读回验证。</td></tr>
<tr><td>preview / cover</td><td><code>drive +cover</code></td><td><span class="verdict v-partial">部分</span></td><td>封面/缩略图可读;没有等价的服务端多格式预览转换,不扩大宣称。</td></tr>
<tr><td>comments / replies</td><td><code>doc +comment-*</code> / <code>doc +review</code></td><td><span class="verdict v-covered">路由</span></td><td>评论归在线文档协作域;独立 resolve、reaction identity 与删除后恢复仍受接口限制。</td></tr>
<tr><td>export / import / task result</td><td><code>doc +export</code> / <code>doc +import</code></td><td><span class="verdict v-ahead">增强</span></td><td>提交、轮询、恢复、安全下载形成类型化闭环,不保留泛化下划线命令。</td></tr>
<tr><td>version history / get / revert</td><td><code>drive +version-*</code></td><td><span class="verdict v-ahead">增强</span></td><td>严格分页、精确版本、历史字节落盘、回滚前预检与终态读回;历史版本删除无接口。</td></tr>
<tr><td>status / inspect</td><td><code>drive +inspect</code></td><td><span class="verdict v-ahead">超越</span></td><td>元数据为必达结果,统计、公开状态和封面按需 fan-out;可选失败为 partial_success。</td></tr>
<tr><td>push / pull / sync</td><td><code>+upload</code> / <code>+download</code> 单文件</td><td><span class="verdict v-partial">部分</span></td><td>不在缺少稳定 hash、rename/delete journal 和冲突向量时制造危险目录同步。</td></tr>
<tr><td>member / permission</td><td><code>doc +access-*</code> / <code>drive permission</code></td><td><span class="verdict v-covered">路由</span></td><td>协作者权限与互联网公开是两个安全域;申请权限需真实上下文,未伪装为通用 Shortcut。</td></tr>
<tr><td>secure labels</td><td>无等价</td><td><span class="verdict v-gap">缺口</span></td><td>当前 DWS/钉钉下层没有 Drive 安全标签目录和写入接口,不能用普通权限代替。</td></tr>
<tr><td>search</td><td><code>drive +search</code> / <code>doc +search</code></td><td><span class="verdict v-ahead">增强</span></td><td>文件与在线文档按域路由;文件搜索严格验证数组、过滤和分页。</td></tr>
</tbody></table></div>
</div>
<div class="callout warn" style="margin-top:14px"><strong>普通文件 copy 边界:</strong>钉钉现有复制接口对普通文件产生 <code>.dlink</code>,不是字节独立副本。因此 <code>+copy</code> 只接受在线对象;普通文件快捷入口用 <code>+create-shortcut</code>,独立副本使用 <code>+download</code> 后 <code>+upload</code>。</div>
</section>
<section id="ahead">
<div class="section-head"><div><p class="section-kicker">Beyond parity</p><h2>DWS 可主推的八个差异化点</h2></div><p class="section-desc">价值来自正确性与完整闭环,而不是额外注册同义命令。</p></div>
<div class="domain-grid">
<article class="domain-card"><h3>严格目录语义</h3><p><code>+list</code> / <code>+recent</code> 只有服务端明确返回数组时才接受空集合。</p><small>缺字段 ≠ 空目录</small></article>
<article class="domain-card"><h3>聚合检查</h3><p><code>+inspect</code> 一次汇总身份、统计、公开状态和封面,并保留局部失败。</p><small>partial_success 可审计</small></article>
<article class="domain-card"><h3>真实文件传输</h3><p>上传执行完整事务;下载验证受控路径、覆盖策略、原子发布和字节。</p><small>不是只返回临时 URL</small></article>
<article class="domain-card"><h3>回收恢复闭环</h3><p>从 <code>recycleItemId</code> 恢复后读取真实节点,证明资源确实回到可访问状态。</p><small>恢复后读回</small></article>
<article class="domain-card"><h3>个人收藏闭环</h3><p>收藏、列表、取消收藏覆盖完整用户偏好过程,并保留分页。</p><small>add → list → remove</small></article>
<article class="domain-card"><h3>版本真实字节</h3><p>除元数据外可下载任意已知历史版本,并用本地字节核验回滚结果。</p><small>version-download</small></article>
<article class="domain-card"><h3>重命名终态</h3><p>处理服务端扩展名规则,再读取节点确认最终名称,避免重复扩展名。</p><small>write → read-back</small></article>
<article class="domain-card"><h3>公开域诚实降级</h3><p>查询和关闭可验证;开启在 eligible 节点闭环完成前保持 unavailable。</p><small>不把 notSupported 当成功</small></article>
</div>
</section>
<section id="truth">
<div class="section-head"><div><p class="section-kicker">Truthful execution</p><h2>空数组不再是“看起来成功”</h2></div><p class="section-desc">合法业务空集合可以成功,但必须先证明响应结构、元素类型和分页语义成立。内部错误、缺字段与坏投影必须失败。</p></div>
<div class="truth-grid">
<article class="truth-card"><h3>四层成功证据</h3>
<div class="truth-step"><b>1</b><div><strong>传输成功</strong><span>进程成功,MCP / HTTP 没有显式错误。</span></div></div>
<div class="truth-step"><b>2</b><div><strong>响应合同</strong><span>对象、数组、success 标志和元素类型与命令声明一致。</span></div></div>
<div class="truth-step"><b>3</b><div><strong>业务终态</strong><span>写命令必须获得新 ID、终态证据或后续元数据读回。</span></div></div>
<div class="truth-step"><b>4</b><div><strong>产物校验</strong><span>下载必须落盘、非零字节;关键链路比较大小和 SHA-256。</span></div></div>
</article>
<article class="truth-card"><h3>明确失败的情况</h3>
<ul><li>空响应、缺少预期集合字段或集合类型错误。</li><li>集合存在坏元素,不能投影时静默丢弃。</li><li><code>success=false</code>、写响应没有 ID 或读回不一致。</li><li>inspect 的可选分支失败却返回整体 success。</li><li>下载得到空文件、越界路径或覆盖既有文件。</li><li>普通文件 copy 返回快捷链接却声称独立副本。</li></ul>
</article>
</div>
</section>
<section id="catalog">
<div class="section-head"><div><p class="section-kicker">Full catalog</p><h2>28 个公开 Shortcut 完整目录</h2></div><p class="section-desc">16 个只读、9 个普通写、3 个高风险写;3 个历史入口保留兼容但不作为新 Agent 主路径。</p></div>
<div class="toolbar"><input id="q" type="search" placeholder="搜索命令或用途,例如 版本、回收、+inspect…" aria-label="搜索 Shortcut"><select id="domain"><option value="all">全部业务域</option><option value="discover">发现与检查</option><option value="lifecycle">文件生命周期</option><option value="personal">个人与公开</option><option value="version">历史版本</option></select><select id="risk"><option value="all">全部风险</option><option value="read">只读</option><option value="write">普通写</option><option value="high">高风险写</option></select><span id="result-count" class="result-count">显示 28 / 28</span></div>
<div class="catalog">
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +list</code></div><div class="row-main"><p>严格分页列出目录,保留游标,区分显式空目录和畸形响应。</p><small>发现与检查</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +inspect</code></div><div class="row-main"><p>聚合元数据与可选统计、公开状态、封面;局部失败如实报告。</p><small>发现与检查</small></div><div class="badges"><span class="badge read">READ</span><span class="badge smart">SMART</span></div></article>
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +info</code></div><div class="row-main"><p>历史元数据兼容入口;新场景优先使用 +inspect。</p><small>兼容入口</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +search</code></div><div class="row-main"><p>按关键词、类型、扩展名、创建人、时间和分页搜索钉盘文件。</p><small>发现与检查</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +find-file</code></div><div class="row-main"><p>历史文件搜索兼容入口;新场景优先使用 +search。</p><small>兼容入口</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +search-docs</code></div><div class="row-main"><p>历史跨域搜索入口;新的在线文档搜索路由 doc +search。</p><small>兼容入口</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +recent</code></div><div class="row-main"><p>读取最近访问或编辑列表,支持创建人筛选并保留分页。</p><small>发现与检查</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +stats</code></div><div class="row-main"><p>读取访问、编辑、评论、点赞、预览和下载统计。</p><small>发现与检查</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="discover" data-risk="read"><div class="command"><code>dws drive +cover</code></div><div class="row-main"><p>读取封面或缩略图;不宣称服务端多格式预览。</p><small>发现与检查</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +upload</code></div><div class="row-main"><p>上传凭证、OSS PUT、严格提交和远端元数据读回的一体化事务。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span><span class="badge smart">SMART</span></div></article>
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="read"><div class="command"><code>dws drive +download</code></div><div class="row-main"><p>安全落盘、no-clobber、原子发布并验证非零字节。</p><small>文件生命周期</small></div><div class="badges"><span class="badge read">READ</span><span class="badge smart">SMART</span></div></article>
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +create-folder</code></div><div class="row-main"><p>创建文件夹后要求新 ID,并读回名称验证。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span></div></article>
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +create-shortcut</code></div><div class="row-main"><p>创建快捷方式并读回,明确区别于独立副本。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span></div></article>
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +copy</code></div><div class="row-main"><p>复制在线对象;普通文件预检拒绝,避免把 .dlink 当副本。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span><span class="badge smart">SMART</span></div></article>
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +move</code></div><div class="row-main"><p>移动到指定文件夹或知识库位置,语义与 copy/shortcut 消歧。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span></div></article>
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +rename</code></div><div class="row-main"><p>重命名后读取真实节点,验证最终名称和扩展名。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span><span class="badge smart">SMART</span></div></article>
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="high"><div class="command"><code>dws drive +delete</code></div><div class="row-main"><p>将确认过的节点移入回收站,要求 success=true 终态证据。</p><small>文件生命周期</small></div><div class="badges"><span class="badge high">HIGH · CONFIRM</span></div></article>
<article class="shortcut-row" data-tool data-domain="lifecycle" data-risk="write"><div class="command"><code>dws drive +recycle-restore</code></div><div class="row-main"><p>按回收项 ID 恢复,并读回恢复后的节点。</p><small>文件生命周期</small></div><div class="badges"><span class="badge write">WRITE · CONFIRM</span><span class="badge smart">SMART</span></div></article>
<article class="shortcut-row" data-tool data-domain="personal" data-risk="read"><div class="command"><code>dws drive +recycle-list</code></div><div class="row-main"><p>严格分页列出回收项并稳定投影 recycleItemId。</p><small>个人与公开</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="personal" data-risk="read"><div class="command"><code>dws drive +star-list</code></div><div class="row-main"><p>严格分页列出当前用户收藏并保留游标。</p><small>个人与公开</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="personal" data-risk="write"><div class="command"><code>dws drive +star-add</code></div><div class="row-main"><p>以幂等用户偏好语义收藏指定节点。</p><small>个人与公开</small></div><div class="badges"><span class="badge write">WRITE</span></div></article>
<article class="shortcut-row" data-tool data-domain="personal" data-risk="write"><div class="command"><code>dws drive +star-remove</code></div><div class="row-main"><p>以幂等用户偏好语义取消收藏指定节点。</p><small>个人与公开</small></div><div class="badges"><span class="badge write">WRITE</span></div></article>
<article class="shortcut-row" data-tool data-domain="personal" data-risk="read"><div class="command"><code>dws drive +publish-get</code></div><div class="row-main"><p>只读查询互联网公开状态,不沿用错误的写风险标签。</p><small>个人与公开</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="personal" data-risk="high"><div class="command"><code>dws drive +publish-unset</code></div><div class="row-main"><p>关闭互联网公开并读回验证外链状态。</p><small>个人与公开</small></div><div class="badges"><span class="badge high">HIGH · CONFIRM</span><span class="badge smart">SMART</span></div></article>
<article class="shortcut-row" data-tool data-domain="version" data-risk="read"><div class="command"><code>dws drive +version-history</code></div><div class="row-main"><p>严格分页列出普通文件历史版本。</p><small>历史版本</small></div><div class="badges"><span class="badge read">READ</span></div></article>
<article class="shortcut-row" data-tool data-domain="version" data-risk="read"><div class="command"><code>dws drive +version-get</code></div><div class="row-main"><p>按正整数版本号精确匹配,零命中显式失败。</p><small>历史版本</small></div><div class="badges"><span class="badge read">READ</span><span class="badge smart">SMART</span></div></article>
<article class="shortcut-row" data-tool data-domain="version" data-risk="read"><div class="command"><code>dws drive +version-download</code></div><div class="row-main"><p>预检版本后安全下载历史字节,要求非零产物。</p><small>历史版本</small></div><div class="badges"><span class="badge read">READ</span><span class="badge smart">SMART</span></div></article>
<article class="shortcut-row" data-tool data-domain="version" data-risk="high"><div class="command"><code>dws drive +version-revert</code></div><div class="row-main"><p>验证版本存在后回滚,并读取当前节点终态。</p><small>历史版本</small></div><div class="badges"><span class="badge high">HIGH · CONFIRM</span><span class="badge smart">SMART</span></div></article>
</div>
<div class="callout warn" style="margin-top:14px"><strong>未公开入口:</strong><code>+publish-set</code> 的安全契约和 set→get 读回代码已存在,但真实后端返回 <code>operation.notSupported</code>。在找到 eligible 节点并完成 set→get→unset 闭环前,不进入公开 Agent catalog。</div>
</section>
<section id="e2e">
<div class="section-head"><div><p class="section-kicker">Real-data E2E</p><h2>真实数据验证,不用空结果证明成功</h2></div><p class="section-desc">测试在隔离目录创建临时资源,覆盖读取、写入、下载、版本、收藏、回收和清理。资源 ID、账号、URL、上传凭证、绝对路径和业务正文均未进入报告。</p></div>
<div class="evidence-grid">
<article class="evidence-card"><strong>39,838 B</strong><p>真实文件上传后下载字节数;与源文件 SHA-256 完全一致。</p></article>
<article class="evidence-card"><strong>2 versions</strong><p>覆盖写入生成两个版本;精确查询、历史下载和回滚全部读回。</p></article>
<article class="evidence-card"><strong>4 / 5</strong><p>隔离夹具中搜索命中 4 项、最近列表命中 5 项,证明非空投影链路。</p></article>
<article class="evidence-card"><strong>0 remain</strong><p>测试结束后隔离根目录无残留;临时资源进入回收站并完成本地清理。</p></article>
</div>
<div class="timeline">
<div class="event"><b>创建与发现</b><span>创建两个隔离目录并读回;+list 命中真实节点,由此发现并修复 dentryId 与 32 字符 fileId 混用。</span></div>
<div class="event"><b>上传与下载</b><span>真实 OSS 上传、远端元数据读回、下载、no-clobber 二次路径、大小与 SHA-256 一致性全部通过。</span></div>
<div class="event"><b>检查与个人状态</b><span>+inspect(含 stats / publish / cover)、+stats、+cover、收藏 add→list→remove 通过。</span></div>
<div class="event"><b>版本闭环</b><span>覆盖文件产生两个版本;history/get/download/revert 通过,回滚后最新字节与原始内容一致。</span></div>
<div class="event"><b>复制、移动与命名</b><span>在线文档 copy 通过;普通文件 .dlink 被修正为预检拒绝;move 往返、rename 扩展名规范化通过。</span></div>
<div class="event"><b>删除与恢复</b><span>delete→recycle-list→recycle-restore 通过,真实回收响应字段已按后端形态修正。</span></div>
<div class="event"><b>平台负向证据</b><span>publish-set 对普通文件和在线文档均明确返回不支持,因此保持 unavailable;没有把失败改写成空对象成功。</span></div>
<div class="event"><b>清理</b><span>隔离目录进入回收站,根目录残留计数为零;本地下载产物删除。</span></div>
</div>
</section>
<section id="review">
<div class="section-head"><div><p class="section-kicker">Review prompts</p><h2>建议业务评审重点确认</h2></div><p class="section-desc">这些是需要接受的产品边界,不是被空结果遮蔽的实现问题。</p></div>
<div class="review-grid">
<article class="review-card"><span class="review-num">01</span><h3>是否接受 26 / 7 / 5 结论?</h3><p>按用户任务计覆盖、部分与缺口,不用同名命令数量代替语义保真。</p></article>
<article class="review-card"><span class="review-num">02</span><h3>普通文件 copy 是否足够清晰?</h3><p>服务端无法提供原子独立副本;快捷方式与下载后上传两条替代路径已明确。</p></article>
<article class="review-card"><span class="review-num">03</span><h3>是否拒绝不可靠目录 sync?</h3><p>缺稳定 hash、删除/重命名日志和冲突向量时,不发布可能覆盖数据的双向同步。</p></article>
<article class="review-card"><span class="review-num">04</span><h3>跨产品路由是否合理?</h3><p>评论、导入导出和协作者权限优先复用 Doc 成熟入口,不在 Drive 制造同义表面。</p></article>
<article class="review-card"><span class="review-num">05</span><h3>publish-set 是否继续 unavailable?</h3><p>建议维持,直到真实 eligible 节点完成开启、查询、关闭的可恢复闭环。</p></article>
<article class="review-card"><span class="review-num">06</span><h3>下一批后端解锁优先级?</h3><p>建议依次评估普通文件原子 copy、同步所需版本信号、安全标签和评论恢复接口。</p></article>
</div>
</section>
</main>
<footer><div class="wrap">依据:DWS 最终 Shortcut catalog / Schema、Drive 实现与测试、真实账号 E2E、lark-cli Drive registrations 与实现。范围仅含 Drive Shortcut 及必要跨产品路由;不包含原子命令总表。所有业务标识、凭证、签名 URL、用户信息和正文均已脱敏。</div></footer>
<script>
const q = document.querySelector('#q');
const domain = document.querySelector('#domain');
const risk = document.querySelector('#risk');
const rows = [...document.querySelectorAll('[data-tool]')];
const count = document.querySelector('#result-count');
function filterTools() {
const needle = q.value.trim().toLocaleLowerCase('zh-CN');
let visible = 0;
rows.forEach((row) => {
const show = (!needle || row.textContent.toLocaleLowerCase('zh-CN').includes(needle)) && (domain.value === 'all' || row.dataset.domain === domain.value) && (risk.value === 'all' || row.dataset.risk === risk.value);
row.classList.toggle('hidden-row', !show);
if (show) visible += 1;
});
count.textContent = `显示 ${visible} / ${rows.length}`;
}
[q, domain, risk].forEach((control) => control.addEventListener('input', filterTools));
</script>
</body>
</html>
+82
View File
@@ -0,0 +1,82 @@
# Drive Shortcut 对齐与超越 Lark CLI
## 目标与判定口径
本轮以 Lark CLI `drive` 的 38 个 shortcut 为对照,但不把“同名命令数量”当完成标准。对齐按用户任务判定:
1. `drive +...` 有更稳定的 Agent 主入口时,提供 Shortcut,并发布 Selection、Safety、Result 与 Pagination。
2. 钉钉已经在其他产品提供更成熟入口时,Skill 明确跨产品路由,不在 Drive 重复实现。
3. 只有原子能力且 Shortcut 不增加校验、编排或投影价值时,保留 Runtime Schema leaf,不制造同义别名。
4. 下层接口不存在或无法满足相同语义时,明确记录 gap;不得用空数组、空对象或只返回任务提交结果伪装完成。
成功判定统一为:进程成功 + 统一结果 `ok=true/outcome=success` + 必要业务字段 + 真实数据读回或本地字节校验。服务端显式返回空数组可以是合法业务空结果;空响应、缺少数组、数组类型错误、坏元素、`success=false`、写入缺少终态证据都必须失败。
## Lark 38 项映射
| Lark Drive shortcut | DWS 路由 | 结论与原因 |
|---|---|---|
| `+upload` | `drive +upload` | 对齐并增强:工作目录边界、OSS PUT、严格 commit、元数据读回。 |
| `+create-folder` | `drive +create-folder` | 对齐并增强:要求新 fileId 和名称读回。 |
| `+create-shortcut` | `drive +create-shortcut` | 对齐并增强:明确 shortcut≠copy,创建后读回。 |
| `+download` | `drive +download` | 对齐并增强:真实落盘、no-clobber、原子发布、非零字节。 |
| `+preview` | `drive +cover`(有限) | 不完全对齐:钉钉当前只提供封面/缩略图读取,没有等价的服务端多格式预览转换接口。 |
| `+cover` | `drive +cover` | 对齐:严格读取封面/缩略图对象。 |
| `+add-comment` | `doc +comment-create` | 用户任务对齐;评论归在线文档协作域,Drive 不复制一套。 |
| `+list-comments` | `doc +comment-list` | 用户任务对齐;Doc 已有类型、状态与分页。 |
| `+batch-query-comments` | `doc +review` / `doc +comment-list` | 超越:可聚合未解决评论与确定性正文上下文;跨文档批量仍由调用方按节点编排。 |
| `+resolve-comment` | `doc +comment-update`(有限) | 部分对齐:DWS 可更新评论,但当前下层未声明独立 resolve 状态接口。 |
| `+restore-comment` | 无等价 | gap:钉钉当前下层未暴露恢复已删除评论的等价能力。 |
| `+add-reply` | `doc +comment-reply` | 对齐。 |
| `+list-replies` | `doc +comment-list` | 用户任务对齐:评论列表返回回复上下文;无独立 Drive reply 目录。 |
| `+update-reply` | `doc +comment-update` | 对齐到评论/回复统一更新语义。 |
| `+delete-reply` | `doc +comment-delete` | 对齐到评论/回复统一删除语义,高风险确认。 |
| `+react-reply` | `doc +comment-reply`(有限) | 部分对齐:支持表情回复;不声称拥有 Lark 的独立 reaction identity。 |
| `+export` | `doc +export` | 用户任务对齐并增强:提交、轮询、安全下载一体化。 |
| `+export-download` | `doc +export` / `doc +export-get` | 超越:常规一体化,`+export-get` 仅作中断恢复。 |
| `+import` | `doc +import` | 用户任务对齐并增强:转换白名单、上传 fallback、轮询终态。 |
| `+version-history` | `drive +version-history` | 对齐并增强:严格空结果与分页。 |
| `+version-get` | `drive +version-get` | 对齐并增强:精确版本号,零命中失败。 |
| `+version-revert` | `drive +version-revert` | 对齐并增强:版本预检、高风险确认、节点读回。 |
| `+version-delete` | 无等价 | gap:钉钉当前普通文件版本接口没有删除历史版本能力。 |
| `+move` | `drive +move` | 对齐;与 copy/shortcut 明确消歧并发布确认。 |
| `+delete` | `drive +delete` | 对齐;移入回收站、高风险确认、终态证据。 |
| `+status` | `drive +inspect` | 超越:远端身份、统计、公开状态和封面按需聚合;不伪装成本地同步状态。 |
| `+push` | `drive +upload`(单文件) | 部分对齐:单文件上传可靠;没有可靠的目录 diff、冲突和远端删除传播语义,因此不提供同名批量 push。 |
| `+pull` | `drive +download`(单文件) | 部分对齐:单文件下载可靠;目录级增量拉取需稳定路径、hash 与冲突策略,当前接口不完整。 |
| `+sync` | 无等价 | gap:在缺少稳定远端内容 hash、rename/delete journal 和冲突版本向量时,双向同步会有数据覆盖风险。 |
| `+task_result` | `doc +export-get` / 导入任务恢复入口 | 用户任务对齐;DWS 按任务所属产品提供类型化恢复入口,不保留 Lark 的下划线泛化命令。 |
| `+apply-permission` | `drive permission apply` raw leaf | 下层能力存在但未提升为 Shortcut:需要真实申请上下文和权限夹具,无法在通用 E2E 中安全创建。 |
| `+member-add` | `doc +access-grant` | 用户任务对齐并增强:解析接收人、批量 ledger、首次写入前停止。 |
| `+member-list` | `drive permission list` / `doc +inspect --include-permissions` | 对齐;常规 Agent 场景优先 Doc 聚合检查。 |
| `+permission-get-setting` | `drive permission list` + `drive +publish-get` | 部分对齐:协作者与互联网公开是两个独立安全域,没有一个与 Lark setting 完全同构的钉钉接口。 |
| `+secure-label-list` | 无等价 | gap:当前 DWS/钉钉下层没有可声明的 Drive 安全标签目录接口。 |
| `+secure-label-update` | 无等价 | gap:没有安全标签写接口,不能用普通权限或公开状态替代。 |
| `+search` | `drive +search` | 对齐并增强:过滤、严格数组和分页;在线文档搜索路由 `doc +search`。 |
| `+inspect` | `drive +inspect` | 对齐并增强:必达元数据 + 可选聚合,部分失败不伪装成功。 |
## DWS 超出 Lark Drive 的可挖掘能力
- `+list`:严格目录分页,而不是把缺字段当空目录。
- `+recent`:最近访问/编辑与创建人筛选。
- `+stats`:阅读、编辑、评论、点赞、预览和下载统计。
- `+recycle-list` / `+recycle-restore`:显式回收项身份与恢复后读回。
- `+star-list` / `+star-add` / `+star-remove`:个人收藏完整闭环。
- `+publish-get` / `+publish-unset`:互联网公开独立安全域与关闭后读回;`+publish-set` 保留为 unavailable 诊断入口。
- `+version-download`:历史版本真实字节下载与本地 artifact 校验。
- `+rename`:写后读回验证。
普通钉盘文件的独立 `copy` 是额外确认出的部分 gap:钉钉当前 `doc/copy_document` 对该对象会生成 `.dlink`,不是字节独立副本。`drive +copy` 因此只接受在线对象;普通文件需要快捷入口时用 `+create-shortcut`,需要独立副本时用 `+download` 后 `+upload`。这不是完整的服务端原子 copy,对大文件也不能宣称完全等价。
互联网公开开启也是账号/对象能力 gap:真实普通文件与在线文档夹具都由服务端返回 `operation.notSupported`。`+publish-get` 与关闭语义可验证,但 `+publish-set` 在找到 eligible 节点完成 set→get→unset 闭环前保持 `unavailable` 且不进入公开 Agent catalog。
## 端到端门禁
每个公开 Drive shortcut 必须至少覆盖:
- Cobra 参数、静态确认、Shortcut Execute、MCP 调度和最终输出;
- 明确业务空集合、空响应、缺字段、错误类型、坏元素、`success=false`;
- 写入的 ID/终态证据与读回不一致;
- 下载的本地路径边界、no-clobber、真实字节数;
- 真实账号数据:读命令必须命中已知非空夹具或明确验证合法空集合;写命令必须创建隔离资源、读回、必要时下载比对字节并清理。
发布前运行 `make build`、完整 Go 测试、Schema 生成/漂移/策略检查,并保存不含账号业务内容的结构化 E2E 汇总。

Some files were not shown because too many files have changed in this diff Show More