Compare commits

..
Author SHA1 Message Date
dingtalk-dws-reviewer-router[bot] 76618a6b8c Merge pull request #1115
Merged by the dedicated Reviewer Router GitHub App for PR #1115.
2026-08-26 00:59:56 +00:00
liyuan333 7ab86b82f4 Merge branch 'main' into fix/remove-download-domain-allowlist 2026-08-26 08:40:38 +08:00
dingtalk-dws-reviewer-router[bot] 5c1bef743a Merge pull request #1125
Merged by the dedicated Reviewer Router GitHub App for PR #1125.
2026-08-26 00:29:24 +00:00
liyuan333 1260c188e1 Merge branch 'main' into fix/remove-download-domain-allowlist 2026-08-26 08:19:50 +08:00
liyuan f9a6981232 Merge remote-tracking branch 'upstream/main' into HEAD 2026-08-26 08:14:23 +08:00
dingtalk-dws-reviewer-router[bot] 2b4187aff8 Merge pull request #1145
Merged by the dedicated Reviewer Router GitHub App for PR #1145.
2026-08-25 16:34:48 +00:00
liyuan333 7a9bac0a05 Merge branch 'main' into fix/report-entry-submit-require-recipient 2026-08-25 23:59:48 +08:00
liyuan333 a316222584 Merge branch 'main' into fix/remove-download-domain-allowlist 2026-08-25 23:55:07 +08:00
赤川 20ee2cc4ee Merge branch 'main' into codex/cancel-stale-pr-ci 2026-08-25 23:52:05 +08:00
liyuan a584c18dc2 test: align expected download URL boundary error with new message
7f353b73 reworded ValidateDownloadURL's rejection from "下载地址必须是
受信任域名上的 HTTPS URL" to "下载地址必须是合法的 HTTPS URL" when
the IP-literal refusal was removed, but missed that the string is a
test contract: paramAliasExpectedCaptureBoundaryError treats the URL
validation stop as the expected capture boundary for drive +download
and +version-download fixture runs. The stale assertion failed those
subtests and the alias-count invariant (58 active, want 64), which
also failed the Coverage jobs running the same tests.

- update the boundary matcher to the new message
2026-08-25 23:05:30 +08:00
dingtalk-dws-reviewer-router[bot] 1de179cbdf Merge pull request #1093
Merged by the dedicated Reviewer Router GitHub App for PR #1093.
2026-08-25 14:55:32 +00:00
chichuan ff6cf48df6 ci: cancel stale pull request revisions 2026-08-25 22:24:18 +08:00
liyuan333 879054000d Merge branch 'main' into fix/remove-download-domain-allowlist 2026-08-25 22:21:45 +08:00
liyuan 4f9cb2ac9f Merge branch 'fix/report-entry-submit-require-recipient' of github.com:liyuan333/dingtalk-workspace-cli into fix/report-entry-submit-require-recipient 2026-08-25 22:10:11 +08:00
liyuan f50f086271 Merge remote-tracking branch 'upstream/main' into fix/report-entry-submit-require-recipient
# Conflicts:
#	scripts/policy/interface-migrations/approved-flag-migrations-v1.json
2026-08-25 22:07:51 +08:00
赤川 0fe9dd8ba0 Merge branch 'main' into fix/markdown-split 2026-08-25 21:46:42 +08:00
赤川 6449ad33c1 Merge pull request #1144 from DingTalk-Real-AI/codex/fix-1093-fork-router-slug
fix(ci): admit fork PRs without repository variables
2026-08-25 21:45:35 +08:00
chichuan c77046531e fix(ci): admit fork PRs without repository variables 2026-08-25 21:43:27 +08:00
赤川 797766ebf7 Merge pull request #1124 from liyuan333/chore/approve-report-recipient-requiredness
chore: approve report to-user-ids requiredness migrations (#85724185)
2026-08-25 21:30:01 +08:00
liyuan 7f353b73a5 fix(security): drop IP-literal refusal from download URL validation
Confirmed with the product team that the official GUI client applies no
client-side SSRF interception to downloads, so the IP-literal refusal
was the last remaining client-side interception beyond transport
hygiene. Dedicated deployments make every host dimension (domain,
port, network location) unenumerable, and an IP-literal host is just
another network location.

- ValidateDownloadURL accepts any HTTPS host, IP literals included;
  HTTPS scheme, userinfo refusal, and per-hop redirect re-validation
  stay as the transport baseline
- this retires the third and last interception layer after the host
  allowlist removal and the dial-time public-IP refusal (be52ca5d)
- upload targets stay unaffected: trustedUploadHost keeps rejecting
  non-DingTalk/OSS hosts, so local file bytes cannot be PUT to an IP
- regressions: IP-literal URLs pass validation on the shared and chat
  download paths; userinfo and plain-HTTP URLs stay rejected
2026-08-25 21:07:18 +08:00
liyuan be52ca5d2a fix(security): remove dial-time public-IP refusal from download client
Customer round-2 testing on the dedicated deployment (Jingbo) found the
storage domain resolving to a customer-intranet address (10.254.87.52),
which the dial-time public-IP policy refused: dedicated storage can be
deployed inside the customer network, so its resolved network location
is as unenumerable as its domain and port.

- delete the public-IP policy, the IANA special-purpose denylist, and
  the NAT64 embedded-IPv4 re-validation introduced in 617b780a;
  downloads now dial the service-issued host directly, still ignoring
  environment proxies
- align with the official GUI client, which applies no client-side
  SSRF interception to downloads: no command accepts a user-supplied
  download URL, TLS hostname verification pins the connection to the
  requested domain, redirects are re-validated per hop, and credential
  headers are stripped once a redirect leaves the original origin
- uploads keep the static DingTalk/OSS default-port trust boundary
- simplify SetSecureDownloadDialTargetForTest to a single dial seam
2026-08-25 20:40:47 +08:00
赤川 d772570f4a Merge branch 'main' into chore/approve-report-recipient-requiredness 2026-08-25 20:27:06 +08:00
赤川 7e1d595036 Merge branch 'main' into fix/markdown-split 2026-08-25 20:22:05 +08:00
dingtalk-dws-reviewer-router[bot] 47ef4274cb Merge pull request #1135
Merged by the dedicated Reviewer Router GitHub App for PR #1135.
2026-08-25 12:20:55 +00:00
liyuan 6134d413f5 fix(security): restore default-port-only HTTPS rule for upload URLs
Review finding on 1ba6bec8: relaxing ValidateDownloadURL to accept
non-default HTTPS ports also widened upload targets, because the upload
validator reuses it and only re-imposed the host trust set.

- validateUploadURL now also rejects non-default ports, making the
  upload trust boundary identical to the pre-removal policy (trusted
  DingTalk/OSS hosts on the default port)
- DingTalk/OSS upload endpoints always serve HTTPS on 443, so unlike
  dedicated-deployment downloads there is no legitimate non-default
  port scenario for uploads
- regressions: trusted-host:8443 upload targets are rejected for both
  public-cloud and dedicated hosts
2026-08-25 20:05:06 +08:00
赤川 529d1f9682 Merge branch 'main' into codex/param-hallucination-remaining-products 2026-08-25 19:54:16 +08:00
赤川 8ed46b2da2 Merge pull request #1142
Break-glass governance rollout by haofeng0705 after exact-head approval and nine source-bound required checks.
2026-08-25 19:53:07 +08:00
liyuan 1ba6bec8c1 fix(security): accept non-default HTTPS ports for download URLs
Customer testing on a dedicated deployment found real download URLs served
on a non-default HTTPS port (e.g. 8443) by the dedicated storage domain,
which the inherited default-port-only rule rejected.

- drop the 443-only restriction from ValidateDownloadURL; HTTPS scheme,
  domain-only hosts (no IP literals), and no-userinfo rules stay
- the port is not a trust signal: SSRF protection lives at dial time in
  the port-agnostic public-IP policy
- redirect hygiene unchanged: a port change is a cross-origin redirect
  and still strips service credential headers (new regression guard)
- dedicated-deployment regression: same host on a non-default port is
  accepted by URL validation and downloads successfully
2026-08-25 19:25:20 +08:00
chichuan 4a4062d09e fix(ci): bind required checks to GitHub Actions 2026-08-25 19:20:57 +08:00
hyz 0e09d23223 Merge branch 'main' into codex/param-hallucination-remaining-products 2026-08-25 18:56:22 +08:00
chichuan 4815fcc7fc fix(ci): merge App-owned PRs synchronously 2026-08-25 18:13:38 +08:00
liyuan333 15a7b0a0a7 Merge branch 'main' into fix/remove-download-domain-allowlist 2026-08-25 17:55:54 +08:00
赤川 27e7f1e1f2 Merge pull request #1141
Break-glass semantic rollback of PR #1101, authorized by repository maintainer.
2026-08-25 17:53:28 +08:00
CHHH e9ee516439 Merge branch 'main' into fix/markdown-split 2026-08-25 17:25:47 +08:00
liyuan 617b780a76 fix(security): block IPv4-embedded IPv6 transition ranges in publicIP
- re-validate NAT64 well-known prefix answers (64:ff9b::/96) against the
  embedded IPv4 address: DNS64-synthesized answers for public IPv4-only
  hosts keep working while embedded loopback/private/special addresses
  are refused before dialing
- refuse NAT64 local-use (64:ff9b:1::/48) outright: the IPv4 embedding
  is deployment-specific and cannot be extracted reliably
- refuse Teredo (2001::/32) outright as part of the transition-mechanism
  audit; 6to4 (2002::/16) was already refused and IPv4-mapped addresses
  are normalized via Unmap before checks
- add dial-layer regression: a hostile AAAA answer embedding 127.0.0.1
  fails before any dial attempt
2026-08-25 16:56:20 +08:00
chichuan 09b0a59949 revert: remove edu and college vendor extensions 2026-08-25 16:55:40 +08:00
liyuan333 aa74779aa6 Merge branch 'main' into chore/approve-report-recipient-requiredness 2026-08-25 16:40:01 +08:00
liyuan333 130b57de4d Merge branch 'main' into fix/report-entry-submit-require-recipient 2026-08-25 16:27:20 +08:00
hyz 5a9328ac44 Merge branch 'main' into codex/param-hallucination-remaining-products 2026-08-25 16:21:39 +08:00
赤川 0dc21e8c36 Merge pull request #1077 from DingTalk-Real-AI/codex/fix-ci-baseline-cache-governance
ci: harden coverage baseline governance
2026-08-25 16:12:50 +08:00
liyuan 4423af1af2 fix(security): keep upload host trust and harden non-public IP policy
- restore the pre-existing DingTalk/OSS trusted host requirement for
  upload target URLs via a dedicated upload validator
- extend the dial-time non-public IP denylist with IANA special-purpose
  ranges (0.0.0.0/8, 192.88.99.0/24, 100::/64, 2002::/16, 3fff::/20,
  5f00::/16)
- document that download credential headers follow the service-issued
  URL as-is on the first hop (same authenticated response issues both);
  cross-host redirects keep stripping them
2026-08-25 15:38:49 +08:00
chichuan b45eba0f5c fix(ci): tolerate merged PR base advancement
Bind coverage repair to the stable PR head and merge identity plus protected-main containment without treating the live base SHA projection as permanent identity.

Add semantic regression coverage for base advancement and update the governance documentation.
2026-08-25 14:06:09 +08:00
chichuan c629e1e3eb fix(ci): handle read-only merge defaults projection
GitHub omits merge-related repository settings from tokens without Contents write. Accept only the exact dual omission in read-only admission, and require the dedicated App to observe the reviewed values before any auto-merge mutation.
2026-08-25 12:34:32 +08:00
克谨 0df2d6d630 feat(cli): harden remaining product parameter aliases 2026-08-25 12:33:16 +08:00
chichuan aeb1b2ced2 fix(ci): cross-check strict ruleset via GraphQL 2026-08-25 11:39:33 +08:00
chichuan e3124ccea1 fix(ci): accept strict ruleset read projection 2026-08-25 11:11:21 +08:00
CHHH 3030faf73d Merge branch 'main' into fix/markdown-split 2026-08-25 11:10:03 +08:00
chichuan 5605821a70 Merge remote-tracking branch 'origin/main' into codex/fix-pr-1077-router-governance 2026-08-25 10:54:12 +08:00
liyuan333 9afe9c1436 Merge branch 'main' into fix/remove-download-domain-allowlist 2026-08-25 10:23:24 +08:00
github-actions[bot] 3fd0d97a26 Merge pull request #1110 from DingTalk-Real-AI/codex/fix-command-typo-guidance
fix(cli): add bounded command typo guidance
2026-08-25 02:11:21 +00:00
赤川 71556a19d9 Merge branch 'main' into codex/fix-command-typo-guidance 2026-08-25 09:50:28 +08:00
github-actions[bot] 69f1337316 Merge pull request #1109 from DingTalk-Real-AI/codex/dev-devapp-agoal-shortcuts
feat(shortcut): harden DevApp and Agoal task surfaces
2026-08-24 22:55:59 +00:00
Dennis 01476727e9 feat(shortcut): harden DevApp and Agoal task surfaces 2026-08-25 02:36:18 +08:00
chichuan 61ffc988c5 test: cover command resolution edge cases 2026-08-25 01:33:52 +08:00
chichuan 156be4cee7 fix: preserve guarded flag values during command resolution 2026-08-25 00:50:05 +08:00
chichuan 2a42e282c7 fix: align typo guidance with command framework 2026-08-25 00:50:03 +08:00
chichuan f5378d2f28 test: cover typo guidance CI edges 2026-08-25 00:50:01 +08:00
chichuan ea4a463956 fix: add bounded command typo guidance 2026-08-25 00:49:59 +08:00
github-actions[bot] 8d34acbb38 Merge pull request #1130 from DingTalk-Real-AI/codex/fix-todo-upstream-internal-error
fix(todo): classify upstream failures and validate inputs
2026-08-25 00:44:39 +08:00
克谨 9401f9921a merge: sync latest main 2026-08-25 00:09:27 +08:00
github-actions[bot] ed951ff0e1 chore: update beta formula for v1.0.60-beta.2 [skip ci] 2026-08-24 15:57:43 +00:00
赤川 b42596214d Merge pull request #1133 from DingTalk-Real-AI/codex/changelog-v1.0.60-beta.2-amendment
docs: amend v1.0.60-beta.2 release notes
2026-08-24 23:42:06 +08:00
chichuan f684c412ea docs: amend v1.0.60-beta.2 release notes 2026-08-24 23:37:21 +08:00
克谨 a4813f78bc fix(todo): preserve priority schema compatibility 2026-08-24 23:33:43 +08:00
赤川 415d962371 Merge pull request #1131 from DingTalk-Real-AI/codex/fix-pretag-beta-seal-amendment
fix(policy): allow audited pre-tag beta amendments
2026-08-24 23:13:59 +08:00
chichuan 26253a4eeb fix(policy): allow audited pre-tag beta amendments 2026-08-24 23:11:30 +08:00
克谨 46fdaed90b Merge remote-tracking branch 'origin/main' into codex/fix-todo-upstream-internal-error 2026-08-24 23:10:17 +08:00
克谨 9ea19315e9 fix(todo): classify upstream failures and validate inputs 2026-08-24 23:04:45 +08:00
github-actions[bot] 8549a90402 Merge pull request #1127 from DingTalk-Real-AI/codex/param-hallucination-6-products
feat(cli): govern six product parameter aliases
2026-08-24 22:59:59 +08:00
克谨 a5dbd2e049 Merge remote-tracking branch 'origin/main' into codex/param-hallucination-6-products 2026-08-24 22:33:48 +08:00
github-actions[bot] 0ebdcb455d Merge pull request #1105 from maoqxxmm/codex/sheet-float-image-local-file
feat(sheet): support local files for float images
2026-08-24 14:30:48 +00:00
克谨 b1a6f9e20c Merge remote-tracking branch 'origin/main' into codex/param-hallucination-6-products 2026-08-24 22:03:15 +08:00
xiatian 29a0dde9d4 Merge remote-tracking branch 'upstream/main' into codex/sheet-float-image-local-file 2026-08-24 22:02:38 +08:00
克谨 0fae9dd8b3 feat(cli): govern six product parameter aliases 2026-08-24 22:01:10 +08:00
赤川 6096227511 Merge pull request #1126 from DingTalk-Real-AI/codex/changelog-v1.0.60-beta.2
chore(release): prepare v1.0.60-beta.2
2026-08-24 22:00:23 +08:00
chichuan aa265acd50 chore(release): prepare v1.0.60-beta.2 2026-08-24 21:46:54 +08:00
赤川 7210e5677c Merge pull request #1121 from hlzjsong/refresh_legacy_slot_fix
recover identity refresh from legacy global slot
2026-08-24 21:38:58 +08:00
赤川 1c90623e31 Merge branch 'main' into refresh_legacy_slot_fix 2026-08-24 21:38:34 +08:00
github-actions[bot] e7236e27d4 Merge pull request #1101 from zh-tinghe/feat/migrate-edu-contact
feat: add edu & college vendor extension commands
2026-08-24 13:33:58 +00:00
liyuan ab883f11f0 fix(report): require --to-user-ids on entry submit
Reject report submissions without visible recipients at the CLI layer
(Cobra required flag + fail-closed on blank values) while keeping the
openAPI create_report parameter optional (bug 85724185).
2026-08-24 21:14:40 +08:00
liyuan 7101ffc89c chore: approve report to-user-ids requiredness migrations (#85724185) 2026-08-24 21:12:18 +08:00
赤川 e73ebab2db Merge branch 'main' into feat/migrate-edu-contact 2026-08-24 21:03:27 +08:00
liyuan 4381a54efa test: adapt mock_mcp download e2e and coverage to proxy-free secure client 2026-08-24 21:00:00 +08:00
xiatian 1b6a592387 Merge remote-tracking branch 'upstream/main' into codex/sheet-float-image-local-file 2026-08-24 20:59:28 +08:00
github-actions[bot] ce7f66ff8e Merge pull request #1117 from maoqxxmm/codex/schema-compat-float-image-one-of
fix(schema): review float image constraint transition
2026-08-24 12:46:49 +00:00
赤川 e58f255476 Merge branch 'main' into refresh_legacy_slot_fix 2026-08-24 20:28:19 +08:00
赤川 79f4f66d34 Merge branch 'main' into feat/migrate-edu-contact 2026-08-24 20:25:16 +08:00
听荷 66061bb0b0 fix(college-contact): 移除可复制破坏性示例中的确认绕过参数并补齐契约示例必填参数
CR P1-1:15 个 user_required 叶子的 Cobra Example 携带 -y/--yes,
Agent 或用户直接复制即绕过本 PR 实现的用户确认门。改为只保留
--dry-run 预览示例(含 -f json 变体),确认参数由调用方在取得
用户明确确认后自行追加。

CR P1-2:create-group-rule 的 Contract.Selection.Examples 缺少必填
--dept-type,示例实际执行会在参数校验阶段失败。同类缺陷在本文件另有
6 处(dept create / employee add / alumni list / alumni add-alumnus /
alumni update-alumnus / graduate batch-update-pending),一并对齐到
各自 Cobra Example;另修正 dept update、dept batch-update-type 契约
示例中 dept-type 取值与 Cobra 示例不一致的问题(standard -> college)。

验证:5 个产品共 153 条 Contract.Selection.Examples 全部 --dry-run
可执行(153/153 ok);changed code coverage 100.0000%。
2026-08-24 20:23:21 +08:00
xiatian 1d14258121 Merge remote-tracking branch 'upstream/main' into codex/schema-compat-float-image-one-of 2026-08-24 20:15:41 +08:00
muling.cs cbbc9a9f90 refresh token legacy_recovery 2026-08-24 20:14:36 +08:00
github-actions[bot] 723d43f660 Merge pull request #1056 from AlwaysLee/feat/drive-permission-get-setting
feat(drive): add permission get-setting command
2026-08-24 12:09:36 +00:00
半圭 12d60d264d fix(drive): address review feedback on permission get-setting
- drop shareScope.required=["linkShare"] from the result schema: the
  production response omits linkShare unless link sharing is configured;
  document the conditional presence in the schema description, tests,
  and both skill references
- spell out policy value tiers in skill docs (READER_AND_ABOVE /
  DOWNLOADER_AND_ABOVE / EDITOR_AND_ABOVE / MANAGER_AND_ABOVE) so the
  shorthand no longer diverges from the schema enums
2026-08-24 19:46:13 +08:00
半圭 7bb2019dfe feat(drive): expose disabledValues with per-value reasons in get-setting schema 2026-08-24 19:46:13 +08:00
半圭 9e692ce0bf docs(drive): align get-setting semantics with final GUI wording
Sync the get-setting schema descriptions and skill references with the
server-side finalized policy texts: policy code semantics now quote the
product permission-settings page labels (e.g. external_share=add
external collaborators, watermark=show watermark, node_spread_scope=
download and distribution scope), the node_spread_scope binary values
read ALL_NODES=all documents and PREVIEWABLE_ONLY=previewable documents
only (online documents, images, videos, etc.), and the contract test
value-semantic fragments follow the same wording. Both mono and multi
drive references also list per-policy name/description examples
(external_share, node_spread with NOBODY, node_move_forbidden) quoted
verbatim from the server i18n single source.
2026-08-24 19:46:13 +08:00
半圭 e2b390217a feat(drive): align get-setting schema with self-explaining policy values
Sync the get_permission_setting result contract with the finalized
server-side enums: switch values ON/OFF become ENABLED/DISABLED and the
node_spread_scope binary ALL_CONTENT becomes ALL_NODES (restriction
applies to all nodes vs previewable-only nodes), while policy codes,
threshold domains, permissionMode and shareScope stay unchanged.
Policies now carry per-entry name and description fields (Chinese label
and value semantics from the server i18n single source) as deterministic
required members, mirrored in the contract test assertions and both mono
and multi drive skill quick references.
2026-08-24 19:46:13 +08:00
半圭 09ecaaa7a1 feat(drive): document get-setting response schema and field semantics
Declare the drive.permission get_setting ResultSpec data schema from the
server-side output metadata so the field-level contract (permissionMode,
shareScope, policy codes and their typed value domains, inherited /
allowedValues semantics) is reviewed code guarded by contract tests, and
add field-quick-reference rows for the get-setting section in both mono
and multi drive skill references.
2026-08-24 19:46:13 +08:00
半圭 95ecc99c41 fix: route drive permission get-setting to drive MCP server 2026-08-24 19:46:13 +08:00
半圭 6df6b3f8dd docs(release): use PR-numbered fragment name for drive permission get-setting 2026-08-24 19:46:13 +08:00
半圭 227861a71d feat(drive): add permission get-setting command 2026-08-24 19:46:13 +08:00
Dennis4477andDennis 20a01aaeff policy: support compatibility-visible availability migrations (#1123)
Co-authored-by: Dennis <xinyang.dxy@alibaba-inc.com>
2026-08-24 19:37:21 +08:00
赤川 4fb3349dcd Merge branch 'main' into fix/remove-download-domain-allowlist 2026-08-24 19:11:03 +08:00
赤川 7844b59a8c Merge branch 'main' into refresh_legacy_slot_fix 2026-08-24 19:08:53 +08:00
听荷 2a875eee1d Merge remote-tracking branch 'upstream/main' into feat/migrate-edu-contact 2026-08-24 19:05:05 +08:00
Dennis4477 c75eda1a2b Merge pull request #1118 from DingTalk-Real-AI/codex/devapp-availability-migration-plan
policy: plan DevApp availability hardening migration
2026-08-24 18:49:08 +08:00
听荷 662352202c docs(skills): complete edu-app command surface and fix dead cross-product ref
Skill 参考此前只描述了 edu-app 42 条叶子里的 16 条,homework(11)/diploma(9)
与 notice 的 6 条全部缺失,导致 Agent 把"作业/通知"一律路由到 task 与
notice confirm;三条 user_required 破坏性命令(notice/homework/diploma
delete)也落在未记录区间,PR 里的确认门禁说明因此无法被文档兑现。
edu-familygroup 的上下文传递表引用了不存在的 edu-contact family list,
真正返回家长信息的是 family parents。

check-skill-commands.sh 只校验以 `dws ` 开头的反引号片段,因此上述两处
死引用能在门禁全绿的情况下存活;本次给跨产品引用统一补上 dws 前缀,
让该门禁从现在起覆盖它们。

同时修正扫描测试的陈旧计数(153→156、edu-contact 26→29、class 18→19,
以 DeclareLeafMetadata 声明数为准),并给 edu-familygroup 第 6 条叶子
manage add-child 补上工具名与整体入参的精确断言。
2026-08-24 18:33:08 +08:00
xiatian fa2d54fd42 Merge remote-tracking branch 'upstream/main' into codex/schema-compat-float-image-one-of 2026-08-24 18:29:05 +08:00
Dennis c5d58ec49d policy: plan devapp shortcut availability hardening 2026-08-24 18:27:12 +08:00
github-actions[bot] adbc9aac3e Merge pull request #1120 from DingTalk-Real-AI/codex/param-hallucination-8-products
feat(cli): optimize parameter aliases for eight products
2026-08-24 18:25:44 +08:00
xiatian 82a87bf9ce Merge remote-tracking branch 'upstream/main' into codex/schema-compat-float-image-one-of 2026-08-24 18:24:13 +08:00
muling.cs 0bfb1ff1de chore: add release fragment for legacy global slot recovery 2026-08-24 18:11:07 +08:00
muling.cs 426ad50927 refresh use legacy global slot 2026-08-24 18:06:05 +08:00
克谨 b57cecb62d feat(cli): optimize eight shortcut parameter aliases 2026-08-24 17:51:45 +08:00
Dennis4477 8dc52d052c Merge pull request #1113 from DingTalk-Real-AI/codex/devapp-availability-compat-governance
policy: add consumable Schema availability migrations
2026-08-24 17:39:07 +08:00
xiatian f8e3f0b12a fix(schema): review float image constraint transition 2026-08-24 17:09:11 +08:00
听荷 aab67202a0 Merge remote-tracking branch 'origin/feat/migrate-edu-contact' into feat/migrate-edu-contact 2026-08-24 17:02:24 +08:00
听荷 2e60b8e121 test: pair confirm-gate and exact-dispatch assertions for all destructive edu leaves
Auto-CR flagged that the destructive-command tests only checked the error
string, so a regression that let a command slip past the confirmation gate
(or dispatch the wrong payload) would go unnoticed.

Every user_required leaf across college-contact (15), edu-contact (2),
edu-group (1) and edu-app (3) is now verified in pairs with a fresh
non-dry-run capture caller: without explicit confirmation the gate must
return confirmation_required AND the caller must see zero calls; with
--yes the command must produce exactly one call carrying the correct
productID, tool name and complete argument payload.
2026-08-24 17:01:57 +08:00
Dennis 6147021ae8 policy: add consumable schema availability migrations 2026-08-24 16:47:19 +08:00
赤川 f84fc1d684 Merge branch 'main' into feat/migrate-edu-contact 2026-08-24 16:46:40 +08:00
赤川 ae31d371de Merge pull request #1114 from DingTalk-Real-AI/codex/fix-policy-timeout-governance
ci: extend Policy job timeout for large Schema surfaces
2026-08-24 16:46:22 +08:00
chichuan c20a53cd8a ci: extend Policy job timeout 2026-08-24 16:43:10 +08:00
liyuan de7591be9c 专属地址下载链路问题修复 2026-08-24 16:39:07 +08:00
xiatian 2dc52afd60 fix(sheet): align float image schema constraints 2026-08-24 16:36:12 +08:00
赤川 21bca3025f Merge branch 'main' into feat/migrate-edu-contact 2026-08-24 16:07:50 +08:00
听荷 874b5b091a Merge remote-tracking branch 'upstream/main' into feat/migrate-edu-contact 2026-08-24 15:58:52 +08:00
github-actions[bot] 1bb56364c2 Merge pull request #1111 from DingTalk-Real-AI/codex/ci-windows-coverage-governance
ci: bound exhaustive coverage test runtimes
2026-08-24 15:54:27 +08:00
赤川 cd93b7fdc2 Merge branch 'main' into feat/migrate-edu-contact 2026-08-24 15:34:40 +08:00
克谨 758b0f875e ci: bound exhaustive coverage test runtimes 2026-08-24 15:28:00 +08:00
nitonitori 0d8008dabf chore: 调整错误引导至 dws doc import. 2026-08-24 15:15:11 +08:00
nitonitori ce8fe16f50 feat: 优化 markwodn 切分时对 html 标签的处理. 2026-08-24 15:15:11 +08:00
nitonitori 1ba326279f fix(markdown): 修复并优化Markdown追加内容分片逻辑
- 重新实现Markdown追加分片,确保每个分片为完整独立的顶级区块序列,满足update_document追加模式要求
- 修复分片过程中丢失换行符导致标题等Markdown元素识别错误的问题
- 解决超大表格和代码块被截断在单元格或代码围栏中间的问题
- 修正读取回写验证,避免与服务器未接收的内容比较导致验证失败
- 统一多条Markdown写入路径,统一分片逻辑与分片大小限制(30000字符)
- doc update --index参数在内容需分片时改为失败,避免不可预期的插入位置
- 添加分片写入过程中的降级变更通知字段,明确分片对文档结构的影响
- 优化写入流水线,增强超长内容自动分片支持和错误处理逻辑
- 补充分片相关的单元测试,保障内容完整性及边界拆分行为
2026-08-24 15:15:11 +08:00
xiatian fdb5fc19f5 test(sheet): cover float image redirect rejection 2026-08-24 14:48:12 +08:00
xiatian 583d31141d feat(sheet): support local files for float images 2026-08-24 14:48:12 +08:00
github-actions[bot] 19be571574 Merge pull request #1107 from DingTalk-Real-AI/codex/devdoc-hrbrain-pat-shortcuts
feat(shortcut): harden Devdoc HRbrain and PAT surfaces
2026-08-24 14:24:48 +08:00
听荷 38360969e7 style: gofmt college_contact files 2026-08-24 13:36:31 +08:00
听荷 f9277ae1d3 fix: use standard confirmation framework for college-contact destructive commands
Remove custom collegeContactConfirmDestructive function and rely on the
standard framework Confirmation: "user_required" in LeafSpec. This ensures
destructive commands return the proper category:validation/code:3 error
instead of category:internal/code:5.

Also adds run_all_edu_commands_test.go covering all 153 leaf commands
across 5 edu/college products.
2026-08-24 13:30:32 +08:00
听荷 232d9dee6e fix: remove edu products from command matrix to avoid Windows timeout
The command matrix test with 5 additional edu products (153 leaf commands)
exceeds the 10-minute go test timeout on Windows CI runners. The dedicated
TestCrossPlatformCoverage* tests in individual edu test files already
provide 100% changed-code coverage without the matrix.
2026-08-24 13:30:32 +08:00
听荷 c2528f1fc8 fix: add CrossPlatformCoverage prefix to edu tests for platform gate
The CI platform coverage gate (macOS/Windows) only executes tests
matching ^(TestAllShortcuts|TestCrossPlatformCoverage). The edu/college
test functions used standard names and were not exercised during the
platform coverage run, causing 73.89% changed-code coverage (target 100%).

Changes:
- Rename all edu/college test functions with TestCrossPlatformCoverage prefix
- Add edu products to command_matrix_test.go selected map
- Add TestCrossPlatformCoverageOpenSupplementServersIncludesEduEndpoints
  in pkg/edition to cover the new supplement server entries

Local verification: coverage-gate-platform reports 100.0000% (3336 stmts).
2026-08-24 13:30:32 +08:00
听荷 1b22b79fa9 fix: replace --help examples with real execution examples in college-contact
The schema catalog validator requires Examples to demonstrate actual
execution, not just --help. Replace all 65 occurrences with realistic
parameter examples derived from each command's required flags.
2026-08-24 13:30:32 +08:00
听荷 f3e98d55e8 feat: add edu & college vendor extension commands
Add five hidden vendor extension commands for education scenarios:
- edu-contact: school/class/family/teacher contact management
- edu-group: student/class group lifecycle
- edu-app: homework, notices, report cards, diplomas, class circles
- edu-familygroup: family group management, child binding, app permissions
- college-contact: university dept/employee/alumni/graduate management

All route to dedicated MCP servers via callMCPToolOnServer with endpoints
registered in openSupplementServers (helper-only, not VisibleProducts).

Includes: helper implementations, dispatch tests, mono/multi skill docs,
coverage.yaml registration, context-budget compliance, and .changes fragment.
2026-08-24 13:30:32 +08:00
Dennis b5a287ae71 feat(shortcut): harden devdoc hrbrain and pat surfaces 2026-08-24 13:10:38 +08:00
github-actions[bot] da6f867dfa Merge pull request #1085 from typefield/feat/drive-permission-pagination
feat(drive,doc,wiki): permission/member list pagination and multi-type members
2026-08-24 12:17:02 +08:00
zengyouling.zyl 82dc2b5e5e Merge remote-tracking branch 'upstream/main' into feat/drive-permission-pagination 2026-08-24 11:57:48 +08:00
zengyouling.zyl 9265fd4cb8 fix(skill): point wiki member pagination at native wiki member list 2026-08-24 11:57:36 +08:00
github-actions[bot] e324ef9d4a Merge pull request #1069 from WHUTzju/feat/add-aitable-datasource-tools
Feat/add aitable datasource tools
2026-08-24 11:33:54 +08:00
zengyouling.zyl fb1847d62e Merge remote-tracking branch 'upstream/main' into feat/drive-permission-pagination 2026-08-24 11:15:26 +08:00
zengyouling.zyl 331681e82b ci: retrigger auto-cr to pick up screenshot evidence 2026-08-24 11:05:14 +08:00
陌渊 1633888290 Merge upstream/main: resolve shortcut count conflict + fix field-ids doc
- schemaPublishedShortcutCount: 461→468 after merging aisearch/contact/live
  shortcuts from upstream/main
- Fix P2: datasource-update --field-ids doc says "不传时同步全部字段" but
  actual behavior keeps existing field config; fixed in usage guide and
  reference
2026-08-24 10:54:58 +08:00
github-actions[bot] 206f33ae1c Merge pull request #1083 from DingTalk-Real-AI/codex/shortcut-aisearch-contact-live
feat(shortcut): harden AiSearch Contact and Live task surfaces
2026-08-24 10:44:39 +08:00
陌渊 9259477372 [WP-46-001] fix: update shortcut count constants to match merged sheet/whiteboard shortcuts
publicShortcutCount 422→424, schemaPublishedShortcutCount 460→462,
publiclyDeliveredShortcutCount 422→424
2026-08-24 10:31:32 +08:00
Dennis 137151b38c fix(shortcut): align reviewed live response shapes 2026-08-24 09:57:54 +08:00
陌渊 081220f15e Merge branch 'main' into feat/add-aitable-datasource-tools 2026-08-24 09:50:50 +08:00
Dennis b8216380de fix(aisearch): declare stable result identity 2026-08-23 20:35:11 +08:00
Dennis 83bc213b7f fix(aisearch): reject non-person search sources 2026-08-23 19:40:27 +08:00
Dennis 804b9a3142 fix(contact): normalize exact mobile lookup input 2026-08-23 19:40:26 +08:00
Dennis cfdb0d0556 fix(contact): preserve legacy role placeholders 2026-08-23 19:40:24 +08:00
Dennis d8686122ab fix(schema): reconcile shortcut counts after rebase 2026-08-23 19:40:21 +08:00
Dennis 222a0230ae fix(contact): preserve strict list roles compatibility 2026-08-23 19:40:18 +08:00
Dennis f7befc7943 fix(contact): preserve roster CLI compatibility 2026-08-23 19:40:15 +08:00
Dennis 1dc924af1b fix(contact): align mobile catalog semantics 2026-08-23 19:40:12 +08:00
Dennis 0e8d6e00cf fix(contact): avoid unnecessary mobile detail lookup 2026-08-23 19:40:09 +08:00
Dennis 0028ed1570 fix(contact): restore exact mobile lookup 2026-08-23 19:40:06 +08:00
Dennis 8b5d9a59b0 fix(contact): preserve published schema interface 2026-08-23 19:40:03 +08:00
Dennis 01d663f597 fix(contact): verify exact mobile ownership 2026-08-23 19:40:01 +08:00
Dennis 2bf314c625 fix(contact): preserve list-roles CLI visibility 2026-08-23 19:39:59 +08:00
Dennis 8c98d1abe4 fix(shortcut): harden AiSearch Contact and Live delivery 2026-08-23 19:39:56 +08:00
Dennis 5e4a65513b fix(aisearch): fail closed on unprovable zero results 2026-08-23 19:39:54 +08:00
Dennis 4ae0e0ffc3 fix(contact): close exhaustive shortcut release gate 2026-08-23 19:39:52 +08:00
Dennis 1c9a977d08 fix(shortcut): close residual search and contact gaps 2026-08-23 19:39:50 +08:00
Dennis 78fabec4bb feat(shortcut): fail close Live list task 2026-08-23 19:39:47 +08:00
Dennis 6d6404993a feat(shortcut): harden Contact task surface 2026-08-23 19:39:45 +08:00
Dennis 008d50bb3d feat(shortcut): harden AiSearch task surface 2026-08-23 19:39:40 +08:00
zengyouling.zyl f871689960 ci: retrigger checks after flaky race shard and transient status upload 2026-08-23 19:34:04 +08:00
zengyouling.zyl b15a21de93 Merge remote-tracking branch 'upstream/main' into feat/drive-permission-pagination 2026-08-23 19:00:08 +08:00
github-actions[bot] 58e8e35948 Merge pull request #1098 from typefield/fix/schema-compat-confirmation-exceptions
fix(ci): review batch remove confirmation hardening
2026-08-23 18:52:41 +08:00
zengyouling.zyl a8b0d8895b fix(ci): review batch remove confirmation hardening
Rebuild the exact-entry reviewedCompatibilityExceptions carve-out in the
base-owned schema-compat checker for the three destructive batch-remove
tools whose confirmation PR #1085 tightens from not_required to
user_required (doc/doc.remove_permission, drive/drive.permission_remove,
wiki/wiki.remove_member). Because the compatibility gate builds its
checker from the PR merge-base, this carve-out has to land on main before
PR #1085 can pass; the entry set is exact (tool + field + old -> new), so
any other confirmation drift, including weakening a reviewed tool back to
not_required, still fails.
2026-08-23 18:33:32 +08:00
zengyouling.zyl 546c2d2eb2 fix(helpers): require user confirmation for batch permission/member remove
Address the P1 review finding on PR #1085: --members lets one call remove
up to 30 USER/DEPT/CONVERSATION/TAG members, where departments, chats,
and role groups can indirectly affect many more users, yet the remove
branches called the MCP tool right after argument parsing with Safety
confirmation=not_required.

- drive permission remove, doc permission remove, and wiki member remove
  now declare confirmation=user_required. DeclareLeafMetadata installs
  the ConfirmSafety gate automatically (deferred to the first
  deps.Caller.CallTool so flag validation still fails first), so an
  unconfirmed invocation exits with the typed confirmation_required
  error and performs zero MCP calls; --yes, an interactive yes, or
  --dry-run previews remain the supported paths.
- Pass framework confirmation errors through WrapErrorWithOperation
  verbatim (new apperrors.IsConfirmationRequired). Text classification
  misrouted them: command paths containing "permission" (drive/doc
  permission remove) were re-reported as AUTH_PERMISSION_DENIED while
  other paths (wiki member remove) lost their reason and degraded to
  UNCLASSIFIED.
- Tests: TestPermissionMemberRemoveRequiresConfirmationBeforeToolCall
  covers all three entry points for both --members and legacy --users —
  unconfirmed rejects with zero MCP calls, --yes dispatches exactly one
  call with the complete precise arguments, --dry-run previews without
  calls. Existing remove tests inject root --yes for the assembly
  assertions; blank --users still fails validation before confirmation.
2026-08-23 17:55:33 +08:00
zengyouling.zyl 5bd0ea7c53 fix(helpers): drop NO_PERMISSION from document permission codes
Address the P2 review finding on PR #1085: NO_PERMISSION is a generic
code name also returned by non-document tools — attendance
get-self-setting (bossAttendStatNotify) and event-subscription attempts
have both been observed returning it — so keying drive permission
apply-* guidance on it would mislead those products, defeating the goal
of the P1 scoping fix. Only the drive-specific forbidden.* domain codes
(forbidden.no.auth / forbidden.accessDenied) and the role-threshold
message wording remain document signals; a bare NO_PERMISSION still
classifies as AUTH_PERMISSION_DENIED but now keeps the product-neutral
suggestion, and NO_PERMISSION combined with document wording still gets
apply guidance.

Add regression tests for the non-document NO_PERMISSION case and update
the changelog fragment; changed-code coverage stays at 100%.
2026-08-23 17:01:49 +08:00
zengyouling.zyl 4833b39071 fix(helpers): scope permission-apply guidance and null->{} rendering to confirmed tools
Address the two P1 review findings on PR #1085:

- Permission suggestions: the drive permission apply-* guidance is now
  limited to document/wiki-specific errors (node access codes
  NO_PERMISSION / forbidden.no.auth / forbidden.accessDenied and the
  role-threshold wording). Permission failures from other products keep
  their product-specific suggestion (e.g. the mail mailbox hint) or fall
  back to a product-neutral hint instead of being told to run document
  permission commands that cannot fix their problem.

- Null rendering: the null->{} adaptation is limited to the four tools
  with a confirmed empty-response-means-success contract
  (update_permission / remove_permission / update_member /
  remove_member). Every other tool keeps its raw null output so the
  shared machine-output contract stays unchanged.

Update tests and the changelog fragment accordingly; changed-code
coverage stays at 100%.
2026-08-23 16:16:34 +08:00
zengyouling.zyl 8a4e49dbf2 test(helpers): cover permission update/remove members and blank --users branches to #1085 2026-08-23 14:34:17 +08:00
zengyouling.zyl 7c924c54ca fix(drive,doc,wiki): register limit mapping exclusion instead of property redirect to #1085
The server rejects the legacy maxResults path; the CLI now validates
--limit (1-50) and sends it as pageSize at runtime. Schema-compat
rejects a non-empty property redirect (maxResults -> pageSize), so
declare --limit as a CLI pagination input via the reviewed mapping
exclusion ledger (property omitted, provenance
reviewed_mapping_exclusion) on doc.list_permission,
drive.list_permission, and wiki.list_member.
2026-08-23 03:04:11 +08:00
zengyouling.zyl 59d0b6dd75 Merge remote-tracking branch 'upstream/main' into feat/drive-permission-pagination 2026-08-23 02:52:58 +08:00
zengyouling.zyl 74f7bbc980 docs(changes): correct release fragment PR reference to #1085 2026-08-23 02:38:43 +08:00
zengyouling.zyl 2b7d5a2c5f fix(drive,doc,wiki): permission notify default, error guidance, pagination contract to #1065
- --notify now defaults to false and is omitted from the server request
  unless passed explicitly (help updated accordingly)
- forbidden.accessDenied / permission-denied bodies classify as
  AUTH_PERMISSION_DENIED with apply-permission guidance
- user/member validation failures intercepted before RESOURCE_NOT_FOUND
  with --members corpId suggestion
- business error display appends backend code/logId for traceability;
  literal null tool responses render as {}
- drive/doc permission list + wiki member list declare cursor pagination
  (next-token) in Contract; cobra.NoArgs hardening on permission leaves
- cross-platform coverage tests and release fragments updated
2026-08-23 02:25:57 +08:00
github-actions[bot] fcfead71cb Merge pull request #1082 from DingTalk-Real-AI/codex/shortcut-sheet-whiteboard-markdown
feat(shortcuts): harden Sheet Whiteboard and Markdown routes
2026-08-23 01:14:11 +08:00
Dennis 0beb1c6b0c fix(whiteboard): compare readback numbers exactly 2026-08-23 00:54:46 +08:00
Dennis 3b38d4c8da docs(whiteboard): fix shortcut file source syntax 2026-08-23 00:30:58 +08:00
Dennis d68e340a5b fix(whiteboard): preserve interactive confirmation in examples 2026-08-23 00:30:56 +08:00
Dennis 98799effba fix(shortcuts): close sheet and whiteboard review gaps 2026-08-23 00:30:54 +08:00
Dennis 9239f9070a test(ci): share shortcut schema boundary fixture 2026-08-23 00:30:52 +08:00
Dennis 202c5ce697 feat(shortcuts): harden Sheet Whiteboard and Markdown routes 2026-08-23 00:30:49 +08:00
github-actions[bot] 8ab2ac5e7c Merge pull request #994 from FloralTide/codex/fix-event-shutdown-lifecycle
fix(event): clean up shutdown lifecycle
2026-08-21 19:24:44 +08:00
炳昱 b85a342e9f fix(npm): preserve interactive terminal ownership 2026-08-21 19:09:59 +08:00
炳昱 ad72cf4b3d fix(npm): signal the vendor process group 2026-08-21 18:15:19 +08:00
炳昱 89154b3952 fix(npm): avoid duplicate terminal signals 2026-08-21 17:39:48 +08:00
炳昱 b01febf52e Merge remote-tracking branch 'official-upstream/main' into codex/fix-event-shutdown-lifecycle 2026-08-21 17:23:25 +08:00
github-actions[bot] 74b7690cbb Merge pull request #1078 from liyuan333/feat/doc-read-public-and-history-version
feat(doc): read password-protected public docs and historical versions
2026-08-21 17:19:39 +08:00
liyuan333 8312c4f30e Merge branch 'main' into feat/doc-read-public-and-history-version 2026-08-21 16:50:25 +08:00
赤川 35c6fd95e1 Merge pull request #1092 from DingTalk-Real-AI/codex/add-secondary-dingtalk-webhook
ci: notify a secondary DingTalk webhook
2026-08-21 16:24:19 +08:00
chichuan 564ff8563f ci: notify a secondary DingTalk webhook 2026-08-21 16:22:46 +08:00
陌渊 c7510cd1a1 fix(datasource): trim whitespace from batch IDs and fix result/processCode docs
- Add trimNonEmpty for --table-ids in +datasource-sync and --task-ids in
  +datasource-sync-status, matching the existing field-ids pattern
- Add 4 test cases: whitespace-only rejection and trim-through for both
- Fix usage guide: typical workflow and notes no longer equate result
  with processCode; correctly describe result as JSON to parse for
  approvals[].processCode/name/iconUrl/url
2026-08-21 16:19:43 +08:00
john 1c3477c087 Merge branch 'main' into feat/drive-permission-pagination 2026-08-21 16:18:15 +08:00
陌渊 93d450a9bf fix(datasource): read --field-ids as string slice, not string
--field-ids is declared as FlagStringSlice, but DatasourceCreate and
DatasourceUpdate previously called rt.Str to check whether the flag
was empty. RuntimeContext.Str delegates to cobra's GetString, which
returns an empty string on slice-typed flags, so the empty-value
guard rejected every explicit --field-ids input and the downstream
MCP tool never received fieldIds.

Switch to rt.StrSlice, sanitize through a new trimNonEmpty helper
(drop whitespace-only / empty entries) and pass the cleaned slice
to MCP. Add success-passthrough tests for both create and update,
plus a whitespace-only rejection case, and enhance the mock caller
to record MCP arguments so fieldIds can be asserted.
2026-08-21 16:10:23 +08:00
陌渊 cf39768095 fix(datasource): align field-ids semantics and test naming for coverage gate
- Update --field-ids description in create/update shortcuts and the
  helper-layer datasource update to clarify that omitting the flag
  keeps existing config (create defaults to all fields), matching the
  actual update overwrite semantics.
- Rename datasource shortcut coverage tests to the
  TestCrossPlatformCoverage* prefix so they are picked up by the
  macOS platform coverage gate.
2026-08-21 16:10:20 +08:00
陌渊 c096258b0f fix(datasource): reject empty field-ids and auto-sync-setting in shortcut layer
Align shortcut layer validation with helper layer to prevent empty slices
from being sent to MCP, which could clear sync field selection due to
datasource update's overwrite semantics.

- Add empty string checks for --field-ids in both create and update shortcuts
- Add empty string checks for --auto-sync-setting in both create and update shortcuts
- Add regression tests verifying MCP is not called when empty values are rejected
- Both public entry points now have consistent validation behavior

Fixes P1 auto-CR issue for empty flag bypass vulnerability.
2026-08-21 16:10:18 +08:00
陌渊 5ba8ac6775 test(aitable): cover datasource shortcut and helper error paths for 100% changed-code coverage 2026-08-21 16:10:15 +08:00
陌渊 66fee5ef5f fix(aitable): update shortcut counts after rebase onto upstream main 2026-08-21 16:10:12 +08:00
陌渊 d9b9c5c7da fix(aitable): reject empty field-ids/auto-sync-setting and non-object JSON 2026-08-21 16:10:09 +08:00
陌渊 684411e54e fix(aitable): require task-ids for datasource sync-status and align docs
Make +datasource-sync-status consistent across shortcut and native
commands: --task-ids is now required, descriptions focus on querying
by taskId, and optional/IDLE semantics are removed. Update tests,
usage guide, reference doc, and SKILL description accordingly.
2026-08-21 16:10:05 +08:00
陌渊 de5ba029d4 fix(aitable): add field-ids/auto-sync-setting to native datasource create/update
Native datasource create/update now expose --field-ids and
--auto-sync-setting, matching the shortcut-layer capabilities:
- flags registered on both commands
- Contract Parameters updated
- values mapped to MCP tool args
- JSON validation for --auto-sync-setting
- no-change update guard now counts the new flags

Also fixes the missing required name in the usage-guide update example.
2026-08-21 16:10:02 +08:00
陌渊 15f139e32d fix(aitable): reject no-change datasource update and fix doc example
+datasource-update now requires at least one mutable option
(--source-config, --auto, --field-ids, or --auto-sync-setting)
before calling update_datasource_config, preventing accidental
sync triggers. The native datasource update command enforces the
same guard for its supported flags. Also adds the required name
field to the +datasource-get-fields doc example.
2026-08-21 16:09:58 +08:00
陌渊 768c1ce494 fix(aitable): only send --auto on datasource-update when explicitly set
Omitting --auto on +datasource-update previously sent auto=false to
MCP, silently disabling auto-sync for existing datasources. Now auto
is only included in tool args when the flag is explicitly provided,
so --auto=true and --auto=false work while omission preserves the
existing setting. Updated flag descriptions and added tests.
2026-08-21 16:09:55 +08:00
陌渊 966fd60e2f fix(aitable): always send auto=false for datasource create/update
MCP requires the auto field in create_datasource / update_datasource_config
requests. Previously CLI only sent it when --auto was explicitly changed,
causing failures when users omitted the flag. Now both shortcut and helper
layers always include auto=false by default.

Also update flag descriptions and docs to clarify that the field is always
sent downstream, and add test assertions for the default-false behavior.
2026-08-21 16:09:51 +08:00
陌渊 7825c3c7e0 docs(aitable): fix datasource doc inconsistencies for auto CR P2
- docs/datasource-usage-guide.md: clarify that list-sources result is a
  JSON string containing approvals[]; add missing --auto-sync-setting
  parameter table rows and a dedicated autoSyncSetting format section
  using the correct scheduled/daily/weekly/monthly enums.
- skills/references/aitable/aitable-datasource.md: fix autoSyncSetting
  enums (schedule/day/week/month -> scheduled/daily/weekly/monthly) and
  update the create example accordingly.
2026-08-21 16:09:49 +08:00
陌渊 10d44615d1 fix(aitable): include required name in datasource source-config examples
The OA approval source-config contract requires processCode, name,
iconUrl, and url to be passed through unchanged from +datasource-list-sources.
Published examples for +datasource-create, +datasource-update, and
+datasource-get-fields were missing `name`, and the usage guide marked it
as optional. Fix all examples in the shortcut layer, helper layer, and
docs; update flag descriptions to mention name; and add a contract test
that validates every delivered example's source-config JSON contains the
required members.
2026-08-21 16:09:39 +08:00
陌渊 1e88612e43 test(aitable): add datasource helper tests for 100% changed-code coverage
21 tests covering all 7 datasource leaf commands' error paths (missing
required flags, count validation) and happy paths (source-config as raw
string, --auto flag, boundary cases for table-ids/task-ids).
2026-08-21 16:09:36 +08:00
陌渊 5934ccac7f fix(aitable): enforce 1-5 count limit on table-ids and task-ids
Both the shortcut (+datasource-sync, +datasource-sync-status) and
helper (datasource sync, datasource sync-status) layers now validate
that table-ids contains 1-5 IDs and task-ids contains at most 5 IDs
before calling MCP, matching the declared contract.
2026-08-21 16:09:30 +08:00
陌渊 d9365f3fff docs: remove unimplemented --conflict-strategy from all datasource docs 2026-08-21 16:09:27 +08:00
陌渊 15d93698bd fix(aitable): use String instead of StringSlice for datasource flags
ValidateRequiredFlags calls GetString which returns empty for
StringSlice flags, causing the examples test to report --table-ids
as missing. Switch to String + parseCSVValues to match the codebase
convention used by record-ids and other comma-separated flags.
2026-08-21 16:09:23 +08:00
陌渊 7e69a3d6fe fix(aitable): add datasource helper leaf commands and fix CI test counts
- Add 7 datasource leaf commands to internal/helpers/aitable.go so
  coverage test can find tool name literals (fixes TestAllShortcutsAssemble)
- Add 7 entries to semantic_catalog_aitable.json and update catalog count
  from 93 to 100 (fixes TestCrossPlatformCoverageAITableSemanticCatalog)
- Update publicShortcutCount/schemaPublishedShortcutCount/publiclyDelivered
  from 422/447/422 to 429/454/429 (fixes TestDeliverySchemaCoversOrExactly)
- Fix Contract.Selection.AgentSummary and UseWhen[0] in datasource.go to
  match Description and Intent exactly as required by schema contract test
2026-08-21 16:09:21 +08:00
陌渊 94b4958038 chore(aitable): regenerate SKILL.md shortcut section via gen_skill_shortcut_sections.py 2026-08-21 16:09:19 +08:00
陌渊 97a99ba04f style: fix gofmt indentation in datasource.go 2026-08-21 16:09:17 +08:00
陌渊 5e1e5cbb84 fix(aitable): remove duplicate datasource-get-fields and datasource-list-sources rows in SKILL.md 2026-08-21 16:09:14 +08:00
陌渊 e04e886c50 chore: add release fragment for aitable datasource shortcuts 2026-08-21 16:09:12 +08:00
陌渊 1acde9b766 feat(aitable): align datasource shortcuts with MCP snapshot [WP-40-006]
- Fix autoSyncSetting enum: scheduled/daily/weekly/monthly; mark
  selectedMonthDays/selectedWeekdays as required for monthly/weekly
- Remove splitParentTableField from --source-config user-settable fields;
  add note that splitParentTableField/enableDataSyncOaDetailList are
  internal downstream fields not to be passed
- Prepend sync-is-fire-and-forget notice to DatasourceSync descriptions
- Remove --conflict-strategy flag (syncConflictStrategy not in MCP schema)
2026-08-21 16:09:09 +08:00
陌渊 852efe56aa feat(aitable): add datasource skill optimization
- Golden Route: add datasource entry (list-sources → create flow)
- 常用 leaf 直达: add datasource-* commands
- 当前最短路径: add list-sources-first rule
- 安全边界: add sync write warning
- 错误最短路径: add errorCode=4014 and sync=false handling
- 按需加载: add datasource reference trigger
- New reference: aitable-datasource.md with full workflow, sourceConfig
  protocol, autoSyncSetting config, command details, error codes
2026-08-21 16:09:05 +08:00
陌渊 6c287bcb4d feat(aitable): align datasource shortcuts with MCP snapshot [WP-40-005]
- Add --auto-sync-setting flag to DatasourceCreate (was only in Execute, not in Flags)
- Expand DatasourceSync description: add 文档链接, errorCode=4014 幂等冲突, 非数据源表参数错误
- Simplify DatasourceGetFields description: remove field property enumeration to match snapshot
2026-08-21 16:09:00 +08:00
陌渊 df24d53886 feat(aitable): align datasource shortcuts with MCP snapshot [WP-40-004]
Sync CLI field descriptions with latest ai-table-mcp-snapshot.json:
- source-config flags: restructure to "两类字段" (4 passthrough + caller-set),
  add splitParentTableField, fix Update flag to optional semantics
- get_datasource_sync_status: update status list (RUNNING/FINISHED/FAILED,
  remove TIMEOUT), change "不传返回最近一次" → "IDLE(下游暂不支持)"
- get_datasource_config: add sync=true guard note, "其他类型暂不支持", sourceConfig hint
- list_datasource_sources: full rewrite explaining result/approvals structure,
  4-field passthrough rule, enableDataSyncOaDetailList internal note
- get_datasource_fields: add "其他数据源类型暂不支持,待后续开放"
2026-08-21 16:08:50 +08:00
陌渊 49cecabb12 [WP-40-003] feat: align 7 datasource shortcuts with latest MCP snapshot
- Add --auto-sync-setting flag (JSON string) to +datasource-create and
  +datasource-update, validated and passed through as raw string.
- Update +datasource-update --source-config desc to reflect full
  replacement semantics ("传入时整体覆盖") and spell out required /
  optional fields with defaults.
- Append "仅支持 OA 审批数据源 (datasourceType=OA)" to
  +datasource-get-config description.
- Simplify +datasource-list-sources / +datasource-get-fields
  descriptions to concise Chinese aligned with snapshot wording.
- Update SKILL.md shortcuts table and add datasource usage guide.
2026-08-21 16:08:45 +08:00
陌渊 09993ad82c [WP-40-002] fix: correct idempotency value from not_idempotent to non_idempotent 2026-08-21 16:08:42 +08:00
陌渊 0efaf6c82f [WP-40-002] feat: update SKILL.md with 5 datasource shortcuts and trigger words 2026-08-21 16:08:39 +08:00
陌渊 26002637f4 [WP-40-001] feat: add 5 datasource shortcuts for aitable
Add 5 data source sync management shortcuts to the aitable service:
- +datasource-create (create_datasource): create sync config + first sync
- +datasource-update (update_datasource_config): update existing sync config
- +datasource-sync (run_datasource_sync): trigger manual sync (max 5 tables)
- +datasource-sync-status (get_datasource_sync_status): query sync task status
- +datasource-get-config (get_datasource_config): get sync config details

Each shortcut declares a full Contract (Identity/Interface/Selection),
Safety, Flags, and Execute that calls rt.CallMCPData on the "aitable"
MCP server. datasource-type is passed through without CLI enum check;
source-config is validated as a JSON object via parseJSONObject.
2026-08-21 16:08:35 +08:00
github-actions[bot] f7229091ae Merge pull request #1053 from anxiangbo/feat/20260817_agoal_search
Feat/20260817 agoal search
2026-08-21 07:50:26 +00:00
liyuan333 77aa813467 Merge branch 'main' into feat/doc-read-public-and-history-version 2026-08-21 15:42:53 +08:00
anxiangbo 1f595571c0 Merge branch 'main' into feat/20260817_agoal_search 2026-08-21 15:27:26 +08:00
github-actions[bot] cd90d1c322 Merge pull request #1075 from Justper/oa_attachment_upload_dws
Oa attachment upload dws
2026-08-21 14:46:26 +08:00
liyuan 49ab53ea0d 评审问题修复 2026-08-21 14:29:05 +08:00
昭逸 78433198fb Merge branch 'oa_attachment_upload_dws' of github.com:Justper/dingtalk-workspace-cli into oa_attachment_upload_dws
to #666
2026-08-21 14:23:27 +08:00
昭逸 4863152a4a Merge remote-tracking branch 'upstream/main' into oa_attachment_upload_dws
to #666
2026-08-21 14:20:33 +08:00
昭逸 2057fec3b0 fix(oa): normalize spaceId/fileSize to match ResultSpec integer declaration to #666
- validateOAAttachmentCommitResult 改为返回归一化后的 result map
- string 型 spaceId 通过 ParseInt 转 int64,json.Number 同理,非法字符串报错
- fileSize 的 json.Number 同样归一化为 int64
- 新增归一化行为测试 + 输出契约测试,覆盖率 100% to #666
2026-08-21 14:19:53 +08:00
anxiangbo 1308d08862 Merge branch 'DingTalk-Real-AI:main' into feat/20260817_agoal_search 2026-08-21 14:00:30 +08:00
github-actions[bot] 8b56e9bc9e Merge pull request #1073 from maoqxxmm/codex/sheet-revision-changeset
feat(sheet): add revision and changeset inspection
2026-08-21 05:56:10 +00:00
毛球 87e141f2de Merge branch 'main' into codex/sheet-revision-changeset 2026-08-21 13:38:56 +08:00
github-actions[bot] 9b521f0392 chore: update beta formula for v1.0.60-beta.1 [skip ci] 2026-08-21 05:17:50 +00:00
YanChangzhi 4dcd528bc1 Merge branch 'main' into oa_attachment_upload_dws 2026-08-21 13:11:34 +08:00
赤川 0bbb3a9d32 Merge pull request #1087 from DingTalk-Real-AI/codex/changelog-v1.0.60-beta.1
chore: prepare v1.0.60-beta.1 changelog
2026-08-21 12:48:22 +08:00
chichuan 0ebd840ba9 chore: prepare v1.0.60-beta.1 changelog 2026-08-21 12:35:38 +08:00
github-actions[bot] 9d356cd664 Merge pull request #1076 from hlzjsong/refresh_org_slot_fix
refresh org slot not only identity
2026-08-21 04:20:48 +00:00
YanChangzhi b00f43ee06 Merge branch 'main' into oa_attachment_upload_dws 2026-08-21 12:14:19 +08:00
赤川 23167ef974 Merge branch 'main' into refresh_org_slot_fix 2026-08-21 11:46:45 +08:00
毛球 8b003aef16 Merge branch 'main' into codex/sheet-revision-changeset 2026-08-21 11:43:45 +08:00
github-actions[bot] 11934eed05 Merge pull request #1081 from DingTalk-Real-AI/codex/fix-report-requiredness-governance
feat(policy): govern optional-to-required flag migrations
2026-08-21 11:40:11 +08:00
玉澜 d552c59d11 feat(drive,doc,wiki): permission/member list pagination and multi-type members
Sync the permission CRUD overhaul from the internal CLI (MR 28965577):

- drive/doc permission list and wiki member list now accept --next-token
  to follow the server cursor (totalCount/hasMore/nextToken); --limit maps
  to pageSize capped at 50 instead of the rejected maxResults=200 path
  (fixes #1065)
- permission add/update/remove and wiki member add/update/remove accept a
  --members JSON array (USER/DEPT/CONVERSATION/TAG grantee types, each with
  its own roleId) with optional --notify; legacy --users/--role stays
- cursor/page-token hidden cross-product aliases now resolve to next-token
- regenerate param_aliases_generated.go; wiki member list override no
  longer blocks cursor
- update mono/multi skill references and add change fragment
2026-08-21 11:30:22 +08:00
YanChangzhi 23e1085a37 Merge branch 'main' into oa_attachment_upload_dws 2026-08-21 11:16:29 +08:00
赤川 a352615e77 Merge branch 'main' into refresh_org_slot_fix 2026-08-21 11:15:06 +08:00
xiatian d210da501a Merge remote-tracking branch 'upstream/main' into codex/sheet-revision-changeset 2026-08-21 11:14:03 +08:00
赤川 6288199a93 Merge branch 'main' into codex/fix-report-requiredness-governance 2026-08-21 11:05:33 +08:00
anxiangbo 6d9781fe15 Merge branch 'main' into feat/20260817_agoal_search 2026-08-21 11:04:19 +08:00
赤川 5b34ed1a7e Merge pull request #1084 from DingTalk-Real-AI/codex/docs-dws-cli-open
docs: 公告 DWS CLI 全面开放
2026-08-21 11:02:36 +08:00
chichuan 3d9a469347 docs: announce DWS CLI availability 2026-08-21 11:01:08 +08:00
xiatian 7640ba7614 fix(sheet): validate changeset audit integrity 2026-08-21 10:59:14 +08:00
anxiangbo c790fe3c3b Merge branch 'main' into feat/20260817_agoal_search 2026-08-21 10:44:53 +08:00
昭逸 4886bdb3f5 Merge remote-tracking branch 'upstream/main' into oa_attachment_upload_dws
to #666
2026-08-21 10:41:06 +08:00
昭逸 4aef07ccd3 fix(oa): validate commit response required fields before reporting success to #666
- 新增 validateOAAttachmentCommitResult 校验 spaceId/fileName/fileSize/fileId 必需字段
- commit 步不再使用通用 callOAAttachmentResultCtx,改为专用校验后才存储成功结果
- 补充 malformed commit 响应回归测试,覆盖率 100%
2026-08-21 10:40:46 +08:00
github-actions[bot] e0c49377d6 Merge pull request #1074 from DingTalk-Real-AI/codex/investigate-calendar-todo-comment-regressions
fix: harden calendar todo and comment shortcuts
2026-08-21 10:30:04 +08:00
昭逸 dc37dd2c34 fix(oa): remove DDAttachment from unsupported table and use testseam.Swap to #666
- 从 oa-form-components.md (mono/multi) 的"API 不支持的控件"表中移除 DDAttachment,避免 Agent 误判为不支持
- computeFileMD5 测试注入改为 testseam.Swap,符合仓库包变量注入约定
2026-08-21 09:56:48 +08:00
hlzjsong 02aad9020a Merge branch 'main' into refresh_org_slot_fix 2026-08-21 09:26:39 +08:00
昭逸 d59d1091dc Merge remote-tracking branch 'upstream/main' into oa_attachment_upload_dws
to #666
2026-08-21 08:51:13 +08:00
昭逸 60d6bfeec4 Merge branch 'oa_attachment_upload_dws' of github.com:Justper/dingtalk-workspace-cli into oa_attachment_upload_dws
to #666
2026-08-21 08:50:28 +08:00
昭逸 bf89acb3d2 fix ci fail to #666 2026-08-21 08:50:13 +08:00
Dennis 35d6f47bd6 Merge remote-tracking branch 'origin/main' into codex/investigate-calendar-todo-comment-regressions 2026-08-21 08:17:52 +08:00
xiatian d24b71614a Merge remote-tracking branch 'upstream/main' into codex/sheet-revision-changeset 2026-08-21 01:23:17 +08:00
github-actions[bot] 765b961f4d Merge pull request #1071 from DingTalk-Real-AI/codex/fix-stable-active-fragments
fix(release): consume post-beta fragments in stable seals
2026-08-21 01:06:18 +08:00
xiatian 9ab4bd10a5 Merge remote-tracking branch 'upstream/main' into codex/sheet-revision-changeset 2026-08-21 01:05:47 +08:00
chichuan 14c5bed4fc ci: split app-c race partition for runner headroom 2026-08-21 00:50:46 +08:00
赤川 c1bd6dcf64 Merge branch 'main' into codex/fix-stable-active-fragments 2026-08-21 00:06:27 +08:00
Dennis 1c8b83ec2f Merge remote-tracking branch 'origin/main' into codex/investigate-calendar-todo-comment-regressions 2026-08-20 23:58:13 +08:00
Dennis bb6f470df5 test: address shortcut regression review 2026-08-20 23:56:08 +08:00
赤川 01af71a5ae Merge branch 'main' into oa_attachment_upload_dws 2026-08-20 23:47:06 +08:00
github-actions[bot] d4ff8a5f4f Merge pull request #1070 from DingTalk-Real-AI/codex/oa-ding-report-shortcuts
feat(shortcut): harden OA DING and Report workflows
2026-08-20 23:44:23 +08:00
赤川 47e3c2b3c5 Merge branch 'main' into refresh_org_slot_fix 2026-08-20 23:19:33 +08:00
xiatian e8ecff586a fix(sheet): classify malformed revision responses 2026-08-20 23:06:56 +08:00
Dennis 11a9ad5d49 fix(shortcut): align OA execution availability 2026-08-20 23:03:35 +08:00
Dennis 23940e4752 revert: keep coverage shard output compact 2026-08-20 22:24:17 +08:00
Dennis 8cb0f64477 fix(shortcut): reject backward OA cursors 2026-08-20 22:15:27 +08:00
Dennis bbaf033618 ci: stream app coverage progress 2026-08-20 22:14:35 +08:00
xiatian 57cca7ef71 fix(sheet): validate revision result contracts 2026-08-20 22:02:26 +08:00
Dennis 2d38beb7be fix(shortcuts): separate compatibility visibility from availability 2026-08-20 21:51:21 +08:00
昭逸 4372a8c5ba Merge remote-tracking branch 'upstream/main' into oa_attachment_upload_dws
to #666
2026-08-20 21:44:16 +08:00
昭逸 422dc0fde3 fix ci fail to #666 2026-08-20 21:44:05 +08:00
muling.cs 3d59411a1a refresh slot repair org 2026-08-20 21:24:06 +08:00
chichuan fe66ac18a4 feat(policy): govern flag requiredness changes 2026-08-20 21:19:55 +08:00
Dennis bda408d966 test(ding): cover compatibility reminder mappings 2026-08-20 21:10:45 +08:00
Dennis 33631502c9 fix(shortcuts): align unavailable writes and query validation 2026-08-20 21:02:52 +08:00
毛球 f2a608d146 Merge branch 'main' into codex/sheet-revision-changeset 2026-08-20 20:44:17 +08:00
Dennis 378b9f67a2 Merge remote-tracking branch 'origin/main' into codex/investigate-calendar-todo-comment-regressions 2026-08-20 20:41:56 +08:00
赤川 e841d41640 Merge branch 'main' into codex/fix-ci-baseline-cache-governance 2026-08-20 20:38:39 +08:00
xiatian 43ba466783 fix(sheet): require fresh confirmation for version revert 2026-08-20 20:28:35 +08:00
Dennis 5c9f738012 fix(shortcuts): preserve published schema bindings 2026-08-20 20:20:41 +08:00
Dennis 56c5fb35b8 chore(policy): retire completed flag migrations 2026-08-20 20:20:27 +08:00
Dennis b19c52f61b fix(oa): make approval keyword normalization explicit 2026-08-20 20:20:25 +08:00
Dennis c0641dbf64 fix(shortcut): preserve OA and DING CLI compatibility 2026-08-20 20:20:23 +08:00
Dennis 3b5cb3b0cb test(shortcut): close OA DING Report coverage gaps 2026-08-20 20:20:21 +08:00
Dennis fd2ed2174f chore(release): add OA DING Report fragment 2026-08-20 20:20:19 +08:00
Dennis 5bbaa304e3 docs(shortcut): refresh OA DING Report live evidence 2026-08-20 20:20:17 +08:00
Dennis db938778af chore(shortcut): sync OA DING Report with current main 2026-08-20 20:20:15 +08:00
Dennis 1155b9b5c0 test(shortcut): align OA reviewed input fixtures 2026-08-20 20:20:12 +08:00
Dennis 8fa93ef030 fix(shortcut): retire OA discovery aliases after downgrade 2026-08-20 20:20:10 +08:00
Dennis f4ad1a15f5 docs(shortcut): record Report double-layer release proof 2026-08-20 20:20:08 +08:00
Dennis d2cbd928e2 docs(shortcut): record DING double-layer release proof 2026-08-20 20:20:06 +08:00
Dennis 2346dfba4e fix(shortcut): require OA zero-page pagination evidence 2026-08-20 20:20:03 +08:00
Dennis f6ad2fa01a fix(shortcut): publish Report range constraints 2026-08-20 20:19:44 +08:00
Dennis 7bc56f3dea feat(shortcut): unlock Report outbox workflows 2026-08-20 20:19:41 +08:00
Dennis 03001eb4e0 fix(shortcut): harden DING write routing evidence 2026-08-20 20:19:39 +08:00
Dennis 91974ce981 docs(shortcut): close OA residual audit gaps 2026-08-20 20:19:37 +08:00
Dennis c6417e3527 feat(shortcut): harden Report reads and availability 2026-08-20 20:19:35 +08:00
Dennis 161687ae4c fix(shortcut): deliver OA validation evidence 2026-08-20 20:19:32 +08:00
Dennis 4da5a07d1d feat(shortcut): harden DING reads and availability 2026-08-20 20:19:30 +08:00
Dennis 2cb83d388b fix(shortcut): publish OA validation constraints 2026-08-20 20:19:25 +08:00
Dennis ba9c0f624e feat(shortcut): harden OA workflows and availability 2026-08-20 20:19:19 +08:00
Dennis ff65f80c98 refactor(oa): add strict shortcut response helpers 2026-08-20 20:19:09 +08:00
github-actions[bot] 42240f5e9e Merge pull request #1079 from DingTalk-Real-AI/codex/fix-stable-migration-receipts
fix(ci): keep completed migration receipts inert
2026-08-20 20:18:04 +08:00
Dennis e6b06b561d Merge remote-tracking branch 'origin/main' into codex/investigate-calendar-todo-comment-regressions 2026-08-20 19:50:43 +08:00
chichuan 11cbc30a10 fix(ci): keep completed migration receipts inert 2026-08-20 19:16:07 +08:00
xiatian 60a474f30c fix(sheet): fail closed on invalid revision results 2026-08-20 19:15:48 +08:00
xiatian 6e8fec5684 chore(policy): retire consumed flag migrations 2026-08-20 17:48:59 +08:00
liyuan 470aa42d7b chore: keep release note in .changes fragment, restore CHANGELOG.md 2026-08-20 17:35:03 +08:00
liyuan a74d96bb96 feat(doc): read password-protected public docs and historical versions 2026-08-20 17:32:11 +08:00
liyuan 9798a60728 feat(doc): read password-protected public docs and historical versions 2026-08-20 17:13:46 +08:00
毛球 e028d443d5 Merge branch 'main' into codex/sheet-revision-changeset 2026-08-20 17:11:29 +08:00
chichuan 74859b966b fix(release): consume active fragments in stable seals 2026-08-20 17:07:53 +08:00
xiatian 76a5559ca2 fix(sheet): align revision dry-run contract 2026-08-20 16:59:18 +08:00
chichuan 7b5e3e2d1f ci: harden coverage baseline governance 2026-08-20 16:57:51 +08:00
昭逸 c4a1018213 删除无关文件 to #666 2026-08-20 16:55:18 +08:00
github-actions[bot] 62d72ad84c chore: update formula for v1.0.59 [skip ci] 2026-08-20 08:45:55 +00:00
Dennis e6fe475aea chore: retire consumed chat flag migration 2026-08-20 16:44:07 +08:00
muling.cs e95ac52ff4 refresh org slot not only identity 2026-08-20 16:41:35 +08:00
Dennis 61f8140f91 test: close shortcut regression coverage gaps 2026-08-20 16:38:05 +08:00
昭逸 09c0cd7849 merge uptream to #666 2026-08-20 16:26:32 +08:00
xiatian 5a368a9ab8 Merge remote-tracking branch 'upstream/main' into codex/sheet-revision-changeset 2026-08-20 16:11:37 +08:00
Dennis fbcd8887ee fix: harden calendar todo and comment shortcuts 2026-08-20 16:09:18 +08:00
赤川 c0838e7e41 Merge pull request #1072 from DingTalk-Real-AI/codex/changelog-v1.0.59-stable
chore: prepare v1.0.59 changelog
2026-08-20 16:06:54 +08:00
chichuan 9c6ab99bf1 chore: prepare v1.0.59 changelog 2026-08-20 16:01:09 +08:00
github-actions[bot] 87ab311764 chore: update beta formula for v1.0.59-beta.5 [skip ci] 2026-08-20 07:55:40 +00:00
昭逸 7f4318a10d fix审批skill中附件描述 to #666 2026-08-20 15:39:14 +08:00
xiatian 5232f632c8 Merge remote-tracking branch 'upstream/main' into codex/sheet-revision-changeset 2026-08-20 15:35:19 +08:00
xiatian 615a775fdf feat(sheet): add revision changeset inspection 2026-08-20 15:34:45 +08:00
anxiangbo b10da77e09 Merge branch 'main' into feat/20260817_agoal_search 2026-08-20 15:22:19 +08:00
昭逸 cefc5c005c 附件上传dws合并为一个 to #666 2026-08-20 15:14:39 +08:00
赤川 15c075e6a6 Merge pull request #1068 from DingTalk-Real-AI/codex/changelog-v1.0.59-beta.5
chore: prepare v1.0.59-beta.5 changelog
2026-08-20 14:56:06 +08:00
chichuan f6d1e685e0 chore: prepare v1.0.59-beta.5 changelog 2026-08-20 14:52:30 +08:00
github-actions[bot] 81108e150b Merge pull request #1046 from xlb1130/feat/85614588-chat-personal-emotion
feat(chat): add personal emotion commands
2026-08-20 06:27:50 +00:00
xlb1130 dad9aefefa Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 14:08:05 +08:00
github-actions[bot] 71d49cb12b Merge pull request #1033 from pengzhihan47-star/codex/dingtalk-doc-skill-opt-v1
docs(skill): optimize dingtalk-doc workflows
2026-08-20 14:04:45 +08:00
柏智 f7e2efaaa2 ci: retrigger checks 2026-08-20 13:50:18 +08:00
pengzhihan47-star 557208e16b Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 13:31:31 +08:00
xlb1130 53401dbb0c Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 13:25:41 +08:00
github-actions[bot] 6c52ac37dd Merge pull request #1066 from DingTalk-Real-AI/codex/minutes-todo-wiki-param-aliases
feat(cli): expand Minutes TODO Wiki parameter aliases
2026-08-20 13:21:38 +08:00
xlb1130 95a17a3ffc Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 13:21:07 +08:00
pengzhihan47-star 3318741508 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 13:01:04 +08:00
克谨 3d7ab2690c feat(cli): expand Minutes TODO Wiki parameter aliases 2026-08-20 12:45:19 +08:00
github-actions[bot] 17eefcd24b Merge pull request #1064 from DingTalk-Real-AI/codex/fix-1060-schema-lineage
fix(policy): preserve historical Schema migration lineage
2026-08-20 04:29:42 +00:00
xlb1130 6f62ce7997 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 12:17:24 +08:00
赤川 2228a32d1a Merge branch 'main' into codex/fix-1060-schema-lineage 2026-08-20 12:11:55 +08:00
github-actions[bot] a6f79e951b Merge pull request #1050 from DingTalk-Real-AI/codex/fix-cli-eval-functional
fix: harden shortcut functional workflows
2026-08-20 04:08:39 +00:00
长真 096dfd48f0 docs(chat): keep emotion skill route within budget 2026-08-20 11:57:55 +08:00
chichuan 3922970bfc fix(policy): preserve schema migration lineage 2026-08-20 11:52:00 +08:00
Dennis4477 9b0441ca56 Merge branch 'main' into codex/fix-cli-eval-functional 2026-08-20 11:47:08 +08:00
xlb1130 2ab0edd5c6 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 11:45:41 +08:00
pengzhihan47-star 4b3272bcd4 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 11:42:41 +08:00
github-actions[bot] b6eaf3c5af chore: update beta formula for v1.0.59-beta.4 [skip ci] 2026-08-20 03:42:00 +00:00
长真 80d5d24637 Revert "docs(chat): trim chat skill context budget"
This reverts commit c5951a10ff.
2026-08-20 11:39:50 +08:00
柏智 e40397e239 docs(skill): restore bounded doc guidance 2026-08-20 11:34:14 +08:00
xlb1130 15bc7fdc3f Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-20 11:27:40 +08:00
柏智 da049be58d docs(skill): require terminal evidence for doc writes 2026-08-20 11:21:58 +08:00
柏智 6ec64e8a03 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 11:09:24 +08:00
柏智 bca56cbba6 Merge remote-tracking branch 'origin/codex/dingtalk-doc-skill-opt-v1' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 11:09:19 +08:00
赤川 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
昭逸 103b05413e 审批附件相关dws help补充 to #666 2026-08-20 10:40:35 +08:00
john bb48aa0cc8 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 10:37:44 +08:00
柏智 6ffb4bcb93 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 10:37:30 +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
昭逸 169bbe88c0 上传附件dws to #666 2026-08-20 10:24:35 +08:00
pengzhihan47-star 08595594d7 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 10:15:26 +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
anxiangbo 30782020ad Merge branch 'main' into feat/20260817_agoal_search 2026-08-20 10:03:21 +08:00
柏智 540bbac35b Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 09:57:23 +08: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
柏智 c4d5595ca9 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-20 02:09:49 +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
柏智 86d1eb8030 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 22:47:50 +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
柏智 ab529e5ee5 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 22:05:02 +08:00
xlb1130 15cb1f4311 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-19 22:01:44 +08:00
前津 67505c6c83 Merge remote-tracking branch 'upstream/main' into feat/chat-group-role-single-flag 2026-08-19 21:55:58 +08:00
长真 97ca00868f test(chat): cover personal emotion user resolution 2026-08-19 21:51:49 +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
xlb1130 df8885c350 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-19 21:06:35 +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
赤川 d87cdef00b Merge branch 'main' into codex/fix-event-shutdown-lifecycle 2026-08-19 20:04:42 +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
柏智 4e27a3a84a Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 19:45:13 +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
xlb1130 8685464c53 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-19 19:28:52 +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
长真 26b5939f9f chore(ci): retrigger pr checks 2026-08-19 18:34: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
柏智 95bcace6bd Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 18:10:22 +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
长真 ce57cdf260 chore(ci): retrigger pr checks 2026-08-19 16:26:32 +08:00
Dennis 17741851f5 test: satisfy native shortcut coverage gate 2026-08-19 16:20:04 +08:00
恋川 ed1ebe5d03 chore: retrigger CI 2026-08-19 16:11:24 +08:00
anxb 999e7a7b9d feat: agoal新增dws2 2026-08-19 15:58:28 +08:00
Dennis d10446bea7 ci: reuse preinstalled archive tooling 2026-08-19 15:50:03 +08:00
anxb 94cee4388e Merge remote-tracking branch 'refs/remotes/origin/main' into feat/20260817_agoal_search 2026-08-19 15:38:58 +08:00
xlb1130 3ec138ba99 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-19 15:14:09 +08:00
anxb dcc7e72ec1 feat: agoal新增dws 2026-08-19 15:05:21 +08:00
Dennis 0ed05a2c5d fix: address shortcut review edge cases 2026-08-19 14:46:27 +08:00
Dennis ae1edefee6 test: cover shortcut hardening branches 2026-08-19 14:46:23 +08:00
Dennis 6dbc7ed82b fix: harden shortcut functional workflows 2026-08-19 14:46:19 +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
柏智 f419c0f96d Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 14:37:01 +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
xlb1130 17101a8901 Merge branch 'main' into feat/85614588-chat-personal-emotion 2026-08-19 13:41:20 +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
柏智 66aa00fb50 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 12:23:42 +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
长真 9d6e151a6f Merge remote-tracking branch 'upstream/main' into feat/85614588-chat-personal-emotion 2026-08-19 10:54:30 +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
柏智 0df41d3eff Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 10:40:45 +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
柏智 2a1ed8cc7a Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-19 10:09:48 +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
长真 c5951a10ff docs(chat): trim chat skill context budget 2026-08-19 00:55:27 +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
长真 3dbd29ab50 feat(chat): add personal emotion commands 2026-08-18 23:59:15 +08:00
柏智 1b50c7a5b4 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 23:25:57 +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
柏智 cbd70d1b88 Merge remote-tracking branch 'upstream/main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 21:03:52 +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
柏智 0ae8949d40 fix(doc): align skill contracts with runtime 2026-08-18 20:53:02 +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 0975d970d1 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 20:36:25 +08:00
pengzhihan47-star c0b013afa9 Merge branch 'main' into codex/pr1035-drive-tree-orphan-fix 2026-08-18 20:31:10 +08:00
柏智 7808673431 fix(doc): align media receipt contract 2026-08-18 20:31:07 +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 50ed921ca1 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 19:28:45 +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
pengzhihan47-star b34c29ec35 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 18:41:09 +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
pengzhihan47-star 97e5ded043 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 13:49:34 +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
pengzhihan47-star 7ceeafbae8 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 12:27:08 +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
pengzhihan47-star 54b4a24a14 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 11:35:30 +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
pengzhihan47-star e1bfb343f4 Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 10:13:27 +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
柏智 7da423bf3c docs(skill): clarify import and recent document routes 2026-08-18 09:37:47 +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
柏智 fa83ee579c docs(skill): remove lark-specific wording 2026-08-18 08:25:06 +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
pengzhihan47-star 25c694aa2a Merge branch 'main' into codex/dingtalk-doc-skill-opt-v1 2026-08-18 07:46:06 +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
柏智 e064d394ba docs(skill): optimize dingtalk doc workflows 2026-08-18 01:02:37 +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
炳昱 5a001f33b6 fix(event): clean up shutdown lifecycle 2026-08-13 17:34:20 +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
玉澜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
昕卉 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
玉澜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
chichuan d7c28bcfef fix: complete whiteboard skill examples 2026-08-04 21:19:45 +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
chichuan 50f8ade1d7 Merge origin/main into codex/sync-wukong-whiteboard 2026-08-04 17:36:59 +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
克谨 dc854acb9b test: cover command fallback edge cases 2026-08-04 14:23:53 +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 7e0957d9e8 feat: add document whiteboard workflows 2026-08-04 11:25:31 +08:00
克谨 cfaf161fc7 fix(cli): add reviewed command path fallbacks 2026-08-04 09:35:48 +08:00
炳昱 703406df13 feat(event): publish typed OA approval schemas 2026-07-29 22:23:13 +08:00
炳昱 753d538140 feat(event): complete personal OA approval events 2026-07-28 21:38:17 +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
wxianfeng c870d2ebdc Merge remote-tracking branch 'upstream/main' 2026-07-28 10:52:41 +08:00
1442 changed files with 269508 additions and 27671 deletions
+36
View File
@@ -0,0 +1,36 @@
# 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>/`。beta 发布后若有新 fragments 合入并直接准备 stable,
stable 封板会把它们追加到明确的 post-beta 小节,并归档到正式版本目录;没有新
fragments 时仍只生成原有 beta 晋级模板。因此 release-seal PR 是唯一会修改
`CHANGELOG.md` 的 PR;它同时归档已消费的 fragments,供审计追溯。
归档只能在同一个 release-seal PR 中以原样移动完成;CI 会拒绝直接修改、
删除或重写已归档文件。
无需面向用户发布说明的改动不添加 fragment。评审者根据改动是否可见来判断该
例外是否成立。
+5
View File
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Command typo guidance** — returns a validation error with up to three nearest command suggestions and the parent `--help` entry instead of printing the full command list.
@@ -0,0 +1,31 @@
---
category: Changed
---
- **Download host trust policy** — retires the static DingTalk/OSS download
host allowlist, the dial-time public-IP refusal, and the IP-literal
refusal from both the shared local download path (`drive +download`,
`drive +version-download`, doc/minutes artifact downloads) and the chat
message-resource path (`chat +messages-resource-download`,
`--download-resources`). Download URLs only require HTTPS without userinfo
and accept non-default HTTPS ports, because every dimension of a
dedicated-deployment storage endpoint — custom domain, port, and network
location — is decided by the customer deployment and cannot be enumerated
or configured client-side. Verified on a dedicated deployment whose
storage domain resolves to a customer-intranet address. Downloads align
with the official GUI client, which applies no client-side SSRF
interception: download URLs only ever come from authenticated service
responses (no command accepts a user-supplied URL), TLS hostname
verification pins the connection to the requested host, redirects are
re-validated per hop, and service credential headers are stripped once a
redirect leaves the original origin.
- **Upload host trust unchanged** — upload target URLs (`drive +upload`,
minutes audio upload) keep the pre-existing public DingTalk/OSS trusted
host requirement through a dedicated upload validator, so removing the
download allowlist does not widen where local file bytes can be sent;
the validator also keeps the pre-existing default-port-only HTTPS rule
(DingTalk/OSS upload endpoints always serve on 443, so non-default ports
accepted for dedicated-deployment downloads stay anomalous for uploads).
Download credential headers are issued together with the download URL by
the same authenticated service response and follow it as-is on the first
request; redirects leaving the original host still strip them.
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Fork pull-request admission** — keeps the read-only Reviewer Router identity check fail-closed while allowing external contributors' CI to use the reviewed public App slug when GitHub withholds repository variables.
+10
View File
@@ -0,0 +1,10 @@
---
category: Fixed
---
- **Markdown append chunking rewritten around safe split positions** — long markdown is now split so that every chunk is a complete, self-contained top-level block sequence, which is what `update_document mode=append` requires: the server inserts a brand new structure per call and cannot continue the previous one. Split points are chosen strictly by how much they change the rendered document — fully safe boundaries (blank lines, block starts that interrupt a paragraph) before boundaries that need repair (a table's rows now carry a re-emitted header and delimiter row; a fenced code block is closed and reopened with its original marker and info string) before boundaries that merely restructure (long paragraphs, list items) before a hard character cut. Within a tier the latest boundary in the window wins, since all chunks land in the same document. Every boundary that changes the rendered structure is reported in a new `degradations` field instead of being applied silently.
- **Fixed markdown chunking dropping a newline** — the previous splitter rebuilt block text from lines and lost one `\n` whenever the content's last line began a heading, table or code fence, so `"para\n# Title"` was written as `"para# Title"` and the heading stopped being a heading. Roughly one in five randomly generated documents was affected. The new splitter slices by offset and never rebuilds text, making content preservation structural.
- **Fixed oversized tables and code blocks being cut mid-cell and mid-fence** — the hard-split path never received the block type, so it cut at arbitrary character boundaries despite claiming to preserve table and code block integrity.
- **Fixed readback verification comparing against content the server never receives** — `doc +create` / `doc +update` verified the readback against the raw input, so any repaired boundary (and, previously, any paragraph split) failed verification on large documents. Verification now compares against the document the chunk plan says the server should hold.
- **Unified four markdown write paths onto one splitter** — `doc create` / `doc update`, `doc +create` / `doc +update` and `doc +checkpoint-update` now share `helpers.SplitMarkdownForAppend` and one limit constant (30000 runes), replacing two independent implementations plus one path that never chunked at all. `doc +checkpoint-update` accepts `@file` and stdin content, so oversized input was reachable there while the equivalent `doc +update` chunked. `doc +doc-append` takes `--text` from argv only and now rejects oversized input with a pointer to `doc +update` rather than sending one oversized call.
- **`doc update --index` now fails closed when the content requires chunking** — each chunk creates an unpredictable number of blocks, so the insertion point for later chunks is unknowable; the flag was previously accepted and silently ignored.
@@ -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.
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Shortcut functional workflows** (#1050) — fixes truthful Drive push/sync previews, strict AITable write verification and deletion accounting, lossless Wiki feeds, and false-success handling across task, Contact, Minutes, and Wiki operations.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Chat personal emotions** — adds `chat emotion list`, `chat emotion send`, and `chat emotion favorite` for current-user personal favorite emotion listing, sending, and favoriting.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Minutes, DingTalk tasks, and Wiki parameter aliases** — adds reviewed parameter-name normalization, ambiguity guards, and end-to-end payload coverage for the three products.
@@ -0,0 +1,7 @@
---
category: Fixed
---
- **Calendar empty windows** (#1074) — returns a legitimate empty result when the service emits its exact exhausted empty-event sentinel.
- **Task update verification** (#1074) — compares due-time readback as exact milliseconds so committed updates are no longer reported as failures.
- **Comment reaction validation** (#1074) — narrows accepted reaction input to reviewed DingTalk emoji names and rejects Unicode emoji and unsupported names such as `like` and `heart` before the RPC.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **OA, DING, and Report shortcuts** — hardens response, identity, pagination, and confirmation contracts; publishes verified form search, receiver status, and report read workflows while withholding shortcuts that lack trustworthy downstream evidence.
@@ -0,0 +1,11 @@
---
category: Fixed
---
- **OAuth refresh falls back to the organization mirror** — when the server rejects the
current identity's `refresh_token` with the reviewed `invalidParameter.authCode.notFound`
business code, `dws` now retries once with the still-valid token mirrored in the same
organization's slot (same corp, matching or backfilled user identity) before giving up,
and writes the rotated credential back to both the identity and the organization slots so
the fallback stays usable on later refreshes. Transient failures and direct-mode HTTP
rejections without a reviewed business code do not trigger the fallback.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **Stable release sealing** — directly preparing a stable release now renders and archives release fragments merged after its beta baseline, avoiding a forced extra beta solely to consume pending notes.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Drive permission get-setting** (#1056) — adds `dws drive permission get-setting --node <ID>` to inspect a document-space node's permission settings (permission mode, share scope, and permission policies) in one call.
@@ -0,0 +1,6 @@
---
category: Added
---
- **Whiteboard shortcuts** (#1082) — adds strict query and confirmed update workflows with stable-target receipts and exact readback verification.
- **Sheet shortcut hardening** (#1082) — makes worksheet listing and cell-range reads fail closed on malformed, ambiguous, or truncated responses, publishes a closed reviewed output shape, and preserves non-executing `--dry-run` previews for range reads.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **AiSearch and Contact shortcuts** (#1083) — adds strict people search and reviewed unified results; people results must use the live-reviewed `person` source, and exact mobile lookups normalize accepted formatting before calling the dedicated mobile interface. Agent/public discovery keeps `contact +list-roles`, `contact +list-roster-fields`, `contact +get-roster`, and incomplete Live routes unavailable rather than publishing ambiguous results, while the historical Contact CLI commands retain legacy MCP execution and real error propagation. The legacy role-list projection preserves the service's reviewed null placeholder without exposing that ambiguous row through Agent Result contracts.
@@ -0,0 +1,24 @@
---
category: Changed
---
- **Permission error guidance and error rendering** (#1085) —
permission-denied responses now exit with the `AUTH_PERMISSION_DENIED` code
instead of a generic business-error rendering; document/wiki-specific errors
(the drive-specific codes `forbidden.accessDenied` / `forbidden.no.auth`,
or the role-threshold wording like
“需要您具备 MANAGER 及以上角色”) carry apply-permission guidance
(`dws drive permission apply-info` / `dws drive permission apply`), while
permission failures carrying only generic code names (`FORBIDDEN`,
`NO_PERMISSION` — also returned by attendance and event-subscription tools)
or other products' wording keep their product-specific or
product-neutral suggestion instead of a misleading document-permission hint;
member-validation failures such as
“用户不存在/不属于当前组织” are classified as tool errors with a
`--members`-with-`corpId` suggestion instead of a misleading
resource-not-found error; business error output now surfaces the backend
message with `code`/`logId` appended for traceability; and the
`update_permission` / `remove_permission` / `update_member` /
`remove_member` tools — whose servers return a literal `null` on successful
no-payload writes — now render `{}` so downstream JSON consumers do not fail
parsing `null`; other tools keep raw `null` output unchanged.
@@ -0,0 +1,22 @@
---
category: Added
---
- **Permission and member list pagination** (#1085) — `drive/doc permission
list` and `wiki member list` now accept `--next-token` to follow the
server-side cursor (output carries `totalCount`/`hasMore`/`nextToken`) and
map `--limit` to `pageSize` capped at 50 instead of the rejected `maxResults
200` path; `permission add/update/remove` and `wiki member add/update/remove`
additionally accept a `--members` JSON array covering USER/DEPT/CONVERSATION/TAG
grantee types. The optional `--notify` defaults to `false` and is omitted from
the server request unless passed explicitly, so member grants no longer notify
recipients by default. These commands also declare cursor pagination
(`next-token`) in the Agent schema contract, mirroring the internal CLI parity
change. Because a single batch remove can revoke access for up to 30
USER/DEPT/CONVERSATION/TAG members — where departments, chats, and role
groups can indirectly affect many more users — `drive/doc permission
remove` and `wiki member remove` now declare
`confirmation=user_required` and gate the actual tool call behind user
confirmation (`--yes`, an interactive yes, or `--dry-run` preview); their
confirmation-gate failure now also passes through verbatim instead of being
reclassified as a permission-denied or unclassified error.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Agoal scorecard search-entities** — `dws agoal scorecard search-entities` searches scorecard metrics and key items by keyword, returning matching entity info (scorecard ID, entity ID, entity type, title, owning team) with optional `--page`/`--page-size` pagination.
@@ -0,0 +1,5 @@
---
category: Added
---
- **AITable datasource shortcuts** — adds 7 shortcuts for datasource sync management (`+datasource-create`, `+datasource-update`, `+datasource-sync`, `+datasource-sync-status`, `+datasource-get-config`, `+datasource-list-sources`, `+datasource-get-fields`) and updates the `dingtalk-aitable` skill with routing rules and a new `aitable-datasource.md` reference guide.
@@ -0,0 +1,13 @@
---
category: Added
---
- **Doc public-link and historical-version reads** — `dws doc read` forwards
the reviewed `password` (internet-public documents with password protection)
and `historyVersion` (read content as of a listed historical version; `0`
denotes the document's initial version) parameters on the markdown, JSONML,
and scope read paths via `--password` / `--version`; `dws doc +fetch` gains
`--password` and `--version` with the same `historyVersion` forwarding, while
`--revision` stays rejected with explicit guidance: revision is the document
edit revision returned by JSONML reads for `+update --expected-revision`
conditional writes, not a historical version number.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Edu & College vendor extensions** — adds five hidden vendor extension commands for education scenarios: `dws edu-contact` (school/class/family/teacher contact management), `dws edu-group` (student/class group lifecycle), `dws edu-app` (homework, notices, report cards, diplomas, class circles), `dws edu-familygroup` (family group management, child binding, app permissions), and `dws college-contact` (university dept/employee/alumni/graduate management). All route to dedicated MCP servers via `callMCPToolOnServer`.
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Legacy global slot recovery** — recovers a rejected identity refresh from the legacy global keychain slot when the organization mirror is absent, with strict corp/user matching so blank-user legacy tokens only recover for single-account organizations.
@@ -0,0 +1,5 @@
---
category: Added
---
- **OA approval attachment upload** — `dws oa approval attachment upload --file <path>` uploads a local file as an approval attachment in one command: it initializes the upload credential (MCP `oa/init_attachment_upload_info`), HTTP PUTs the file to OSS, then commits it (MCP `oa/commit_attachment_upload_info`). `--file-name` defaults to the file's base name and `--md5` is auto-computed when omitted.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Sheet floating images** — supports creating or replacing a floating image directly from a local file with `create-float-image --file` and `update-float-image --file`, while retaining the existing `--src` workflow.
@@ -0,0 +1,5 @@
---
category: Added
---
- **Sheet revision changesets** — adds read-only commands for querying the current workbook revision and reviewing Agent-readable changes between revisions, with guidance for distinguishing revisions from saved history versions and safely selecting rollback targets.
@@ -0,0 +1,5 @@
---
category: Removed
---
- **Education and college vendor extensions removed** — removes `dws edu-contact`, `dws edu-group`, `dws edu-app`, `dws edu-familygroup`, and `dws college-contact` from the CLI, Schema, bundled Skills, and open-edition MCP endpoint registry. Future DWS packages no longer expose these five command surfaces.
@@ -0,0 +1,5 @@
---
category: Changed
---
- **report entry submit requires recipients** — `dws report entry submit`(及废弃别名 `dws report create`)的 `--to-user-ids` 从可选提升为必填:无接收人的日志提交在服务端仍返回成功,但日志对任何接收人都不可见。openAPI `create_report` 的 `toUserIds` 参数保持可选不动,规则仅在 dws CLI 侧收紧——Cobra required 拦截未传场景,RunE 内对空值/纯分隔符(如 `--to-user-ids ","`)同样 fail-closed 拒绝。修复 [#85724185](https://project.aone.alibaba-inc.com/v2/project/2170318/bug/85724185)。
@@ -0,0 +1,5 @@
---
category: Fixed
---
- **Reviewer Router merge recovery** — retries exact App-owned merge intents through a SHA-bound synchronous merge after GitHub has enforced approval and nine GitHub Actions source-bound required checks.
+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 -2
View File
@@ -19,8 +19,10 @@ 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.
- [ ] Exact in-place `CHANGELOG.md`-only check (otherwise `N/A`):
`./scripts/policy/check-changelog-pr.sh --fast-path "$(git merge-base HEAD origin/main)" HEAD`
- [ ] 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
+10
View File
@@ -4,3 +4,13 @@ paths:
# 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'
.github/workflows/coverage-baseline-promotion.yml:
ignore:
# Serialize every acknowledgement for one Formula target without
# allowing Actions' default single-pending replacement to orphan a run.
- 'unexpected key "queue" for "concurrency" section'
.github/workflows/coverage-baseline-repair.yml:
ignore:
# Keep the closed-event dispatcher and its exact-SHA producer queued for
# the same target instead of replacing either half of the repair chain.
- 'unexpected key "queue" for "concurrency" section'
+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;
});
+971 -88
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,322 @@
name: Coverage Baseline Promotion
run-name: Promote coverage baseline for ${{ github.event.client_payload.target_sha }}
on:
repository_dispatch:
types: [coverage-baseline-promote]
# repository_dispatch loads this workflow from the protected default branch.
# The requested target is treated as untrusted input until the validation step
# proves it is an exact Formula-only successor already contained in main.
permissions:
checks: write
contents: read
concurrency:
group: coverage-baseline-promotion-${{ github.event.client_payload.target_sha }}
cancel-in-progress: false
queue: max
jobs:
promote:
if: github.repository == 'DingTalk-Real-AI/dingtalk-workspace-cli'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Validate Formula-only main target
id: validate-target
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const targetSha = context.payload.client_payload?.target_sha;
const sourceRunId = context.payload.client_payload?.source_run_id;
const checkRunId = Number(context.payload.client_payload?.check_run_id);
if (!/^[0-9a-f]{40}$/.test(targetSha || '')) {
throw new Error('coverage-baseline-promote requires one full target_sha');
}
if (!/^[1-9][0-9]*$/.test(sourceRunId || '')) {
throw new Error('coverage-baseline-promote requires one source_run_id');
}
if (!Number.isSafeInteger(checkRunId) || checkRunId <= 0) {
throw new Error('coverage-baseline-promote requires one safe check_run_id');
}
// Bind the finalizer before any target or cache validation. A
// later failure must complete the release-created acknowledgement
// instead of leaving Release to poll a permanently queued check.
const promotionExternalId = `release-${sourceRunId}-${targetSha}`;
const {data: promotionCheck} = await github.rest.checks.get({
owner,
repo,
check_run_id: checkRunId,
});
if (
promotionCheck.id !== checkRunId ||
promotionCheck.head_sha !== targetSha ||
promotionCheck.name !== 'Coverage Baseline Cache' ||
promotionCheck.external_id !== promotionExternalId ||
promotionCheck.app?.slug !== 'github-actions' ||
promotionCheck.status !== 'queued' ||
promotionCheck.conclusion !== null
) {
throw new Error('coverage baseline acknowledgement has an invalid identity');
}
core.setOutput('target_sha', targetSha);
core.setOutput('check_run_id', String(checkRunId));
core.setOutput('check_external_id', promotionExternalId);
const {data: targetCommit} = await github.rest.repos.getCommit({
owner,
repo,
ref: targetSha,
per_page: 100,
});
const files = targetCommit.files || [];
const message = targetCommit.commit.message;
const formulaPath = files[0]?.filename;
const stableFormula =
formulaPath === 'Formula/dingtalk-workspace-cli.rb' &&
/^chore: update formula for v[0-9]+\.[0-9]+\.[0-9]+ \[skip ci\]$/.test(message);
const betaFormula =
formulaPath === 'Formula/dingtalk-workspace-cli-beta.rb' &&
/^chore: update beta formula for v[0-9]+\.[0-9]+\.[0-9]+-beta\.[1-9][0-9]* \[skip ci\]$/.test(message);
if (
targetCommit.sha !== targetSha ||
targetCommit.parents.length !== 1 ||
targetCommit.author?.login !== 'github-actions[bot]' ||
targetCommit.committer?.login !== 'github-actions[bot]' ||
files.length !== 1 ||
!['added', 'modified'].includes(files[0].status) ||
(!stableFormula && !betaFormula)
) {
throw new Error(
`${targetSha} is not an exact release-produced Formula-only commit`,
);
}
const parentSha = targetCommit.parents[0].sha;
const requiredContexts = [
'Lint',
'Test',
'Coverage',
'Policy',
'Edition',
'Interface Integrity',
'AI Behavior',
'CLI Smoke',
'Mock MCP',
];
async function requireSuccessfulAdmission(ref, label) {
for (let attempt = 1; attempt <= 6; attempt += 1) {
const runs = await github.paginate(github.rest.checks.listForRef, {
owner,
repo,
ref,
filter: 'latest',
per_page: 100,
});
const latestByName = new Map();
for (const run of runs) {
if (
run.head_sha !== ref ||
run.app?.slug !== 'github-actions' ||
!requiredContexts.includes(run.name)
) {
continue;
}
const current = latestByName.get(run.name);
if (!current || run.id > current.id) {
latestByName.set(run.name, run);
}
}
const invalid = requiredContexts.filter((name) => {
const run = latestByName.get(name);
return !run || run.conclusion !== 'success';
});
if (invalid.length === 0) {
return;
}
if (attempt < 6) {
await new Promise(resolve => setTimeout(resolve, 5000));
continue;
}
throw new Error(
`${label} ${ref} lacks successful Code Admission contexts: ${invalid.join(', ')}`,
);
}
}
await requireSuccessfulAdmission(parentSha, 'Formula parent');
await requireSuccessfulAdmission(targetSha, 'Formula target');
const {data: branch} = await github.rest.repos.getBranch({
owner,
repo,
branch: context.payload.repository.default_branch,
});
const {data: containment} =
await github.rest.repos.compareCommitsWithBasehead({
owner,
repo,
basehead: `${targetSha}...${branch.commit.sha}`,
});
if (!['ahead', 'identical'].includes(containment.status)) {
throw new Error(`${targetSha} is not contained in the protected default branch`);
}
core.setOutput('parent_sha', parentSha);
core.setOutput('formula_path', formulaPath);
- name: Mark Formula cache promotion in progress
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
await github.rest.checks.update({
...context.repo,
check_run_id: Number('${{ steps.validate-target.outputs.check_run_id }}'),
status: 'in_progress',
started_at: new Date().toISOString(),
output: {
title: 'Producing exact-SHA coverage baseline',
summary: 'The trusted default-branch workflow is validating or producing the main-scoped cache.',
},
});
- name: Check out validated Formula-only target
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ steps.validate-target.outputs.target_sha }}
- name: Verify checked-out Formula-only identity
shell: bash
env:
TARGET_SHA: ${{ steps.validate-target.outputs.target_sha }}
PARENT_SHA: ${{ steps.validate-target.outputs.parent_sha }}
FORMULA_PATH: ${{ steps.validate-target.outputs.formula_path }}
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$TARGET_SHA"
test "$(git rev-parse HEAD^)" = "$PARENT_SHA"
test "$(git diff --name-only --no-renames "$PARENT_SHA" "$TARGET_SHA")" = "$FORMULA_PATH"
- name: Set up Go
id: setup-go
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version-file: go.mod
- name: Restore existing target coverage profile
id: target-cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: coverage-cache.txt
key: dws-coverage-full-v2-${{ steps.validate-target.outputs.target_sha }}-go${{ steps.setup-go.outputs.go-version }}
- name: Validate existing target coverage profile
if: steps.target-cache.outputs.cache-hit == 'true'
run: |
set -eu
test -s coverage-cache.txt
test "$(head -n 1 coverage-cache.txt)" = "mode: atomic"
- name: Restore exact Formula parent coverage profile
id: parent-cache
if: steps.target-cache.outputs.cache-hit != 'true'
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: coverage-cache.txt
key: dws-coverage-full-v2-${{ steps.validate-target.outputs.parent_sha }}-go${{ steps.setup-go.outputs.go-version }}
- name: Validate promoted Formula parent profile
if: steps.target-cache.outputs.cache-hit != 'true' && steps.parent-cache.outputs.cache-hit == 'true'
run: |
set -eu
test -s coverage-cache.txt
test "$(head -n 1 coverage-cache.txt)" = "mode: atomic"
- name: Install archive tooling for cold Formula baseline
if: steps.target-cache.outputs.cache-hit != 'true' && steps.parent-cache.outputs.cache-hit != 'true'
run: |
if command -v zip >/dev/null && command -v unzip >/dev/null; then
echo "zip and unzip are already available"
else
sudo apt-get update
sudo apt-get install -y zip unzip
fi
- name: Recompute cold Formula baseline
if: steps.target-cache.outputs.cache-hit != 'true' && steps.parent-cache.outputs.cache-hit != 'true'
env:
DWS_PACKAGE_VERSION: 0.0.0-test
run: |
set -euo pipefail
go test -count=1 -p 1 \
-coverprofile=coverage-cache.txt \
-covermode=atomic \
./ ./cmd/... ./internal/... ./skills/...
test -s coverage-cache.txt
test "$(head -n 1 coverage-cache.txt)" = "mode: atomic"
- name: Save Formula main SHA coverage profile
if: steps.target-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: coverage-cache.txt
key: dws-coverage-full-v2-${{ steps.validate-target.outputs.target_sha }}-go${{ steps.setup-go.outputs.go-version }}
- name: Verify Formula main SHA coverage cache exists
id: formula-target-cache-verification
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: coverage-cache.txt
key: dws-coverage-full-v2-${{ steps.validate-target.outputs.target_sha }}-go${{ steps.setup-go.outputs.go-version }}
lookup-only: true
fail-on-cache-miss: true
- name: Require exact Formula main SHA coverage cache
env:
EXACT_CACHE_HIT: ${{ steps.formula-target-cache-verification.outputs.cache-hit }}
run: test "$EXACT_CACHE_HIT" = true
- name: Complete Formula cache promotion acknowledgement
if: ${{ always() && steps.validate-target.outputs.check_run_id != '' }}
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
env:
PROMOTION_JOB_STATUS: ${{ job.status }}
with:
script: |
const checkRunId = Number('${{ steps.validate-target.outputs.check_run_id }}');
const targetSha = '${{ steps.validate-target.outputs.target_sha }}';
const expectedExternalId = '${{ steps.validate-target.outputs.check_external_id }}';
const {data: currentCheck} = await github.rest.checks.get({
...context.repo,
check_run_id: checkRunId,
});
if (
currentCheck.head_sha !== targetSha ||
currentCheck.name !== 'Coverage Baseline Cache' ||
currentCheck.external_id !== expectedExternalId ||
currentCheck.app?.slug !== 'github-actions'
) {
throw new Error('refusing to update a changed promotion acknowledgement');
}
const succeeded = process.env.PROMOTION_JOB_STATUS === 'success';
await github.rest.checks.update({
...context.repo,
check_run_id: checkRunId,
status: 'completed',
conclusion: succeeded ? 'success' : 'failure',
completed_at: new Date().toISOString(),
output: {
title: succeeded
? 'Exact-SHA coverage baseline is available'
: 'Exact-SHA coverage baseline promotion failed',
summary: succeeded
? `Verified the main-scoped exact cache for ${targetSha}.`
: `Promotion failed for ${targetSha}; rerun the failed Release job after correcting the producer.`,
},
});
@@ -0,0 +1,560 @@
name: Coverage Baseline Repair
run-name: Repair coverage baseline from ${{ github.event_name }}
on:
pull_request_target:
branches: [main]
types: [closed]
workflow_run:
workflows: [CI]
types: [completed]
branches: [main]
repository_dispatch:
types: [coverage-baseline-repair]
schedule:
- cron: "23 * * * *"
workflow_dispatch:
# pull_request_target and workflow_run are allowed to inspect only GitHub API
# data and dispatch the trusted producer. GitHub deliberately makes both
# triggers read-only for the default-branch cache, so all checkout and cache
# writes live in repository_dispatch, schedule, or main-only workflow_dispatch.
permissions:
contents: read
concurrency:
group: coverage-baseline-repair-${{ github.event_name == 'pull_request_target' && github.event.pull_request.merge_commit_sha || github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.event_name == 'repository_dispatch' && github.event.client_payload.merge_commit_sha || github.sha }}
cancel-in-progress: false
# Retain every pending repair for one target. actionlint v1.7.12's bundled
# schema predates GitHub's concurrency.queue support.
queue: max
jobs:
dispatch-merged-pr:
if: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.merged == true && github.repository == 'DingTalk-Real-AI/dingtalk-workspace-cli' }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
actions: read
contents: write
pull-requests: read
steps:
# Never check out or execute pull-request content in this privileged
# base-owned event. Re-read the merged PR, bind every immutable identity,
# prove the result is in main, and send only those values to the producer.
- name: Dispatch trusted merged-PR repair
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const eventPull = context.payload.pull_request;
const fullCommit = /^[0-9a-f]{40}$/;
const pullNumber = Number(eventPull?.number);
const headSha = eventPull?.head?.sha;
const baseRef = eventPull?.base?.ref;
const mergeCommitSha = eventPull?.merge_commit_sha;
if (
context.payload.repository?.full_name !==
'DingTalk-Real-AI/dingtalk-workspace-cli' ||
context.payload.repository?.default_branch !== 'main' ||
!Number.isSafeInteger(pullNumber) ||
pullNumber <= 0 ||
!fullCommit.test(headSha || '') ||
baseRef !== 'main' ||
!fullCommit.test(mergeCommitSha || '')
) {
throw new Error('closed PR event has an invalid repository or revision identity');
}
// REST base.sha follows the live base branch and can move after
// merge. Bind the closed event's stable PR head snapshot and merge
// facts, then authorize the target through main containment.
function isStableMergedPRIdentity(
currentPull,
pullNumber,
headSha,
mergeCommitSha,
) {
return (
currentPull?.number === pullNumber &&
currentPull.state === 'closed' &&
currentPull.merged === true &&
typeof currentPull.merged_at === 'string' &&
currentPull.merged_at.length > 0 &&
currentPull.base?.ref === 'main' &&
currentPull.head?.sha === headSha &&
currentPull.merge_commit_sha === mergeCommitSha
);
}
const {data: currentPull} = await github.rest.pulls.get({
owner,
repo,
pull_number: pullNumber,
});
if (!isStableMergedPRIdentity(
currentPull,
pullNumber,
headSha,
mergeCommitSha,
)) {
throw new Error(`PR #${pullNumber} no longer matches the merged-main event`);
}
async function requireMainContainment(targetSha) {
let lastState = 'not checked';
for (let attempt = 1; attempt <= 6; attempt += 1) {
try {
const {data: branch} = await github.rest.repos.getBranch({
owner,
repo,
branch: 'main',
});
const {data: comparison} =
await github.rest.repos.compareCommitsWithBasehead({
owner,
repo,
basehead: `${targetSha}...${branch.commit.sha}`,
});
lastState = comparison.status;
if (['ahead', 'identical'].includes(comparison.status)) {
return;
}
} catch (error) {
lastState = error.message;
}
if (attempt < 6) {
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
throw new Error(
`${targetSha} is not contained in protected main after retries: ${lastState}`,
);
}
await requireMainContainment(mergeCommitSha);
// Normal App or human merges emit a protected-main push run whose
// CI producer owns this exact key. Give Actions event delivery a
// short visibility window and avoid a duplicate full-suite repair.
// A workflow-skip directive or suppressed built-in-token event has
// no such run, so only that missing-event path reaches dispatch.
const {data: ciWorkflow} = await github.rest.actions.getWorkflow({
owner,
repo,
workflow_id: '.github/workflows/ci.yml',
});
if (
ciWorkflow.name !== 'CI' ||
ciWorkflow.path !== '.github/workflows/ci.yml' ||
ciWorkflow.state !== 'active'
) {
throw new Error('protected CI workflow identity is not active or exact');
}
for (let attempt = 1; attempt <= 12; attempt += 1) {
const {data: workflowRuns} =
await github.rest.actions.listWorkflowRunsForRepo({
owner,
repo,
branch: 'main',
event: 'push',
per_page: 100,
});
const exactPushRun = workflowRuns.workflow_runs.find(run =>
run.name === 'CI' &&
run.workflow_id === ciWorkflow.id &&
run.path === ciWorkflow.path &&
run.event === 'push' &&
run.head_sha === mergeCommitSha &&
run.head_branch === 'main' &&
['queued', 'in_progress', 'completed'].includes(run.status),
);
if (exactPushRun) {
core.info(
`CI push run ${exactPushRun.id} already owns the exact-SHA producer for ${mergeCommitSha}; repair dispatch is unnecessary.`,
);
return;
}
if (attempt < 12) {
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
// repository_dispatch is one of GitHub's explicit GITHUB_TOKEN
// recursion exceptions and receives default-branch cache-write scope.
await github.rest.repos.createDispatchEvent({
owner,
repo,
event_type: 'coverage-baseline-repair',
client_payload: {
source: 'merged_pr',
pull_number: String(pullNumber),
head_sha: headSha,
merge_commit_sha: mergeCommitSha,
source_run_id: String(context.runId),
},
});
core.info(
`Dispatched exact-SHA coverage repair for merged PR #${pullNumber} at ${mergeCommitSha}.`,
);
dispatch-failed-ci:
if: >-
${{
github.event_name == 'workflow_run' &&
github.repository == 'DingTalk-Real-AI/dingtalk-workspace-cli' &&
github.event.workflow_run.name == 'CI' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.status == 'completed' &&
github.event.workflow_run.conclusion != 'success'
}}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
actions: read
contents: write
steps:
# workflow_run cannot write the default-branch cache. Re-read the exact
# completed CI run from Actions, bind it to the protected CI workflow and
# main revision, then use the repository_dispatch recursion exception.
- name: Dispatch trusted failed-CI repair
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const upstream = 'DingTalk-Real-AI/dingtalk-workspace-cli';
const eventRun = context.payload.workflow_run;
const fullCommit = /^[0-9a-f]{40}$/;
const runID = Number(eventRun?.id);
const runAttempt = Number(eventRun?.run_attempt);
const headSha = eventRun?.head_sha;
const conclusion = eventRun?.conclusion;
if (
context.payload.repository?.full_name !== upstream ||
context.payload.repository?.default_branch !== 'main' ||
!Number.isSafeInteger(runID) ||
runID <= 0 ||
!Number.isSafeInteger(runAttempt) ||
runAttempt <= 0 ||
eventRun?.name !== 'CI' ||
eventRun?.event !== 'push' ||
eventRun?.head_branch !== 'main' ||
eventRun?.status !== 'completed' ||
typeof conclusion !== 'string' ||
conclusion.length === 0 ||
conclusion === 'success' ||
!fullCommit.test(headSha || '')
) {
throw new Error('workflow_run event is not one completed non-success main CI push');
}
const {data: ciWorkflow} = await github.rest.actions.getWorkflow({
owner,
repo,
workflow_id: '.github/workflows/ci.yml',
});
const {data: currentRun} = await github.rest.actions.getWorkflowRun({
owner,
repo,
run_id: runID,
});
if (
ciWorkflow.name !== 'CI' ||
ciWorkflow.path !== '.github/workflows/ci.yml' ||
eventRun.workflow_id !== ciWorkflow.id ||
currentRun.id !== runID ||
currentRun.workflow_id !== ciWorkflow.id ||
currentRun.name !== 'CI' ||
currentRun.event !== 'push' ||
currentRun.head_branch !== 'main' ||
currentRun.head_sha !== headSha ||
currentRun.run_attempt !== runAttempt ||
currentRun.status !== 'completed' ||
currentRun.conclusion !== conclusion ||
currentRun.conclusion === 'success' ||
currentRun.repository?.full_name !== upstream ||
currentRun.head_repository?.full_name !== upstream
) {
throw new Error(`CI workflow run ${runID} no longer matches the completed event`);
}
await github.rest.repos.createDispatchEvent({
owner,
repo,
event_type: 'coverage-baseline-repair',
client_payload: {
source: 'failed_ci',
workflow_run_id: String(runID),
workflow_run_attempt: String(runAttempt),
workflow_conclusion: conclusion,
merge_commit_sha: headSha,
source_run_id: String(context.runId),
},
});
core.info(
`Dispatched exact-SHA coverage repair for ${conclusion} CI run ${runID} at ${headSha}.`,
);
repair:
if: ${{ github.event_name == 'repository_dispatch' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
timeout-minutes: 35
permissions:
actions: read
contents: read
pull-requests: read
steps:
- name: Resolve trusted main repair target
id: resolve-target
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const fullCommit = /^[0-9a-f]{40}$/;
if (
context.payload.repository?.full_name !==
'DingTalk-Real-AI/dingtalk-workspace-cli' ||
context.payload.repository?.default_branch !== 'main'
) {
throw new Error('coverage repair is restricted to the protected upstream repository');
}
async function requireMainContainment(targetSha) {
let lastState = 'not checked';
for (let attempt = 1; attempt <= 6; attempt += 1) {
try {
const {data: branch} = await github.rest.repos.getBranch({
owner,
repo,
branch: 'main',
});
const {data: comparison} =
await github.rest.repos.compareCommitsWithBasehead({
owner,
repo,
basehead: `${targetSha}...${branch.commit.sha}`,
});
lastState = comparison.status;
if (['ahead', 'identical'].includes(comparison.status)) {
return branch.commit.sha;
}
} catch (error) {
lastState = error.message;
}
if (attempt < 6) {
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
throw new Error(
`${targetSha} is not contained in protected main after retries: ${lastState}`,
);
}
// The dispatcher froze the stable PR head snapshot in this payload.
// Do not re-read mutable base.sha; bind the head and stable merge
// facts, then prove protected-main containment below.
function isStableMergedPRIdentity(
currentPull,
pullNumber,
headSha,
mergeCommitSha,
) {
return (
currentPull?.number === pullNumber &&
currentPull.state === 'closed' &&
currentPull.merged === true &&
typeof currentPull.merged_at === 'string' &&
currentPull.merged_at.length > 0 &&
currentPull.base?.ref === 'main' &&
currentPull.head?.sha === headSha &&
currentPull.merge_commit_sha === mergeCommitSha
);
}
let targetSha;
if (context.eventName === 'repository_dispatch') {
const payload = context.payload.client_payload || {};
const sourceRunIDText = String(payload.source_run_id || '');
if (!/^[1-9][0-9]*$/.test(sourceRunIDText)) {
throw new Error('coverage-baseline-repair payload has an invalid source run');
}
if (payload.source === 'merged_pr') {
const rawPullNumber = String(payload.pull_number || '');
const pullNumber = Number(rawPullNumber);
const headSha = payload.head_sha;
targetSha = payload.merge_commit_sha;
if (
!/^[1-9][0-9]*$/.test(rawPullNumber) ||
!Number.isSafeInteger(pullNumber) ||
!fullCommit.test(headSha || '') ||
!fullCommit.test(targetSha || '')
) {
throw new Error('coverage-baseline-repair payload has an invalid PR identity');
}
const {data: currentPull} = await github.rest.pulls.get({
owner,
repo,
pull_number: pullNumber,
});
if (!isStableMergedPRIdentity(
currentPull,
pullNumber,
headSha,
targetSha,
)) {
throw new Error(
`repair payload no longer matches merged PR #${pullNumber}`,
);
}
} else if (payload.source === 'failed_ci') {
const rawWorkflowRunID = String(payload.workflow_run_id || '');
const workflowRunID = Number(rawWorkflowRunID);
const rawWorkflowRunAttempt = String(payload.workflow_run_attempt || '');
const workflowRunAttempt = Number(rawWorkflowRunAttempt);
const workflowConclusion = payload.workflow_conclusion;
targetSha = payload.merge_commit_sha;
if (
!/^[1-9][0-9]*$/.test(rawWorkflowRunID) ||
!Number.isSafeInteger(workflowRunID) ||
!/^[1-9][0-9]*$/.test(rawWorkflowRunAttempt) ||
!Number.isSafeInteger(workflowRunAttempt) ||
typeof workflowConclusion !== 'string' ||
workflowConclusion.length === 0 ||
workflowConclusion === 'success' ||
!fullCommit.test(targetSha || '')
) {
throw new Error('coverage-baseline-repair payload has an invalid CI identity');
}
const {data: ciWorkflow} = await github.rest.actions.getWorkflow({
owner,
repo,
workflow_id: '.github/workflows/ci.yml',
});
const {data: currentRun} = await github.rest.actions.getWorkflowRun({
owner,
repo,
run_id: workflowRunID,
});
if (
ciWorkflow.name !== 'CI' ||
ciWorkflow.path !== '.github/workflows/ci.yml' ||
currentRun.id !== workflowRunID ||
currentRun.workflow_id !== ciWorkflow.id ||
currentRun.name !== 'CI' ||
currentRun.event !== 'push' ||
currentRun.head_branch !== 'main' ||
currentRun.head_sha !== targetSha ||
currentRun.run_attempt !== workflowRunAttempt ||
currentRun.status !== 'completed' ||
currentRun.conclusion !== workflowConclusion ||
currentRun.conclusion === 'success' ||
currentRun.repository?.full_name !==
'DingTalk-Real-AI/dingtalk-workspace-cli' ||
currentRun.head_repository?.full_name !==
'DingTalk-Real-AI/dingtalk-workspace-cli'
) {
throw new Error(
`repair payload no longer matches failed CI run ${workflowRunID}`,
);
}
} else {
throw new Error('coverage-baseline-repair payload has an unknown source');
}
await requireMainContainment(targetSha);
} else {
if (context.ref !== 'refs/heads/main') {
throw new Error('scheduled and manual repair must run from refs/heads/main');
}
// github.sha is the default-branch tip that keyed this workflow's
// concurrency group. Keep the producer bound to that exact
// event-time target even if main advances while this run queues.
targetSha = context.sha;
if (!fullCommit.test(targetSha || '')) {
throw new Error('protected main did not resolve to one full commit SHA');
}
await requireMainContainment(targetSha);
}
core.setOutput('target_sha', targetSha);
core.info(`Resolved protected-main coverage repair target ${targetSha}.`);
- name: Check out exact protected-main target
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ steps.resolve-target.outputs.target_sha }}
- name: Verify checked-out repair target
env:
TARGET_SHA: ${{ steps.resolve-target.outputs.target_sha }}
run: test "$(git rev-parse HEAD)" = "$TARGET_SHA"
- name: Set up Go
id: setup-go
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version-file: go.mod
- name: Restore exact target coverage profile
id: target-cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: coverage-cache.txt
key: dws-coverage-full-v2-${{ steps.resolve-target.outputs.target_sha }}-go${{ steps.setup-go.outputs.go-version }}
- name: Validate existing exact target profile
if: steps.target-cache.outputs.cache-hit == 'true'
run: |
set -eu
test -s coverage-cache.txt
test "$(head -n 1 coverage-cache.txt)" = "mode: atomic"
- name: Install archive tooling for cold repair
if: steps.target-cache.outputs.cache-hit != 'true'
run: |
if command -v zip >/dev/null && command -v unzip >/dev/null; then
echo "zip and unzip are already available"
else
sudo apt-get update
sudo apt-get install -y zip unzip
fi
- name: Recompute complete target coverage profile
if: steps.target-cache.outputs.cache-hit != 'true'
env:
DWS_PACKAGE_VERSION: 0.0.0-test
run: |
set -euo pipefail
go test -count=1 -p 1 \
-coverprofile=coverage-cache.txt \
-covermode=atomic \
./ ./cmd/... ./internal/... ./skills/...
test -s coverage-cache.txt
test "$(head -n 1 coverage-cache.txt)" = "mode: atomic"
- name: Save exact protected-main coverage profile
if: steps.target-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: coverage-cache.txt
key: dws-coverage-full-v2-${{ steps.resolve-target.outputs.target_sha }}-go${{ steps.setup-go.outputs.go-version }}
# Cache uploads are fail-open warnings. A lookup-only restore plus the
# explicit cache-hit assertion makes an absent or partial key fail hard.
- name: Verify exact protected-main coverage cache exists
id: target-cache-verification
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: coverage-cache.txt
key: dws-coverage-full-v2-${{ steps.resolve-target.outputs.target_sha }}-go${{ steps.setup-go.outputs.go-version }}
lookup-only: true
fail-on-cache-miss: true
- name: Require exact protected-main coverage cache
env:
EXACT_CACHE_HIT: ${{ steps.target-cache-verification.outputs.cache-hit }}
run: test "$EXACT_CACHE_HIT" = true
+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
+15 -9
View File
@@ -14,9 +14,12 @@ jobs:
uses: actions/github-script@v7
with:
script: |
const webhook = process.env.DINGTALK_WEBHOOK;
if (!webhook) {
console.log('⚠️ DINGTALK_WEBHOOK not set, skipping notification');
const webhooks = [
process.env.DINGTALK_WEBHOOK,
process.env.DINGTALK_WEBHOOK_SECONDARY
].filter(Boolean);
if (webhooks.length === 0) {
console.log('⚠️ No DingTalk webhook configured, skipping notification');
return;
}
@@ -39,12 +42,15 @@ jobs:
}
};
await fetch(webhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message)
});
await Promise.all(webhooks.map(webhook =>
fetch(webhook, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message)
})
));
console.log('✅ DingTalk notification sent');
console.log(`✅ DingTalk notification sent to ${webhooks.length} webhook(s)`);
env:
DINGTALK_WEBHOOK: ${{ secrets.DINGTALK_WEBHOOK }}
DINGTALK_WEBHOOK_SECONDARY: ${{ secrets.DINGTALK_WEBHOOK_SECONDARY }}
+169 -3
View File
@@ -1112,6 +1112,9 @@ jobs:
needs: [release-contract, release-validation, release, verify-darwin-signatures]
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
coverage_baseline_required: ${{ steps.seal-formula.outputs.coverage_baseline_required }}
coverage_baseline_commit: ${{ steps.seal-formula.outputs.coverage_baseline_commit }}
permissions:
checks: write
contents: write
@@ -1495,6 +1498,7 @@ jobs:
DWS_GIT_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
- name: Seal Formula-only Code Admission contexts
id: seal-formula
if: ${{ github.repository_owner == 'DingTalk-Real-AI' }}
uses: actions/github-script@v7
env:
@@ -1515,6 +1519,8 @@ jobs:
const sourcePath = channel === "stable"
? "dist/homebrew/dingtalk-workspace-cli.rb"
: "dist/homebrew/dingtalk-workspace-cli-beta.rb";
core.setOutput("coverage_baseline_required", "false");
core.setOutput("coverage_baseline_commit", "");
const expectedMessage = channel === "stable"
? `chore: update formula for ${version} [skip ci]`
: `chore: update beta formula for ${version} [skip ci]`;
@@ -1650,6 +1656,11 @@ jobs:
},
});
}
core.setOutput("coverage_baseline_required", "true");
core.setOutput("coverage_baseline_commit", commit);
core.info(
`Formula-only Code Admission is sealed for ${commit}; the independent confirmation job will dispatch its exact-SHA cache producer.`,
);
- name: Reverify exact immutable npm package
run: ./scripts/release/verify-package-managers.sh --npm-only --expected-version "$RELEASE_VERSION"
@@ -2177,6 +2188,117 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
coverage-baseline-confirmation:
name: Confirm Formula coverage baseline
# Once Formula sealing has exposed a target SHA, later publication
# verification failures must not orphan its exact-main cache producer.
if: ${{ !cancelled() && (needs.publish-release.result == 'success' || needs.publish-release.outputs.coverage_baseline_required == 'true') }}
needs: publish-release
runs-on: ubuntu-latest
timeout-minutes: 35
permissions:
checks: write
contents: write
steps:
- name: Require exact Formula cache acknowledgement
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
env:
BASELINE_REQUIRED: ${{ needs.publish-release.outputs.coverage_baseline_required }}
FORMULA_COMMIT: ${{ needs.publish-release.outputs.coverage_baseline_commit }}
with:
script: |
const rawRequired = process.env.BASELINE_REQUIRED;
if (!['true', 'false'].includes(rawRequired)) {
throw new Error(`Formula baseline requirement is invalid: ${rawRequired || 'empty'}`);
}
const required = rawRequired === 'true';
const targetSha = process.env.FORMULA_COMMIT;
if (!required) {
if (targetSha) {
throw new Error('Formula baseline outputs are inconsistent for a no-op publication');
}
core.info('Formula was already current; no new exact-SHA cache acknowledgement is required.');
return;
}
if (!/^[0-9a-f]{40}$/.test(targetSha)) {
throw new Error('Formula baseline target output is malformed');
}
const expectedExternalId = `release-${context.runId}-${targetSha}`;
let promotionCheck;
try {
const created = await github.rest.checks.create({
...context.repo,
name: 'Coverage Baseline Cache',
head_sha: targetSha,
status: 'queued',
external_id: expectedExternalId,
output: {
title: 'Waiting for exact-SHA baseline promotion',
summary:
'The independent release governance job is waiting for the default-branch cache producer.',
},
});
promotionCheck = created.data;
await github.rest.repos.createDispatchEvent({
...context.repo,
event_type: 'coverage-baseline-promote',
client_payload: {
target_sha: targetSha,
source_run_id: String(context.runId),
check_run_id: String(promotionCheck.id),
},
});
} catch (error) {
if (promotionCheck) {
try {
await github.rest.checks.update({
...context.repo,
check_run_id: promotionCheck.id,
status: 'completed',
conclusion: 'failure',
completed_at: new Date().toISOString(),
output: {
title: 'Coverage baseline dispatch failed',
summary: `Release could not dispatch the exact-SHA producer: ${error.message}`,
},
});
} catch (cleanupError) {
core.error(
`Could not close failed cache acknowledgement ${promotionCheck.id}: ${cleanupError.message}`,
);
}
}
throw error;
}
const checkRunId = promotionCheck.id;
for (let attempt = 1; attempt <= 180; attempt += 1) {
const {data: currentCheck} = await github.rest.checks.get({
...context.repo,
check_run_id: checkRunId,
});
if (
currentCheck.head_sha !== targetSha ||
currentCheck.name !== 'Coverage Baseline Cache' ||
currentCheck.external_id !== expectedExternalId ||
currentCheck.app?.slug !== 'github-actions'
) {
throw new Error('Formula baseline promotion acknowledgement changed identity');
}
if (currentCheck.status === 'completed') {
if (currentCheck.conclusion !== 'success') {
throw new Error(
`Formula baseline promotion failed with ${currentCheck.conclusion || 'unknown'}`,
);
}
core.info(`Formula baseline promotion completed for ${targetSha}.`);
return;
}
if (attempt < 180) {
await new Promise(resolve => setTimeout(resolve, 10000));
}
}
throw new Error(`Formula baseline promotion timed out for ${targetSha}`);
release-delivery-gate:
name: Release delivery gate
if: ${{ !cancelled() }}
@@ -2189,6 +2311,7 @@ jobs:
- verify-darwin-signatures
- publish-release
- publish-channels
- coverage-baseline-confirmation
- mirror-gitee-release
- repair-npm
- repair-channel
@@ -2211,6 +2334,7 @@ jobs:
DARWIN_SIGNATURE_RESULT: ${{ needs.verify-darwin-signatures.result }}
PUBLISH_RELEASE_RESULT: ${{ needs.publish-release.result }}
PUBLISH_CHANNELS_RESULT: ${{ needs.publish-channels.result }}
COVERAGE_BASELINE_CONFIRMATION_RESULT: ${{ needs.coverage-baseline-confirmation.result }}
MIRROR_GITEE_RESULT: ${{ needs.mirror-gitee-release.result }}
REPAIR_NPM_RESULT: ${{ needs.repair-npm.result }}
REPAIR_CHANNEL_RESULT: ${{ needs.repair-channel.result }}
@@ -2234,6 +2358,7 @@ jobs:
require_result verify-darwin-signatures "$DARWIN_SIGNATURE_RESULT" success
require_result publish-release "$PUBLISH_RELEASE_RESULT" success
require_result publish-channels "$PUBLISH_CHANNELS_RESULT" success
require_result coverage-baseline-confirmation "$COVERAGE_BASELINE_CONFIRMATION_RESULT" success
if test "$GITEE_FALLBACK_ENABLED" = true; then
require_result mirror-gitee-release "$MIRROR_GITEE_RESULT" success
else
@@ -2273,6 +2398,7 @@ jobs:
require_result verify-darwin-signatures "$DARWIN_SIGNATURE_RESULT" skipped
require_result publish-release "$PUBLISH_RELEASE_RESULT" skipped
require_result publish-channels "$PUBLISH_CHANNELS_RESULT" skipped
require_result coverage-baseline-confirmation "$COVERAGE_BASELINE_CONFIRMATION_RESULT" skipped
require_result mirror-gitee-release "$MIRROR_GITEE_RESULT" skipped
require_result repair-npm "$REPAIR_NPM_RESULT" skipped
require_result repair-channel "$REPAIR_CHANNEL_RESULT" skipped
@@ -2294,6 +2420,7 @@ jobs:
require_result verify-darwin-signatures "$DARWIN_SIGNATURE_RESULT" skipped
require_result publish-release "$PUBLISH_RELEASE_RESULT" skipped
require_result publish-channels "$PUBLISH_CHANNELS_RESULT" skipped
require_result coverage-baseline-confirmation "$COVERAGE_BASELINE_CONFIRMATION_RESULT" skipped
require_result mirror-gitee-release "$MIRROR_GITEE_RESULT" skipped
require_result repair-npm "$REPAIR_NPM_RESULT" skipped
require_result repair-channel "$REPAIR_CHANNEL_RESULT" skipped
@@ -2309,6 +2436,7 @@ jobs:
require_result verify-darwin-signatures "$DARWIN_SIGNATURE_RESULT" skipped
require_result publish-release "$PUBLISH_RELEASE_RESULT" skipped
require_result publish-channels "$PUBLISH_CHANNELS_RESULT" skipped
require_result coverage-baseline-confirmation "$COVERAGE_BASELINE_CONFIRMATION_RESULT" skipped
require_result mirror-gitee-release "$MIRROR_GITEE_RESULT" skipped
require_result repair-channel "$REPAIR_CHANNEL_RESULT" skipped
require_cloud_jobs_skipped
@@ -2323,6 +2451,7 @@ jobs:
require_result verify-darwin-signatures "$DARWIN_SIGNATURE_RESULT" skipped
require_result publish-release "$PUBLISH_RELEASE_RESULT" skipped
require_result publish-channels "$PUBLISH_CHANNELS_RESULT" skipped
require_result coverage-baseline-confirmation "$COVERAGE_BASELINE_CONFIRMATION_RESULT" skipped
require_result mirror-gitee-release "$MIRROR_GITEE_RESULT" skipped
require_result repair-npm "$REPAIR_NPM_RESULT" skipped
require_cloud_jobs_skipped
@@ -2337,6 +2466,7 @@ jobs:
require_result verify-darwin-signatures "$DARWIN_SIGNATURE_RESULT" skipped
require_result publish-release "$PUBLISH_RELEASE_RESULT" skipped
require_result publish-channels "$PUBLISH_CHANNELS_RESULT" skipped
require_result coverage-baseline-confirmation "$COVERAGE_BASELINE_CONFIRMATION_RESULT" skipped
require_result mirror-gitee-release "$MIRROR_GITEE_RESULT" skipped
require_result repair-npm "$REPAIR_NPM_RESULT" skipped
require_cloud_jobs_skipped
@@ -2645,6 +2775,40 @@ jobs:
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/verify-github-tag-authority.sh" \
"$RELEASE_VERSION" "$RELEASE_COMMIT" "$RELEASE_TAG_OBJECT"
# The sealed candidate tag is intentionally visible while its GitHub
# authority is checked above. Compatibility must instead discover the
# previous delivered stable tag, so hide only this verified candidate
# from this isolated runner's local tag namespace.
- name: Prepare delivered-stable compatibility ref view
if: ${{ matrix.check == 'compatibility' }}
env:
RELEASE_VERSION: ${{ needs.release-contract.outputs.release_version }}
RELEASE_COMMIT: ${{ needs.release-contract.outputs.release_commit }}
RELEASE_TAG_OBJECT: ${{ needs.release-contract.outputs.release_tag_object }}
PREVIOUS_STABLE: ${{ needs.release-contract.outputs.previous_stable }}
PREVIOUS_STABLE_COMMIT: ${{ needs.release-contract.outputs.previous_stable_commit }}
run: |
set -eu
test -n "$RELEASE_VERSION"
test -n "$RELEASE_COMMIT"
test -n "$RELEASE_TAG_OBJECT"
test -n "$PREVIOUS_STABLE"
test -n "$PREVIOUS_STABLE_COMMIT"
test "$RELEASE_VERSION" != "$PREVIOUS_STABLE"
test "$(git rev-parse HEAD)" = "$RELEASE_COMMIT"
test "$(git rev-parse --verify "refs/tags/${RELEASE_VERSION}")" = "$RELEASE_TAG_OBJECT"
test "$(git rev-parse --verify "refs/tags/${RELEASE_VERSION}^{commit}")" = "$RELEASE_COMMIT"
test "$(git rev-parse --verify "${PREVIOUS_STABLE}^{commit}")" = "$PREVIOUS_STABLE_COMMIT"
git update-ref -d "refs/tags/${RELEASE_VERSION}" "$RELEASE_TAG_OBJECT"
if git show-ref --verify --quiet "refs/tags/${RELEASE_VERSION}"; then
echo "sealed candidate tag is still visible to compatibility baseline discovery" >&2
exit 2
fi
test "$(git rev-parse HEAD)" = "$RELEASE_COMMIT"
test "$(git rev-parse --verify "${PREVIOUS_STABLE}^{commit}")" = "$PREVIOUS_STABLE_COMMIT"
- name: Set up Go
uses: actions/setup-go@v5
with:
@@ -2664,9 +2828,11 @@ jobs:
;;
compatibility)
test -n "$PREVIOUS_STABLE"
./scripts/policy/check-command-compatibility.sh \
"$GITHUB_WORKSPACE/tmp/trusted-release-tooling/scripts/release/check-release-compatibility.sh" \
--repo-root "$GITHUB_WORKSPACE" \
--base-ref HEAD \
--stable-ref "$PREVIOUS_STABLE"
--stable-ref "$PREVIOUS_STABLE" \
--candidate-ref HEAD
;;
e2e)
bash scripts/dev/test-multi-profile-e2e.sh
@@ -2793,7 +2959,7 @@ jobs:
fi
if test "${{ needs.dispatch-contract.outputs.mode }}" = plan_release; then
echo
echo "Plan only: no tag or package was created. Add the exact \`CHANGELOG.md\` section, merge it to main, then run publish."
echo "Plan only: no tag or package was created. Render pending \`.changes/*.md\` fragments into the exact \`CHANGELOG.md\` section, merge the release-seal PR to main, then run publish."
fi
} >> "$GITHUB_STEP_SUMMARY"
@@ -0,0 +1,19 @@
name: Reviewer Router approval signal
on:
pull_request_review:
types: [submitted, dismissed]
# This workflow only converts an approval-state change into a trusted
# workflow_run event. It must never read secrets, check out code, or mutate the
# pull request; the default-branch Reviewer routing workflow owns reconciliation.
permissions: {}
jobs:
signal:
runs-on: ubuntu-latest
timeout-minutes: 1
permissions: {}
steps:
- name: Signal approval-state change
run: echo "Review state changed; default-branch reconciliation will re-evaluate App-owned merge intents."
File diff suppressed because it is too large Load Diff
+4
View File
@@ -20,6 +20,10 @@ test/cli_compat/testdata/
.gitignore
.worktrees/
.qoder/
_logs/
_docs/
_output/
vendor/
# Secrets & credentials
.env
+147 -13
View File
@@ -44,7 +44,8 @@ Schema contract) keep separate authorities — do not merge them with
## Command framework declaration
- Framework definition: `docs/rfc-command-framework-convergence.md` **§5.0**
- Today: `helpers.LeafSpec` / `shortcut.Shortcut` → `corecmd.Spec` (+ optional `Contract`) → `corecmd.New`
- Today (leaf): `helpers.LeafSpec` / `shortcut.Shortcut` → `corecmd.Spec` (+ optional `Contract`) → `corecmd.New`
- Today (non-leaf): owning Cobra command → complete `corecmd.GroupPolicy{Mode, Positionals, Recovery}` → `corecmd.ApplyGroupPolicy`; the final assembled-tree gate rejects undeclared groups and stale group declarations on leaves
- **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.
@@ -60,6 +61,7 @@ Schema contract) keep separate authorities — do not merge them with
- **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.
- Group policy is separate from the leaf tiers: `corecmd.Spec` remains leaf-only. `ApplyGroupPolicy` must not infer or enable `TraverseChildren`; parent local-flag inheritance remains an explicit owning-command surface.
- 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
@@ -302,9 +304,8 @@ on the leaf:
```bash
dws auth status # token_valid should be true
dws cache refresh # deprecated no-op: prints a retirement notice (discovery cache is gone; refreshes nothing)
dws schema <mcp-canonical> -f json
# or CLI path: dws schema --cli-path "drive copy" -f json
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
@@ -321,8 +322,10 @@ Skill (evidence only)**.
Split work by product groups. Each agent must:
- Read Skill, Cobra/`--help`, Runtime confirmation sites, and live `dws schema`
for its tools.
- 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).
@@ -463,6 +466,134 @@ 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
@@ -471,13 +602,16 @@ path; a generator unit test or JSON count alone is insufficient.
`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 -> product/group
-> leaf for routine Agent work. `--compact` is supported for context-saving
projections, but a compact full export is not a complete compatibility
baseline.
- `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 leaf Schema defines Agent selection, parameter mapping
and constraints, and safety/confirmation semantics. A conflict is contract
drift, not permission to guess.
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.
+675
View File
File diff suppressed because one or more lines are too long
+15 -4
View File
@@ -68,14 +68,25 @@ coverage is additionally selected for platform-sensitive code.
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 also runs
`./scripts/policy/check-command-compatibility.sh --base-ref <main-ref> --stable-ref <latest-GA-tag>`
against both the target branch and latest stable release.
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 `CHANGELOG.md` for behavior/interface changes.
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
+11 -11
View File
@@ -1,33 +1,33 @@
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.56-beta.4"
version "1.0.60-beta.2"
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.56-beta.4/dws-darwin-arm64.tar.gz"
sha256 "f1f9b6394137edbd0b08d632aab34e92a0f3f81d80107a47de1bec9b384f0515"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.60-beta.2/dws-darwin-arm64.tar.gz"
sha256 "e7776807f0664cbf0d0728cc236f2415c0981eb8d6557a897d2eeee708641b1d"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.4/dws-darwin-amd64.tar.gz"
sha256 "cd3c64d20723c420e2490405d0bf8eecfd7e2b8fc352f63f23de5847a1d38f55"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.60-beta.2/dws-darwin-amd64.tar.gz"
sha256 "3004474df3cfb529719348f02c9f2f39afa88f0fca469fe8303a9ebe0f3a0034"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.4/dws-linux-arm64.tar.gz"
sha256 "910918d88074534e680a2e320d3cb364ad092e96b9c422f9e75d11c9c0815dd8"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.60-beta.2/dws-linux-arm64.tar.gz"
sha256 "6386885d10f149c8c555031dda4cf07bf34e1e9daad61d4cd948b92d3c7b7bad"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.4/dws-linux-amd64.tar.gz"
sha256 "172fe0d84443be953d0c6f2c2433540e4b972fbe7776cff1417ec9c73723552b"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.60-beta.2/dws-linux-amd64.tar.gz"
sha256 "5c94c2af269d2fe5a79a400d4fa3af267a86d6ab21b01a24ede1d29514a6eaef"
end
end
resource "skills" do
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56-beta.4/dws-skills.zip"
sha256 "a3457befe858cbf3fe85848428b630bfd3a5f626256ed6b49415267948915152"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.60-beta.2/dws-skills.zip"
sha256 "c3bd917f1b44a978ba2a9fbe95c5d0910ccf75f870f1c9b0dc356262ab1080c5"
end
def install
+11 -11
View File
@@ -1,33 +1,33 @@
class DingtalkWorkspaceCli < Formula
desc "Automate DingTalk workspace tasks from the terminal"
homepage "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli"
version "1.0.56"
version "1.0.59"
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.56/dws-darwin-arm64.tar.gz"
sha256 "5c6003fe484aa36cc00820a574186652467b9d075f19c159cf807e57590256ba"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59/dws-darwin-arm64.tar.gz"
sha256 "61135a2a9286204ce060847e653c63c1e9784a0fa631bb7e0563b90628762a35"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56/dws-darwin-amd64.tar.gz"
sha256 "969b005a10682c2a1a828fa112165b5b0cd8ceeed8d22110ef7f39402cc36804"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59/dws-darwin-amd64.tar.gz"
sha256 "fd14b0b1a1475891fb243bf6453857a1044ab5a40bcf7dc1c7c795f57e5b03ba"
end
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56/dws-linux-arm64.tar.gz"
sha256 "530c5ea7ddc7de320d9c2471fbd33752a723d00c9665f49321c7580e8392c756"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59/dws-linux-arm64.tar.gz"
sha256 "5bfe9ac7d1798b028f0fad579bbdffec5898e2fb16ee36f5766ab58e208abd50"
else
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56/dws-linux-amd64.tar.gz"
sha256 "675fa42727ac9a549c6710b82e1980cd0f795363d71d5116a4e69771b7c5470e"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59/dws-linux-amd64.tar.gz"
sha256 "be1eb9a1f8fc5048e578b5b0bde212fc90baca0f289236c7c333d824bd869cf3"
end
end
resource "skills" do
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.56/dws-skills.zip"
sha256 "3d57794e4660a089209ce3962571d16ca0d46141e973c9993257a301cce0e097"
url "https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v1.0.59/dws-skills.zip"
sha256 "7ce5c3ab6f6a367407f64971bc5ff96cfcdfade2c1a10d326144b17c7b25a57e"
end
def install
+49 -12
View File
@@ -10,7 +10,7 @@ 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 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
.PHONY: all help build rebuild test test-plan test-auth-legacy-compat shortcut-public-e2e-proof 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
@@ -18,19 +18,20 @@ 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 test-plan - Verify every default Go package belongs to one CI test shard\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 shortcut-public-e2e-proof - Prove every reviewed Devdoc/HRbrain/PAT public Shortcut through exact and owning raw execution\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 - Check historical commands and help contracts still work\n"
@printf " make authoritative-interface-integrity BASE_REF=<ref> - Check the Git-owned PR merge-base\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 - Add new CLI contracts without removing history\n"
@printf " make reset-interface-baseline - DANGEROUS: replace all CLI compatibility history\n"
@printf " make schema-compatibility BASE_REF=<ref> - Check the complete Schema contract against the PR merge-base\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"
@@ -62,6 +63,9 @@ test-auth-legacy-compat:
@mkdir -p "$(POLICY_GOTMPDIR)"
@GO="$(GO)" $(POLICY_ENV) ./scripts/policy/check-auth-legacy-compat.sh
shortcut-public-e2e-proof: build
@GO="$(GO)" DWS_PACKAGE_VERSION="$(DWS_PACKAGE_VERSION)" ./scripts/policy/check-shortcut-public-e2e-proof.sh
lint:
@./scripts/dev/lint.sh
@@ -84,11 +88,12 @@ fmt:
$(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
policy: test-auth-legacy-compat shortcut-public-e2e-proof
@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
@@ -102,10 +107,22 @@ edition-test:
$(GO) test -v -count=1 ./pkg/editiontest/...
interface-integrity:
@./scripts/policy/check-interface-baseline.sh
@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:
@./scripts/policy/check-authoritative-interface-baselines.sh --base-ref "$(BASE_REF)"
@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
@@ -120,7 +137,12 @@ reset-interface-baseline:
@./scripts/policy/check-interface-baseline.sh --reset
schema-compatibility:
@./scripts/policy/check-authoritative-schema-compatibility.sh --base-ref "$(BASE_REF)"
@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
@@ -131,6 +153,9 @@ skill-context-budget:
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
@@ -148,9 +173,13 @@ generate-schema:
@set -e; \
concepts_guard=$$(mktemp); \
concepts_schema_guard=$$(mktemp); \
trap 'rm -rf "$$concepts_guard" "$$concepts_schema_guard"' EXIT HUP INT TERM; \
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; \
@@ -166,6 +195,14 @@ generate-schema:
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; \
+64 -44
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** | 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.** All product-scoped skills 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>
@@ -210,7 +210,7 @@ The verifier uses isolated directories and does not replace the `dws` on the cur
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.
@@ -371,7 +371,7 @@ dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserNam
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>"` is the Agent contract for command selection, parameter mappings and constraints, risk, and confirmation semantics.
- `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.
@@ -380,32 +380,32 @@ Use Cobra help and Schema for different parts of the command contract:
dws aitable record query --help
# Discover within a product, then inspect the selected leaf contract
dws schema aitable
dws schema "aitable record query"
dws schema aitable --compact
dws schema "aitable record query" --compact
# 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 prefer product/group discovery followed by a leaf query to avoid loading the full Catalog into context.
`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/`, 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/`, ...), 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).
@@ -415,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>`.
@@ -443,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>
@@ -474,7 +482,7 @@ Env vars: `DWS_SKILL_MODE=mono|multi` (also honored by `install.sh` / `install.p
<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, and group title/disband lifecycle events.
`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`.
@@ -484,28 +492,33 @@ 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 consume user_im_message_receive_at --flatten -f ndjson
dws event +listen-im --kind at-me -f ndjson
# Listen for one-to-one messages with a specified user
dws event consume user_im_message_receive_o2o --user <userId> --flatten -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 consume user_im_message_receive_o2o --open-dingtalk-id <openDingtalkId> --flatten -f ndjson
dws event +listen-im --kind sender --open-dingtalk-id <openDingtalkId> -f ndjson
# Listen for messages in a specified group
dws event consume user_im_message_receive_group --group <openConversationId> --flatten -f ndjson
dws event +listen-im --kind group --chat-id <openConversationId> -f ndjson
# Listen for all one-to-one or all group messages
dws event consume user_im_message_receive_o2o_all --flatten -f ndjson
dws event consume user_im_message_receive_group_all --flatten -f ndjson
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
@@ -513,14 +526,20 @@ dws event consume user_im_group_member_added --group <openConversationId> --flat
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 multiple events for the same user in one process
# 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_im_message_receive_o2o \
user_im_message_read_o2o \
user_im_message_recall_o2o \
--user <userId> \
--flatten \
-f ndjson
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
@@ -624,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
@@ -636,9 +655,9 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocati
<summary><strong>Schema Introspection</strong> — Agent command discovery and execution contracts</summary>
```bash
dws schema aitable # discover product commands
dws schema "aitable record query" # view the selected leaf contract
dws schema "aitable record query" --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
```
@@ -719,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>
@@ -768,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
+65 -45
View File
@@ -19,7 +19,7 @@
</p>
> [!IMPORTANT]
> **共创阶段**:本项目涉及钉钉企业数据访问,需企业管理员授权后方可使用。欢迎加入钉钉 DWS 共创群获取支持与最新动态。详见下方 [开始使用](#开始使用)。
> **钉钉 DWS CLI 已全面开放,欢迎使用**:本项目涉及钉钉企业数据访问,需企业管理员授权后方可使用。欢迎加入钉钉 DWS 共创群获取支持与最新动态。详见下方 [开始使用](#开始使用)。
>
> <img src="https://img.alicdn.com/imgextra/i1/O1CN01WJyAsJ1prD2ovQACM_!!6000000005413-2-tps-718-720.png" alt="dws 开源沟通群二维码" width="150">
@@ -70,17 +70,17 @@ irm https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-workspace-cli/ma
| 模式 | 安装内容 | 适合场景 |
|------|----------|----------|
| **mono**(稳定,默认) | 一个 `dws` skill,覆盖全部产品 | 跨产品组合操作;单一入口召唤 |
| **multi** 🧪 **试验版 / Preview** | 按产品拆分的独立 skill(`dingtalk-aitable` / `dingtalk-calendar` / `dingtalk-chat` ...) | 单产品任务;每次召唤上下文更小 |
| **multi**(默认) | 按产品拆分的独立 skill(`dingtalk-aitable` / `dingtalk-calendar` / `dingtalk-chat` ...) | 单产品任务;每次召唤上下文更小 |
| **mono**(legacy) | 一个 `dws` skill,覆盖全部产品 | 跨产品组合操作;单一入口召唤 |
> 🧪 **multi 模式当前为 EXPERIMENTAL(试验版 / Preview)**。全部独立 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>
@@ -207,7 +207,7 @@ bash verify-all-channels.sh
升级过程采用两阶段原子流程,确保一致性:
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` 随时回滚。
@@ -365,7 +365,7 @@ dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserNam
命令帮助和 Schema 分别负责命令契约的不同部分:
- `dws <path> --help` 是命令是否存在、当前二进制接受哪些 flags 的事实源。
- `dws schema "<path>"` 是 Agent 选命令、参数映射与约束、风险和确认语义的契约。
- `dws schema "<path>" --compact` 是 Agent 选命令、CLI 参数与约束、风险和确认语义的规范视图;映射或 provenance 审计使用 full leaf 配合 `--jq` 精确投影。
- Help 与 Schema 冲突时视为契约漂移:执行只传 Cobra 接受的参数,安全语义取更保守值。
- Schema 只描述命令,不读取或搜索钉钉业务数据;发现命令后仍需执行真实产品命令。
@@ -374,32 +374,32 @@ dws contact user get-self --jq '.result[0].orgEmployeeModel | {name: .orgUserNam
dws aitable record query --help
# 先在产品内发现命令,再查看选中 leaf 的契约
dws schema aitable
dws schema "aitable record query"
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 应优先按产品/分组发现后查询 leaf,避免把整个 Catalog 加载进上下文。
`dws schema --all` 会完整导出命令契约,供工具、CI、审计和兼容性基线使用。Agent 应使用 `--compact` 渐进查询;该视图采用正向字段白名单,full 新增的审计字段不会自动进入 Agent 上下文。
### Agent Skills
仓库内置完整的 Agent Skill 体系(`skills/` 目录),分为两套布局:
- `skills/mono/` — 单 skill 布局(一个 `SKILL.md` + `references/products/`),默认推荐。
- `skills/multi/` — 每个产品一个独立 skill(`dingtalk-aitable/` / `dingtalk-calendar/` / `dingtalk-chat/` ...),每个 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 镜像,见 [国内加速安装](#国内加速安装)。
@@ -409,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=<路径>`。
@@ -437,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>
@@ -468,7 +476,7 @@ 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。当前公开目录覆盖指定范围和全量单聊/群消息、指定发送人、已读/撤回/表情回应,以及群标题变更和群解散事件。
`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` 同时使用。
@@ -478,28 +486,33 @@ DWS_SKILL_SOURCE=/path/to/skills dws skill setup --mode multi
```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 consume user_im_message_receive_at --flatten -f ndjson
dws event +listen-im --kind at-me -f ndjson
# 监听与指定用户的单聊消息
dws event consume user_im_message_receive_o2o --user <userId> --flatten -f ndjson
# 监听指定发送人的消息
dws event +listen-im --kind sender --user <userId> -f ndjson
# 使用 openDingtalkId 监听外部联系人、机器人或跨组织身份
dws event consume user_im_message_receive_o2o --open-dingtalk-id <openDingtalkId> --flatten -f ndjson
dws event +listen-im --kind sender --open-dingtalk-id <openDingtalkId> -f ndjson
# 监听指定群的消息
dws event consume user_im_message_receive_group --group <openConversationId> --flatten -f ndjson
dws event +listen-im --kind group --chat-id <openConversationId> -f ndjson
# 监听所有单聊或所有群消息
dws event consume user_im_message_receive_o2o_all --flatten -f ndjson
dws event consume user_im_message_receive_group_all --flatten -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
@@ -507,14 +520,20 @@ dws event consume user_im_group_member_added --group <openConversationId> --flat
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_im_message_receive_o2o \
user_im_message_read_o2o \
user_im_message_recall_o2o \
--user <userId> \
--flatten \
-f ndjson
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
@@ -618,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
@@ -630,9 +649,9 @@ dws aitable record query --base-id BASE_ID --table-id TABLE_ID --fields invocati
<summary><strong>Schema 自省</strong> — Agent 命令发现与执行契约</summary>
```bash
dws schema aitable # 发现产品命令
dws schema "aitable record query" # 查看选中 leaf 契约
dws schema "aitable record query" --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/审计/基线的全量导出
```
@@ -708,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>
@@ -759,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 层
+63 -5
View File
@@ -13,13 +13,71 @@ if (!fs.existsSync(binaryPath)) {
process.exit(1);
}
const result = childProcess.spawnSync(binaryPath, process.argv.slice(2), {
// Interactive commands must remain in the terminal's foreground session so
// prompts can use /dev/tty. Non-interactive launches use a separate process
// group, allowing a signal sent only to this wrapper to reach the full vendor
// process tree exactly once.
const isolateVendorProcessGroup = process.platform !== "win32" && !process.stdin.isTTY;
const child = childProcess.spawn(binaryPath, process.argv.slice(2), {
stdio: "inherit",
detached: isolateVendorProcessGroup,
});
if (result.error) {
console.error(result.error.message);
process.exit(1);
let spawnFailed = false;
let forwardedSignal = null;
const forwardedSignals = ["SIGINT", "SIGTERM"];
function forwardSignal(signal) {
forwardedSignal = signal;
if (child.exitCode === null && child.signalCode === null) {
if (process.platform === "win32") {
child.kill(signal);
return;
}
if (!isolateVendorProcessGroup) {
// Ctrl-C is generated for the whole foreground process group, including
// the vendor. SIGTERM is not terminal-generated and still needs an
// explicit handoff when a process manager targets only this wrapper.
if (signal === "SIGTERM") {
child.kill(signal);
}
return;
}
try {
// detached makes the vendor PID the leader of its POSIX process group.
// Signal the whole group so any subprocesses inherit the same shutdown.
process.kill(-child.pid, signal);
} catch (error) {
// The group may have completed between the state check and kill.
if (error.code !== "ESRCH") {
throw error;
}
}
}
}
process.exit(result.status === null ? 1 : result.status);
const signalHandlers = new Map(
forwardedSignals.map((signal) => [signal, () => forwardSignal(signal)]),
);
for (const signal of forwardedSignals) {
process.on(signal, signalHandlers.get(signal));
}
child.on("error", (error) => {
spawnFailed = true;
console.error(error.message);
});
child.on("close", (code, signal) => {
for (const forwarded of forwardedSignals) {
process.removeListener(forwarded, signalHandlers.get(forwarded));
}
const exitSignal = forwardedSignal || signal;
if (exitSignal && process.platform !== "win32") {
process.kill(process.pid, exitSignal);
return;
}
process.exitCode = spawnFailed || code === null ? 1 : code;
});
+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"
}
}
+147 -2
View File
@@ -17,18 +17,23 @@
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))
}
@@ -112,7 +117,11 @@ func runGenerate(args []string, stdout, stderr io.Writer) error {
defer i18n.SetLang(previousLang)
i18n.SetLang("en")
snapshot := interfacesnapshot.Capture(app.NewRootCommand())
root := newRootCommand()
snapshot := interfacesnapshot.Capture(root)
if err := validateHelpRendering(root, snapshot); err != nil {
return err
}
if *output == "-" {
return interfacesnapshot.Write(stdout, snapshot)
}
@@ -138,6 +147,26 @@ func runCompare(args []string, stdout, stderr io.Writer) (bool, error) {
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
}
@@ -150,6 +179,19 @@ func runCompare(args []string, stdout, stderr io.Writer) (bool, error) {
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 {
@@ -170,6 +212,57 @@ func runCompare(args []string, stdout, stderr io.Writer) (bool, error) {
}
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("", " ")
@@ -179,6 +272,58 @@ func runCompare(args []string, stdout, stderr io.Writer) (bool, error) {
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 {
@@ -191,5 +336,5 @@ func readSnapshot(path string) (interfacesnapshot.Snapshot, error) {
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]")
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]")
}
+570
View File
@@ -15,11 +15,16 @@ 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) {
@@ -56,6 +61,24 @@ func TestCrossPlatformCoverageRunGenerateCapturesActualRootOffline(t *testing.T)
}
}
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")
@@ -83,6 +106,464 @@ func TestCrossPlatformCoverageRunCompareUsesBothSnapshotInputsAndExitCode(t *tes
}
}
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 {
@@ -120,6 +601,95 @@ func writeSnapshot(t *testing.T, dir, name string, snapshot interfacesnapshot.Sn
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 {
+66 -2
View File
@@ -15,12 +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 exit = os.Exit
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() {
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 },
)
}
+206 -13
View File
@@ -1,27 +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 TestCrossPlatformCoverageMainExitsWithSuccessfulVersionCommand(t *testing.T) {
previousExit := exit
previousArgs := os.Args
t.Cleanup(func() {
exit = previousExit
os.Args = previousArgs
})
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
code := -1
exit = func(value int) {
trackRun(clitrack.Config{}, func() error {
called = true
code = value
return nil
}, nil)
if !called {
t.Fatal("default tracker did not execute callback")
}
os.Args = []string{"dws", "version"}
}
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()
if !called || code != 0 {
t.Fatalf("main exit = called %v, code %d", called, code)
}
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)
}
}
-1
View File
@@ -40,7 +40,6 @@
- `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)
+216 -5
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,9 +62,171 @@ make lint
git diff --check
```
## Reviewer Router GitHub App
Reviewer requests and merge authority intentionally use different identities.
The base-owned `pull_request_target` workflow may use its built-in
`GITHUB_TOKEN` to request reviewers, but it must mint a dedicated GitHub App
installation token before enabling auto-merge. GitHub suppresses most workflow
events created by the built-in token; using it for auto-merge prevents the
merge commit's `push` workflows from running and leaves the exact-SHA Coverage
baseline without a trusted main-scoped producer.
Configure the dedicated App before merging a workflow revision that requires
it:
- install it only on `DingTalk-Real-AI/dingtalk-workspace-cli`;
- grant only `Contents: read and write` and `Pull requests: read and write`;
- set repository variable `REVIEWER_ROUTER_APP_CLIENT_ID` to its client ID;
- set `REVIEWER_ROUTER_APP_SLUG` to its exact lowercase slug;
- set repository secret `REVIEWER_ROUTER_APP_PRIVATE_KEY` to its private key;
- create one active repository branch ruleset named `main-merge-writers`,
targeting only `refs/heads/main`, with exactly one `Restrict updates` rule
(`update_allows_fetch_and_merge: false`). GitHub may project that strict
value through the read APIs as `{type: "update"}` with `parameters` omitted;
consumers accept only that exact omission or a one-field `parameters` object
containing explicit boolean `false`, and reject every other present shape or
value. They then bind the same ruleset node through GraphQL and require its
non-null `updateAllowsFetchAndMerge` value to be exactly `false`;
- give that ruleset exactly three bypass actors: the Reviewer Router App as an
`Integration` in `pull_request` mode, plus `haofeng0705` (ID `30925823`) and
`PeterGuy326` (ID `47820304`) in `always` mode for Formula publication and
break-glass recovery;
- never give the App bypass on `main-protection`, `main-quality`, or any other
ruleset, and never reuse `HOMEBREW_PR_TOKEN`,
`RELEASE_GOVERNANCE_TOKEN`, or a personal token for Reviewer Router.
The workflow limits each minted token to the current repository, requests the
two permissions explicitly, and lets the token action revoke it at job end.
It also requires the minted App slug to equal the reviewed repository variable;
there is no `GITHUB_TOKEN` fallback. Before reading App credentials, the
base-owned workflow revalidates the event's exact base/head and uses its
built-in token only to disable an existing request owned by
`github-actions[bot]` or one whose title or merge metadata requests that GitHub
skip workflows. A mint or permission failure therefore leaves that PR
manual-merge only. The built-in token's `Contents: write` permission is
isolated to this trusted cleanup job and is never used to enable auto-merge;
review routing keeps `Contents: read`. Existing requests owned by a human or
another non-built-in identity are replaced with the exact dedicated-App
request after token minting. Only an already App-owned request with the fixed
headline/body is preserved. The required `Test` context reads the live
repository settings and applied rulesets, verifies the exact writer-rule
shape, and requires its own built-in Actions identity to report
`current_user_can_bypass: never`. Before enabling or reconciling auto-merge,
the minted App independently requires `pull_requests_only` on that writer rule
and `never` on every other active main ruleset. These identity-relative checks
remain available to low-privilege tokens; GitHub deliberately hides the full
`bypass_actors` list from callers without ruleset-write access. Operators must
therefore inspect that list during rollout and keep it at the exact three actors
above. The required `Test` context then briefly waits for the concurrent
router takeover and accepts only a null request or the configured App owner
with exact fixed metadata. A null request is safe for this failure mode because
the built-in Actions identity cannot pass the writer rule; other permitted
identities emit either a protected-main push or the trusted closed-PR repair.
Draft PRs skip this identity check; the explicit `ready_for_review` trigger
reruns admission when they become merge-eligible,
while `edited` and `auto_merge_enabled` rerun both workflows when the PR title
or merge request changes. A human `auto_merge_disabled` event reruns CI without
silently re-enabling the request, leaving it available only to the designated
break-glass identity. The required `Test` context rejects GitHub workflow-skip
directives in the PR title or an existing auto-merge request and verifies the
repository's reviewed `MERGE_MESSAGE` title plus `PR_TITLE` or `BLANK` body
defaults. GitHub does not expose those merge-related settings to the read-only
admission token: the classifier accepts only both exact reviewed values or the
complete omission of both properties, and rejects partial omission, `null`, or
any other value. Before any enable, reconcile, or merge mutation, the dedicated
App's current-repository token (which has `Contents: write`) must observe both
exact reviewed values. The dedicated App binds each mutation to the exact head
OID and supplies a fixed safe headline and body, so GitHub cannot copy an unsafe
PR title into its merge commit.
After enabling, the workflow requires the owner to equal the token action's
exact `<app-slug>[bot]` output. If the event base/head changes during the
mutation window, it removes only that App-owned request and fails the run.
The App-owned native auto-merge request is the reviewed automation intent, not
the sole executor: GitHub's deferred auto-merge path does not reliably apply a
GitHub App's pull-request-only ruleset bypass. A zero-permission approval-signal
workflow converts submitted or dismissed reviews into `workflow_run`; completed
admission workflows use the same trusted default-branch trigger. The serialized
reconcile job treats `workflow_run` only as a wake-up signal: it never reads the
triggering run's pull-request payload or artifacts and never checks out code
from that run. It enumerates open `main` PRs again through the API, then
revalidates the safe App owner, metadata, and ruleset boundary immediately
before calling the synchronous PR merge endpoint with the exact current head
SHA. The preflight requires exactly one repository-owned `main-protection`
ruleset with one latest-head approval and exactly one repository-owned
`main-quality` ruleset with the reviewed nine strict checks. The App must report
`never` on both and on every other non-writer ruleset. Every required context
must be bound to the GitHub Actions App (`integration_id=15368`); a missing,
different, or duplicate context/source entry fails closed together with
deletion or weakening of either gate. HTTP 405 means the PR is not ready,
while 409 means its revision
changed; either remains open for the next event. Other failures make
reconciliation red. A concurrent native merge is accepted only after the final
PR state proves the exact head, App identity, and non-empty merge SHA.
A staggered twice-hourly schedule provides eventual recovery if a webhook or
workflow completion is delayed, and `workflow_dispatch` remains the on-demand
repair path.
The break-glass publisher must preserve a safe final commit message;
`[skip ci]`, `[ci skip]`, `[no ci]`, `[skip actions]`,
`[actions skip]`, and a `skip-checks: true` trailer are forbidden outside the
release-controlled Formula-only path below.
GitHub may suppress `pull_request_target` entirely for security-sensitive head
branch names, including names that look like commit SHAs. Such a PR receives
neither App takeover nor the closed-event repair. Rename the head branch for
the normal path; if break-glass merge is unavoidable, preserve a safe final
message so the protected-main push CI remains the authoritative producer.
After installing the App, the protected-main push that deploys this workflow
runs reconciliation automatically. Approval-signal and admission-workflow
completions run the same serialized recovery path. The job enumerates open,
ready `main` PRs
with any non-App owner, unsafe App metadata, or workflow-skip metadata. It
revalidates each base/head, converges a safe request to the exact dedicated-App
owner and fixed message, and leaves a workflow-skipping request disabled for
manual correction. It never enables auto-merge where the request was already
null. Every exact safe App request is then attempted through the synchronous,
SHA-bound merge endpoint; a server-declared not-ready result remains open for
the next event. A mid-migration failure leaves the affected PR disabled for a
fresh routing event or break-glass merge. One PR failure is recorded
without preventing later legacy owners from being attempted; the batch ends
red with a per-PR summary. Manually dispatch `Reviewer routing` from `main`
until the failed count is zero.
Disabling the App-owned auto-merge request before the reconcile job's final PR
read leaves that PR manual-only. That final read is the cancellation
linearization point: GitHub's merge API can condition atomically on the head SHA
but not on the auto-merge request itself, so a disable racing after that read may
lose to an already-issued merge request. To stop an in-flight attempt
before the merge endpoint accepts it, close the PR or change its head; if the
server observes that state first, it rejects the state/SHA-bound merge. No
client-side action can revoke a merge that GitHub has already accepted.
The endpoint has no equivalent expected-base parameter. The workflow therefore
checks `base=main` and the repository before and after merge and fails any
retargeted result, but a retarget racing after the final read cannot be made
atomic client-side. Never retarget a PR while its App-owned intent is active:
disable the request, wait until all running `Reviewer routing` reconciliation
jobs finish, and only then change the base. Preventing a malicious same-instant
retarget requires a GitHub-side branch/ruleset control rather than workflow
code.
A PR that introduces or rotates this identity still runs the old base-owned
router. Install/configure the App and activate the exact writer ruleset first;
this blocks its legacy `github-actions[bot]` request from writing `main`. After
the governance PR's final push, disable that old request, confirm the live
settings/ruleset contract and all required checks are green for the exact head,
then have only `haofeng0705` or `PeterGuy326` merge that head with the
repository-generated safe merge message. Verify the resulting merge SHA has a
`CI` run with `event=push`,
a successful `Coverage` context, and an exact-SHA baseline cache under
`refs/heads/main`. Confirm automatic reconciliation reports zero failures and
zero non-App owners. Finally use a normal canary PR to verify that the dedicated
App is both `enabledBy` and `mergedBy`, and that the same post-merge chain
repeats before declaring the rollout complete.
## Homebrew Formula Delivery
Official releases use the Release workflow's built-in `GITHUB_TOKEN` to update
Official releases use the designated `HOMEBREW_PR_TOKEN` identity 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
@@ -72,11 +234,60 @@ 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
Actions App to bypass this repository's rulesets. Its owner is the designated
always-bypass actor for controlled Formula publication and break-glass recovery,
including on `main-merge-writers`. 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.
exactly matches this release's verified bytes. Formula commits retain
`[skip ci]`, so the sealing step exposes only the reviewed commit identity to
an independent confirmation job. That job creates the
`Coverage Baseline Cache` acknowledgement and emits the reviewed
`coverage-baseline-promote` repository dispatch. The default-branch
`Coverage Baseline Promotion` workflow independently verifies the exact
single-parent Formula commit, both parent and target admission contexts, and
default-branch containment before checking out the target. It restores only
the exact parent profile, recomputes the complete profile if that cache is
absent, and saves the Formula SHA under the `main` cache scope. Because the
cache save action treats upload errors as warnings, a second lookup must report
`cache-hit=true` for the exact target key before the producer succeeds. The
promotion completes the unique acknowledgement, and the confirmation job
waits for that exact check-run ID. npm and mirror publication depend only on
the immutable release job, so a transient
cache-service failure cannot strand an otherwise valid release between
channels; the final release-delivery gate still fails until the exact cache is
confirmed. Once Formula sealing exposes the target SHA, the confirmation job
also runs when a later immutable-package recheck fails, so a post-push failure
cannot orphan the producer. Rerun the failed promotion/confirmation path after
repairing the producer. Never add a prefix `restore-keys` fallback to this path.
`Coverage Baseline Repair` is the independent safety net for every merged PR.
Its base-owned `pull_request_target: closed` job never checks out or executes PR
content: it binds the closed event's PR number and stable head SHA to the
current merged-PR facts (`merged_at`, `base.ref`, and `merge_commit_sha`) and
proves that merge commit is contained in `main`. It deliberately does not
compare REST `base.sha`, because that field follows the live base branch and
can move after the merge. Only then does it emit a
`coverage-baseline-repair` repository dispatch. Workflow-skip directives alone
do not suppress `pull_request_target`, subject to GitHub's separate
security-sensitive branch-name restriction described above. The low-trust
trigger is forbidden from writing the default-branch cache directly. Before
dispatching, it gives Actions event delivery one minute to expose a run from
the exact protected `.github/workflows/ci.yml` workflow and exits if that normal producer already
owns the SHA, avoiding a duplicate full-suite run. A successful CI producer
must hard-verify its exact cache key. If that run instead completes with any
non-success conclusion, a separate base-owned `workflow_run` dispatcher binds
the exact workflow ID/path, run ID/attempt, conclusion, repository, branch, and
head SHA before requesting repair. `workflow_run` also has read-only
default-branch cache access, so both dispatchers use the reviewed
`repository_dispatch` exception. The dispatched default-branch producer
revalidates the corresponding merged-PR or failed-CI identity before checkout,
restores only the exact target key, recomputes the complete profile on a miss,
and verifies `cache-hit=true` after saving. An hourly schedule refreshes the
event-time `main` SHA after direct break-glass pushes or cache eviction;
`workflow_dispatch` provides the same current-main repair on demand. The
dedicated App identity remains mandatory because events created by the built-in
`GITHUB_TOKEN` can suppress both the main push and the closed-PR event.
Keep `HOMEBREW_PR_TOKEN` repository-scoped with `Contents: write` and
`Pull requests: write` (the latter remains necessary for withdrawal rollback),
+254 -27
View File
@@ -48,8 +48,12 @@ It then runs:
--fast-path "$PR_BASE_SHA" HEAD
```
Because the verified PR diff contains only `CHANGELOG.md`, the validator and
its policy dependencies in that merge tree are byte-for-byte the current base
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.
@@ -79,10 +83,12 @@ 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 permits the
second file but still rejects invalid dates or versions, missing bullets,
placeholder `TODO`/`TBD`, unmanaged-section changes, and unsafe tree modes.
Adding a second file therefore cannot bypass CHANGELOG validation.
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
@@ -141,13 +147,112 @@ 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.
The branch rulesets keep one human approval and all nine strict required
contexts, require someone other than the latest pusher to approve after the
most recent head update, and restrict `main` updates to the dedicated Reviewer
Router App in pull-request mode plus the designated Formula publishers and
break-glass identities. Repository auto-merge is enabled for ready PRs, so the
App-owned request records the automation intent while the App's synchronous
merge path waits for that approval and the current revision's nine green
checks. If `main` advances, strict checks rerun before merge. The
reviewer routing job uses the built-in `GITHUB_TOKEN` to request reviewers with
`Contents: read` and `Pull requests: write`. A separate base-owned cleanup job
isolates the merge-authority permissions (`Contents: write` and `Pull
requests: write`), revalidates the exact event base/head, and uses the built-in
token only to disable an existing request owned by `github-actions[bot]` or one
whose title or merge metadata requests that GitHub skip workflows; it never
enables auto-merge. The job then mints a current-repository installation token
for the dedicated Reviewer Router GitHub App, proves its emitted slug matches
the reviewed `REVIEWER_ROUTER_APP_SLUG`, replaces every non-App request, and
enables native auto-merge with fixed metadata. This
identity boundary is required because GitHub suppresses
most workflow events created by the built-in token; using it for auto-merge would
silently skip the merge commit's protected-main CI and baseline-cache
producer. Token minting or takeover fails closed without falling back to
`GITHUB_TOKEN`: the unsafe request is cleared before credentials are read, and
the required `Test` context live-verifies the exact `main-merge-writers` update
rule. GitHub's read APIs may omit `parameters` for the strict
`update_allows_fetch_and_merge: false` value, so the gate accepts only that
exact omission or a one-field `parameters` object containing explicit boolean
`false`; every other present shape or value fails closed. The gate then binds
the same ruleset node through GraphQL and requires its non-null
`updateAllowsFetchAndMerge` value to be exactly `false`. It also requires its
own built-in token to report
`current_user_can_bypass: never`. The minted App separately requires
`pull_requests_only` on that writer rule and `never` on every other active main
ruleset before it can enable, reconcile, or synchronously merge. The read-only
`Test`
token may receive a repository projection with both merge-default properties
omitted; it accepts only that complete omission or exact `MERGE_MESSAGE` plus
`PR_TITLE`/`BLANK`, while partial or malformed projections fail closed.
The same unprivileged `pull_request` job may receive an empty repository-variable
projection for an external fork. Only when the event head repository differs
from the base repository does it substitute the exact reviewed public slug
`dingtalk-dws-reviewer-router` for identity comparison. An empty variable on a
same-repository PR and every malformed non-empty value still fail closed. This
fallback neither mints a token nor grants merge authority; the base-owned
Router continues to require its minted App slug to equal the repository
variable before any mutation. The minted App's `Contents: write` token must
observe the exact reviewed defaults
before either mutation path proceeds. GitHub hides the complete
`bypass_actors` list from low-privilege callers, so the rollout audit must still
keep the writer list at exactly the Reviewer App, `haofeng0705` (ID
`30925823`), and `PeterGuy326` (ID `47820304`). The required check finally
accepts a null or exact App-owned
request after a short takeover grace period. Null is safe from the suppressed
event path because the built-in Actions identity cannot update `main`; other
permitted identities produce either a main push or the trusted closed-PR
repair. Drafts skip the identity step, while `ready_for_review`, `edited`,
`auto_merge_enabled`, and `auto_merge_disabled` explicitly start fresh admission
for readiness, title, and merge-request changes. Router does not react to
`auto_merge_disabled`, so a
human can deliberately leave the PR manual-only for break-glass handling.
Reviewer routing remains available. The protected-main push that deploys the
workflow automatically migrates every open, ready non-App request and repairs
unsafe App metadata; it disables workflow-skipping requests for correction.
Because GitHub's deferred native auto-merge path does not reliably apply an
App's pull-request-only ruleset bypass, a zero-permission approval-signal
workflow and completed `CI` / `Code Admission — AI Behavior` workflows wake the
same trusted default-branch reconciliation through `workflow_run`. That event
is only a wake-up signal: the privileged job does not consume its pull-request
payload or artifacts and does not check out the triggering run's code. It
re-enumerates open `main` PRs through the API and attempts only an exact
App-owned request through the synchronous PR merge endpoint. Immediately before
each attempt it revalidates the App's ruleset boundary and PR intent, supplies
the current head SHA, and treats server-declared not-ready or
concurrent-revision responses as retriable. The live preflight requires the
exact repository-owned approval ruleset and exact nine-check strict quality
ruleset, with every context bound to the GitHub Actions App
(`integration_id=15368`) and the Reviewer Router App unable to bypass either;
a missing, disabled, incorrectly sourced, or weakened gate fails closed before
merge. GitHub—not the workflow—decides whether the
merge is admissible. A staggered twice-hourly schedule provides eventual
recovery, and a manual `workflow_dispatch` from `main` is the immediate
idempotent retry path.
Reconciliation never enables an originally null request. The reviewer router
is orchestration, not a quality context, and
must not be added to the ruleset.
Disabling the App-owned request before the reconcile job's final PR read keeps
the PR manual-only. GitHub can atomically bind the subsequent merge to the head
SHA, but it cannot bind that call to the auto-merge intent; a disable racing
after the final read may therefore lose to the in-flight merge. Closing the PR
or changing its head blocks the attempt only if GitHub observes that state
before accepting the merge endpoint call; no client-side action can revoke a
merge that the server has already accepted.
The merge endpoint has no expected-base precondition. Reconciliation checks
that the base is this repository's `main` immediately before and after the call,
but a retarget racing after the final read is not atomically preventable in the
workflow. Operators must disable the App-owned intent and wait for all running
`Reviewer routing` reconciliation jobs to finish before retargeting a PR; a
stronger adversarial guarantee requires a GitHub-side branch/ruleset control.
GitHub may omit `pull_request_target` for security-sensitive head branch names,
including names that look like commit SHAs. Those PRs cannot use Router App
takeover or the closed-event repair: rename the branch for the supported path,
or use the designated break-glass identity with a safe final message so main
push CI remains the exact-SHA producer.
## Running focused gates locally
@@ -157,28 +262,128 @@ expected to repeat every CI job locally:
```sh
make build
make policy
make interface-integrity
make authoritative-interface-integrity BASE_REF=<merge-base>
make schema-compatibility BASE_REF=<merge-base>
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/...
```
For an exact CHANGELOG-only branch:
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 --fast-path "$base_ref" HEAD
./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. Supporting and (when platform-selected) native profiles are
generated before the aggregate `Coverage` context evaluates them. The
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. PR concurrency is keyed by PR number, so a later
revision cancels the stale run instead of letting obsolete test matrices
compete with the replacement for hosted runners. If cancellation interrupts a
cold-cache fallback, the latest run recomputes the same exact merge-base
profile authoritatively. Main concurrency remains keyed by pushed SHA, so a
newer main push cannot cancel a predecessor's producer.
Every supported main advancement path has an exact-SHA producer. The required
`Test` context rejects GitHub workflow-skip directives in PR and auto-merge
metadata, reruns when that metadata is enabled, disabled, or edited, and
verifies the live App/writer-ruleset identity contract. Reviewer Router
additionally binds auto-merge to the exact head OID and writes a fixed safe
merge headline/body. The sole break-glass publisher must retain a safe final
message; the release-controlled Formula-only path
is the sole supported use of `[skip ci]`. A full source push
saves the assembled profile after the aggregate gate passes. A trusted
documentation or release-seal push independently verifies that the complete
`before...after` diff contains only the reviewed metadata allowlist, restores
only the exact `before` cache, recomputes the full profile if the chain is
cold, and makes that helper a dependency of the required `Coverage` context.
Release-generated Formula commits intentionally retain `[skip ci]`; after
their nine synthetic contexts are sealed, an independent release-governance
job creates an acknowledgement and emits a `coverage-baseline-promote`
repository dispatch. The default-branch promotion
workflow revalidates the exact single-parent Formula identity, successful
parent and target contexts, and main containment before it promotes the exact
parent cache or performs the same full fallback. Every target-main producer
follows its save with a lookup-only restore and requires
`cache-hit=true` for the exact key; this turns the cache action's otherwise
warning-only upload failure or prefix match into a hard failure. Formula
promotion additionally updates one release-created `Coverage Baseline Cache`
check. A separate confirmation job waits for that exact check-run ID while npm
and mirrors remain dependent only on the immutable publication job; cache
failure therefore makes the final delivery gate red without creating a
partially published release. Once Formula sealing exposes its SHA, a later
publication verification failure cannot suppress that confirmation job.
A separate base-owned `pull_request_target: closed` safety net covers the final
merged SHA even if a human or integration changes the merge message after PR
checks finish. Skip directives alone do not suppress `pull_request_target`,
subject to GitHub's separate security-sensitive branch-name restriction above.
That job executes no PR code and only dispatches after binding the exact
closed-event PR number and stable head SHA to merged-PR facts
(`merged_at`, `base.ref`, and `merge_commit_sha`) and proving `main`
containment. It does not compare the later REST `base.sha`, which follows the
live base branch after merge. Because GitHub makes default-branch caches
read-only to `pull_request_target`, the dispatcher first waits up to one minute
for a run from the exact protected
`.github/workflows/ci.yml` workflow and exits when that normal producer exists.
A successful main CI hard-verifies the exact key itself. A completed
non-success run starts a separate base-owned `workflow_run` dispatcher, which
binds the exact CI workflow ID/path, run ID/attempt, conclusion, upstream
repository, `main` branch, and head SHA. That trigger is also cache-read-only,
so either trusted dispatcher uses `repository_dispatch`; its producer
revalidates the merged-PR or failed-CI identity, checks out the contained SHA,
and produces/verifies the exact full cache.
An hourly schedule and a main-only manual dispatch repair the event-time main
SHA after a direct break-glass push or cache eviction. The dispatch exception
is intentional: unlike an ordinary event created by `GITHUB_TOKEN`, GitHub
allows `repository_dispatch` to start another workflow. A legacy built-in-token
merge can suppress the closed event too, which is why the required `Test`
identity gate and dedicated Reviewer Router App are still mandatory.
A cold miss can still occur during a producer race or after cache eviction,
but it remains fail-safe: the PR recomputes the authoritative baseline with a
30-minute job budget and saves a PR-scoped copy for same-PR reruns. It is no
longer possible for a supported main-advance path to omit its producer
silently. That PR-scoped fallback save remains a best-effort acceleration and
does not replace the normal push, metadata, Formula, and merged-PR repair
producers. 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
@@ -186,17 +391,32 @@ 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.
Compatibility checks derive authoritative Interface snapshots from the PR
merge-base and the latest reachable stable release. The candidate cannot bless
a breaking change by editing a fixture. Schema additions are allowed;
historical products, tools, parameters, mappings, positional execution fields,
constraints, and safety semantics remain protected.
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:
`main` advances. Every entry must select the GitHub Actions App
(`integration_id=15368`), not “any source”. It must require these exact
context/source pairs and no legacy aliases:
- `Lint`
- `Test`
@@ -215,4 +435,11 @@ 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.
reviewer router outside the required-context list. Install its dedicated
GitHub App only on this repository with `Contents: read and write` and `Pull
requests: read and write`; do not grant Actions, Workflows, or Administration.
Give it pull-request-only bypass on `main-merge-writers` and no bypass on any
other ruleset. Store the App client ID and lowercase slug in repository
variables `REVIEWER_ROUTER_APP_CLIENT_ID` and `REVIEWER_ROUTER_APP_SLUG`, and
its private key in repository secret `REVIEWER_ROUTER_APP_PRIVATE_KEY`. Do not
reuse release, Homebrew, or personal tokens for this boundary.
+270
View File
@@ -0,0 +1,270 @@
# CLI Help / Schema 兼容迁移治理
本文定义两种受控 flag 迁移:
1. `flag_rename`:保留旧 flag 的可执行兼容性,但把它从 Help 与 Agent Schema 中隐藏,并将新的规范 flag 设为唯一可见入口;rename 必须保持原 flag 的 requiredness,optional 只能迁到 optional,required 只能迁到 required。
2. `requiredness_change`:同一个公开 flag 从 optional 精确提升为 required;flag 的名称、类型、作用域、可见性、shorthand、`no_opt` 与 alias 关系必须保持不变。
两种原语都只放行清单精确登记的变化,不是通用 breaking-change 豁免,也不得在同一 command/flag 上叠加以绕过 rename 的 requiredness 保持规则。
同一套 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。历史未声明 `kind` 的记录按 `flag_rename` 解释;新增同名 requiredness 迁移必须显式写 `kind: requiredness_change` 和单一 `flag` before/after。清单中的 `pending` 记录只记录已评审计划,并授权其精确列出的后续产品迁移;候选与 merge-base 仍必须精确匹配 `before`,不能授权同一个提交中的接口变化,也不能作为其他命令或参数的通配豁免。
首次引入一个旧 merge-base 不认识的新 `kind` 时,机制 PR 不得同时写入该 kind 的 pending 记录,因为旧的 base-owned 严格解析器会拒绝未知字段。必须先合入 parser、lifecycle、CLI/Schema adapter 与 hostile tests;待这些实现成为新的 merge-base authority 后,再用独立治理审批 PR 新增 pending,最后才由产品 PR 消费。
## 跨命令迁移原语
`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` 的语义不在本轮证明范围,必须另行设计,不能借本清单放行。
如果 `command_move` 的参数 `from` 在更早 stable 中仍使用另一历史名称,Schema adapter
只能把同一 legacy command 上、已经由 base-owned lifecycle 返回且
`state=consumed` 的 flag rename 回执作为前驱边。例如
`group → conversation-id` 与 `conversation-id → open-topic-id` 可以组合,但不能把
candidate 自增的 pending 记录、其他命令的同名参数、参数概念词典或 CLI alias 当作证据。
首次消费 pending command 回执时,merge-base 的 normalized Schema 必须真实发布中间参数,
并逐跳验证参数签名和 constraints;command 回执合入为 consumed 后,中间 Schema 已从 main
消失,此时保留的两份 consumed 回执可继续对 stable 做受限重放,直到 stable 也达到 after
并让回执转为惰性记录或由独立 PR 清理。两种阶段都拒绝残留 predecessor/intermediate、字段漂移、环、分叉、
target 碰撞或 primary path/tool identity 不唯一;positionals 不在该组合授权面内。
`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 → inert/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。
## 两阶段迁移与回执清理
rename 以 `(kind, command, legacy flag, canonical flag)` 为唯一精确键;requiredness change 以 `(kind, command, flag)` 为唯一精确键。二者经历同一生命周期:
| 阶段 | PR 可以做什么 | 必须满足的快照状态 |
|---|---|---|
| 1. 治理审批 | 新增 `state: pending` 的精确记录;不得在同一个 PR 修改产品 surface | candidate 和 merge-base 都与记录中的 `before` 完全一致;该记录不改变 stable 的判断 |
| 2. 产品迁移 | merge-base 已拥有 `pending` 后,按记录一次性切到精确 `after`,并把记录改为 `state: consumed` | rename 的 legacy 仍存在但由 visible 变 hidden,且声明 `alias_of`,canonical requiredness 保持不变;requiredness change 只把同名 flag 从 optional 提升为 required |
| 3. 保留回执 | 产品 PR 合入后,如果 stable 仍是 `before`,继续保留 `consumed` | merge-base 或 stable 仍有任一份尚未达到 `after` |
| 4. 惰性保留或清理 | 当 merge-base 和 stable 都已经是 `after`,该记录不再提供任何授权;后续 PR 可以原样保留或删除 | 两份参考快照均精确匹配 `after`;保留时仍必须是不可改写的 `consumed`,接口偏离 `after` 继续失败 |
因此,新增 `pending` 和修改产品 surface 不能发生在同一个 PR;candidate 自己新增的记录不能 self-approve。迁移也不能部分执行:legacy、canonical、`alias_of` 或状态只要有一项不匹配,门禁即失败。stable 发布只会让已经追平的 `consumed` 回执变成无授权效果的审计记录,不会在没有代码变更时让后续业务 PR 失去合规性;清理仍可作为独立的账本压缩动作,但不再是下一个 PR 的强制前置条件。
下面只是清单结构示例,不代表已审批命令;实际字段必须从 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`。
同名 flag requiredness 迁移的清单结构如下;示例不代表已经审批:
```json
{
"version": 1,
"migrations": [
{
"kind": "requiredness_change",
"command": "dws report entry submit",
"flag": {
"name": "to-user-ids",
"before": {"present": true, "type": "string", "scope": "local"},
"after": {"present": true, "type": "string", "required": true, "scope": "local"}
},
"state": "pending",
"reason": "Reject report submissions that have no visible recipient."
}
]
}
```
## `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。
3. `requiredness_change` 中同名 flag 从 optional 提升为 required 时产生的 `flag_became_required`。
以下变化仍按普通兼容规则阻塞,不能被迁移记录掩盖:
- 删除 legacy、canonical、命令或其他 flag;
- flag 类型或迁移记录中的 scope、shorthand、`no_opt` 漂移;
- `alias_of` 缺失、指向变化或 alias chain;
- 命令路径及任何无关的阻塞性接口变化;
- requiredness change 同时发生的 rename、隐藏、类型、scope、shorthand、`no_opt` 或 alias 漂移;
- 不精确、部分完成、超出记录范围的 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 兼容。
`requiredness_change` 的 Schema adapter 只把历史同名 parameter 的 `required` 与
`cli_required` 提升到 candidate 的 `true` 值,并要求 candidate 两者都为 `true`。parameter
不存在、tool/path 不匹配时不制造 Schema surface;type、property、interface type、default、
format、enum、`required_when`、constraints、positionals 与 safety 等全部字段仍交给原 checker,
任何不相干漂移继续阻塞。
## 本地验证
先确保 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。

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